get(); /** @var Dealer $dealer */ foreach ($dealers as $dealer) { $dealerSheet = new DealerStockSheet($dealer); $sheetTitle = $dealerSheet->title(); // Handle duplicate sheet names $originalTitle = $sheetTitle; $counter = 1; while (in_array($sheetTitle, $usedNames)) { $sheetTitle = substr($originalTitle, 0, 28) . '_' . $counter; $counter++; } $usedNames[] = $sheetTitle; // Set the unique title $dealerSheet->setUniqueTitle($sheetTitle); $sheets[] = $dealerSheet; } return $sheets; } } class DealerStockSheet implements FromCollection, WithTitle, WithHeadings, WithStyles, WithColumnWidths { protected $dealer; protected $uniqueTitle; public function __construct(Dealer $dealer) { $this->dealer = $dealer; } public function collection() { // Get all products with stock for this dealer $stocks = $this->dealer->stocks() ->with(['product.category']) ->whereHas('product', function($query) { $query->where('active', true); }) ->get(); $data = collect(); $no = 1; foreach ($stocks as $stock) { $product = $stock->product; $data->push([ 'no' => $no++, 'kode_produk' => $product->code, 'nama_produk' => $product->name, 'kategori' => $product->category ? $product->category->name : '-', 'satuan' => $product->unit ?? '-', 'stok' => number_format($stock->quantity, 2) ]); } // If no stock, add empty row if ($data->isEmpty()) { $data->push([ 'no' => '-', 'kode_produk' => '-', 'nama_produk' => 'Tidak ada stok produk', 'kategori' => '-', 'satuan' => '-', 'stok' => '0' ]); } return $data; } public function setUniqueTitle(string $title): void { $this->uniqueTitle = $title; } public function title(): string { if (isset($this->uniqueTitle)) { return $this->uniqueTitle; } // Clean dealer name for sheet title (remove invalid characters and handle edge cases) $cleanName = $this->dealer->name; // Remove parentheses and their contents $cleanName = preg_replace('/\([^)]*\)/', '', $cleanName); // Remove dots, commas, and other special characters $cleanName = preg_replace('/[^A-Za-z0-9\-_ ]/', '', $cleanName); // Clean up multiple spaces and trim $cleanName = preg_replace('/\s+/', ' ', trim($cleanName)); // If name is empty after cleaning, use dealer ID if (empty($cleanName)) { $cleanName = 'Dealer_' . $this->dealer->id; } // Limit to 31 characters and ensure no leading/trailing spaces $cleanName = trim(substr($cleanName, 0, 31)); // Ensure it doesn't end with a space (which can cause Excel issues) return rtrim($cleanName); } public function headings(): array { return [ 'No', 'Kode Produk', 'Nama Produk', 'Kategori', 'Satuan', 'Stok' ]; } public function styles(Worksheet $sheet) { // Add dealer info at the top first $sheet->insertNewRowBefore(1, 2); $sheet->setCellValue('A1', 'STOK PRODUK DEALER: ' . strtoupper($this->dealer->name)); $sheet->setCellValue('A2', 'Tanggal Export: ' . now()->format('d/m/Y H:i:s')); // Merge cells for dealer info $sheet->mergeCells('A1:F1'); $sheet->mergeCells('A2:F2'); $lastRow = $sheet->getHighestRow(); // Style dealer info $sheet->getStyle('A1:A2')->applyFromArray([ 'font' => ['bold' => true, 'size' => 12], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER] ]); // Style headers (row 3 after inserting 2 rows) $sheet->getStyle('A3:F3')->applyFromArray([ 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]] ]); // Style data rows if they exist if ($lastRow > 3) { $sheet->getStyle('A4:F' . $lastRow)->applyFromArray([ 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'CCCCCC']]], 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER] ]); // Center align specific columns $sheet->getStyle('A4:A' . $lastRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle('E4:F' . $lastRow)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); } return $sheet; } public function columnWidths(): array { return [ 'A' => 8, // No 'B' => 15, // Kode Produk 'C' => 30, // Nama Produk 'D' => 20, // Kategori 'E' => 12, // Satuan 'F' => 12 // Stok ]; } }