From 2f9c73576bc08efc85468330986cc223928eff88 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 14 Dec 2025 18:03:12 +0700 Subject: [PATCH] feat(exports): Add export functionality for brands, categories, formulas, perfumes, and products with Excel integration and UI buttons for user access --- app/Exports/BrandExport.php | 132 ++++++++++++++ app/Exports/CategoryExport.php | 135 ++++++++++++++ app/Exports/FormulaExport.php | 138 ++++++++++++++ app/Exports/PerfumeExport.php | 168 ++++++++++++++++++ app/Exports/ProductExport.php | 153 ++++++++++++++++ app/Http/Controllers/ExportController.php | 69 +++++++ composer.json | 1 + composer.lock | 2 +- .../livewire/studio/catalog/brands.blade.php | 15 +- .../studio/catalog/categories.blade.php | 18 +- .../studio/catalog/perfume/index.blade.php | 15 +- .../studio/catalog/product/index.blade.php | 15 +- .../livewire/studio/master/formulas.blade.php | 27 +-- routes/pages/studio.php | 10 ++ 14 files changed, 868 insertions(+), 30 deletions(-) create mode 100644 app/Exports/BrandExport.php create mode 100644 app/Exports/CategoryExport.php create mode 100644 app/Exports/FormulaExport.php create mode 100644 app/Exports/PerfumeExport.php create mode 100644 app/Exports/ProductExport.php create mode 100644 app/Http/Controllers/ExportController.php diff --git a/app/Exports/BrandExport.php b/app/Exports/BrandExport.php new file mode 100644 index 0000000..c438588 --- /dev/null +++ b/app/Exports/BrandExport.php @@ -0,0 +1,132 @@ +name, + formatDateLocalized($brand->created_at, 'd/m/Y H:i'), + formatDateLocalized($brand->updated_at, 'd/m/Y H:i'), + ]; + } + + public function columnWidths(): array + { + return [ + 'A' => 20, // Brand Name + 'B' => 18, // Created Date + 'C' => 18, // Updated Date + ]; + } + + /** + * @return array + */ + public function styles(Worksheet $sheet) + { + $lastRow = $sheet->getHighestRow(); + $lastColumn = $sheet->getHighestColumn(); + + // Header style + $headerStyle = [ + 'font' => [ + 'bold' => true, + 'size' => 12, + 'color' => ['rgb' => 'FFFFFF'], + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => '71717A'], // Zinc - elegant + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]; + + // Data rows style + $dataStyle = [ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_TOP, + ], + ]; + + return [ + // Header styling + 1 => $headerStyle, + + // Data rows styling + 'A2:'.$lastColumn.$lastRow => $dataStyle, + ]; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + // Auto-size columns with max width + foreach (range('A', 'C') as $column) { + $event->sheet->getColumnDimension($column)->setAutoSize(true); + } + + // Set row height for header + $event->sheet->getRowDimension(1)->setRowHeight(25); + }, + ]; + } +} diff --git a/app/Exports/CategoryExport.php b/app/Exports/CategoryExport.php new file mode 100644 index 0000000..dc67714 --- /dev/null +++ b/app/Exports/CategoryExport.php @@ -0,0 +1,135 @@ +name, + strip_tags($category->description) ?: '-', + formatDateLocalized($category->created_at, 'd/m/Y H:i'), + formatDateLocalized($category->updated_at, 'd/m/Y H:i'), + ]; + } + + public function columnWidths(): array + { + return [ + 'A' => 20, // Category Name + 'B' => 30, // Description + 'C' => 18, // Created Date + 'D' => 18, // Updated Date + ]; + } + + /** + * @return array + */ + public function styles(Worksheet $sheet) + { + $lastRow = $sheet->getHighestRow(); + $lastColumn = $sheet->getHighestColumn(); + + // Header style + $headerStyle = [ + 'font' => [ + 'bold' => true, + 'size' => 12, + 'color' => ['rgb' => 'FFFFFF'], + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => '78716C'], // Stone - elegant + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]; + + // Data rows style + $dataStyle = [ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_TOP, + ], + ]; + + return [ + // Header styling + 1 => $headerStyle, + + // Data rows styling + 'A2:'.$lastColumn.$lastRow => $dataStyle, + ]; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + // Auto-size columns with max width + foreach (range('A', 'D') as $column) { + $event->sheet->getColumnDimension($column)->setAutoSize(true); + } + + // Set row height for header + $event->sheet->getRowDimension(1)->setRowHeight(25); + }, + ]; + } +} diff --git a/app/Exports/FormulaExport.php b/app/Exports/FormulaExport.php new file mode 100644 index 0000000..2849eff --- /dev/null +++ b/app/Exports/FormulaExport.php @@ -0,0 +1,138 @@ +quality, + $formula->size, + $formula->volume, + formatDateLocalized($formula->created_at, 'd/m/Y H:i'), + formatDateLocalized($formula->updated_at, 'd/m/Y H:i'), + ]; + } + + public function columnWidths(): array + { + return [ + 'A' => 18, // Quality + 'B' => 15, // Size (ML) + 'C' => 15, // Volume (ML) + 'D' => 21, // Created Date + 'E' => 21, // Updated Date + ]; + } + + /** + * @return array + */ + public function styles(Worksheet $sheet) + { + $lastRow = $sheet->getHighestRow(); + $lastColumn = $sheet->getHighestColumn(); + + // Header style + $headerStyle = [ + 'font' => [ + 'bold' => true, + 'size' => 12, + 'color' => ['rgb' => 'FFFFFF'], + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => '475569'], // Slate - elegant + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]; + + // Data rows style + $dataStyle = [ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_TOP, + ], + ]; + + return [ + // Header styling + 1 => $headerStyle, + + // Data rows styling + 'A2:'.$lastColumn.$lastRow => $dataStyle, + ]; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + // Auto-size columns with max width + foreach (range('A', 'E') as $column) { + $event->sheet->getColumnDimension($column)->setAutoSize(true); + } + + // Set row height for header + $event->sheet->getRowDimension(1)->setRowHeight(25); + }, + ]; + } +} diff --git a/app/Exports/PerfumeExport.php b/app/Exports/PerfumeExport.php new file mode 100644 index 0000000..9e61a4e --- /dev/null +++ b/app/Exports/PerfumeExport.php @@ -0,0 +1,168 @@ +get(); + } + + public function title(): string + { + return 'Data Parfum'; + } + + public function headings(): array + { + return [ + 'Aroma', + 'SKU', + 'Merek', + 'Konsentrasi', + 'Harga Jual', + 'Base Notes', + 'Middle Notes', + 'Top Notes', + 'Deskripsi', + 'Tanggal Dibuat', + 'Tanggal Diperbarui', + ]; + } + + /** + * @param Perfume $perfume + */ + public function map($perfume): array + { + return [ + $perfume->name, + $perfume->sku, + $perfume->brand?->name, + $perfume->concentration->label(), + $perfume->sale_price, + $perfume->base_notes, + $perfume->middle_notes, + $perfume->top_notes, + strip_tags($perfume->description), + formatDateLocalized($perfume->created_at, 'd/m/Y H:i'), + formatDateLocalized($perfume->updated_at, 'd/m/Y H:i'), + ]; + } + + public function columnWidths(): array + { + return [ + 'A' => 28, // Perfume Name + 'B' => 18, // SKU + 'C' => 23, // Brand + 'D' => 23, // Concentration + 'E' => 18, // Sale Price + 'F' => 23, // Base Notes + 'G' => 23, // Middle Notes + 'H' => 23, // Top Notes + 'I' => 35, // Description + 'J' => 21, // Created Date + 'K' => 21, // Updated Date + ]; + } + + /** + * @return array + */ + public function styles(Worksheet $sheet) + { + $lastRow = $sheet->getHighestRow(); + $lastColumn = $sheet->getHighestColumn(); + + // Header style + $headerStyle = [ + 'font' => [ + 'bold' => true, + 'size' => 12, + 'color' => ['rgb' => 'FFFFFF'], + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => '1E40AF'], // Navy Blue - elegant + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]; + + // Data rows style + $dataStyle = [ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_TOP, + 'horizontal' => Alignment::HORIZONTAL_LEFT, + 'indent' => 1, // Add padding + ], + ]; + + // Currency columns style (format currency) + $currencyStyle = [ + 'numberFormat' => [ + 'formatCode' => '"Rp" #,##0', + ], + ]; + + return [ + // Header styling + 1 => $headerStyle, + + // Data rows styling + 'A2:'.$lastColumn.$lastRow => $dataStyle, + + // Currency columns styling + 'E2:E'.$lastRow => $currencyStyle, + ]; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + // Auto-size columns with max width + foreach (range('A', 'K') as $column) { + $event->sheet->getColumnDimension($column)->setAutoSize(true); + } + + // Set row height for header + $event->sheet->getRowDimension(1)->setRowHeight(25); + }, + ]; + } +} diff --git a/app/Exports/ProductExport.php b/app/Exports/ProductExport.php new file mode 100644 index 0000000..1a8d543 --- /dev/null +++ b/app/Exports/ProductExport.php @@ -0,0 +1,153 @@ +name, + $product->sku, + $product->sale_price, + strip_tags($product->description) ?: '-', + formatDateLocalized($product->created_at, 'd/m/Y H:i'), + formatDateLocalized($product->updated_at, 'd/m/Y H:i'), + ]; + } + + public function columnWidths(): array + { + return [ + 'A' => 28, // Product Name + 'B' => 18, // SKU + 'C' => 18, // Sale Price + 'D' => 35, // Description + 'E' => 21, // Created Date + 'F' => 21, // Updated Date + ]; + } + + /** + * @return array + */ + public function styles(Worksheet $sheet) + { + $lastRow = $sheet->getHighestRow(); + $lastColumn = $sheet->getHighestColumn(); + + // Header style + $headerStyle = [ + 'font' => [ + 'bold' => true, + 'size' => 12, + 'color' => ['rgb' => 'FFFFFF'], + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => '0F766E'], // Teal - elegant + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]; + + // Data rows style + $dataStyle = [ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_TOP, + 'horizontal' => Alignment::HORIZONTAL_LEFT, + 'indent' => 1, // Add padding + ], + ]; + + // Currency columns style (format currency) + $currencyStyle = [ + 'numberFormat' => [ + 'formatCode' => '"Rp" #,##0', + ], + ]; + + return [ + // Header styling + 1 => $headerStyle, + + // Data rows styling + 'A2:'.$lastColumn.$lastRow => $dataStyle, + + // Currency columns styling + 'C2:C'.$lastRow => $currencyStyle, + ]; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + // Auto-size columns with max width + foreach (range('A', 'F') as $column) { + $event->sheet->getColumnDimension($column)->setAutoSize(true); + } + + // Set row height for header + $event->sheet->getRowDimension(1)->setRowHeight(25); + }, + ]; + } +} diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php new file mode 100644 index 0000000..fc788fb --- /dev/null +++ b/app/Http/Controllers/ExportController.php @@ -0,0 +1,69 @@ +format('Y-m-d_H-i-s').'.xlsx' + ); + } + + /** + * Export products to Excel + */ + public function products(): BinaryFileResponse + { + return Excel::download( + new ProductExport, + 'data_produk_botol_'.now()->format('Y-m-d_H-i-s').'.xlsx' + ); + } + + /** + * Export formulas to Excel + */ + public function formulas(): BinaryFileResponse + { + return Excel::download( + new FormulaExport, + 'data_rumus_'.now()->format('Y-m-d_H-i-s').'.xlsx' + ); + } + + /** + * Export categories to Excel + */ + public function categories(): BinaryFileResponse + { + return Excel::download( + new CategoryExport, + 'data_kategori_'.now()->format('Y-m-d_H-i-s').'.xlsx' + ); + } + + /** + * Export brands to Excel + */ + public function brands(): BinaryFileResponse + { + return Excel::download( + new BrandExport, + 'data_merek_'.now()->format('Y-m-d_H-i-s').'.xlsx' + ); + } +} diff --git a/composer.json b/composer.json index 7536c79..7f540bb 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "livewire/flux": "2.2.5", "livewire/flux-pro": "2.2.5", "livewire/livewire": "^3.6", + "maatwebsite/excel": "^3.1", "minishlink/web-push": "^9.0", "rappasoft/laravel-livewire-tables": "^3.7", "spatie/laravel-activitylog": "^4.10", diff --git a/composer.lock b/composer.lock index c8ea1f3..6e42867 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c3882fb2ea84252fd7d1709795007970", + "content-hash": "a6ef280e8ed158371bb27e1088a51102", "packages": [ { "name": "archtechx/enums", diff --git a/resources/views/livewire/studio/catalog/brands.blade.php b/resources/views/livewire/studio/catalog/brands.blade.php index 6d0994d..5edb87f 100644 --- a/resources/views/livewire/studio/catalog/brands.blade.php +++ b/resources/views/livewire/studio/catalog/brands.blade.php @@ -3,15 +3,22 @@
{{ $pageTitle }}
- @can('create brand') -
+
+ @can('view brand') + + + Ekspor Ecxel + + + @endcan + @can('create brand') Tambah -
- @endcan + @endcan +
diff --git a/resources/views/livewire/studio/catalog/categories.blade.php b/resources/views/livewire/studio/catalog/categories.blade.php index ba08220..6c05e58 100644 --- a/resources/views/livewire/studio/catalog/categories.blade.php +++ b/resources/views/livewire/studio/catalog/categories.blade.php @@ -3,15 +3,22 @@
{{ $pageTitle }}
- @can('create category') -
+
+ @can('view category') + + + Ekspor Ecxel + + + @endcan + @can('create category') Tambah -
- @endcan + @endcan +
@@ -32,8 +39,7 @@ Nama * - + diff --git a/resources/views/livewire/studio/catalog/perfume/index.blade.php b/resources/views/livewire/studio/catalog/perfume/index.blade.php index 8b0ffc2..85acea9 100644 --- a/resources/views/livewire/studio/catalog/perfume/index.blade.php +++ b/resources/views/livewire/studio/catalog/perfume/index.blade.php @@ -3,14 +3,21 @@
{{ $pageTitle }}
- @can('create perfume') -
+
+ @can('view perfume') + + + Ekspor Ecxel + + + @endcan + @can('create perfume') Tambah -
- @endcan + @endcan +
diff --git a/resources/views/livewire/studio/catalog/product/index.blade.php b/resources/views/livewire/studio/catalog/product/index.blade.php index 644a3b1..c6d7cc1 100644 --- a/resources/views/livewire/studio/catalog/product/index.blade.php +++ b/resources/views/livewire/studio/catalog/product/index.blade.php @@ -3,14 +3,21 @@
{{ $pageTitle }}
- @can('create product') -
+
+ @can('view product') + + + Ekspor Ecxel + + + @endcan + @can('create product') Tambah -
- @endcan + @endcan +
diff --git a/resources/views/livewire/studio/master/formulas.blade.php b/resources/views/livewire/studio/master/formulas.blade.php index e9c9242..7ed2618 100644 --- a/resources/views/livewire/studio/master/formulas.blade.php +++ b/resources/views/livewire/studio/master/formulas.blade.php @@ -3,15 +3,22 @@
{{ $pageTitle }}
- @can('create formula') -
+
+ @can('view formula') + + + Ekspor Ecxel + + + @endcan + @can('create formula') Tambah -
- @endcan + @endcan +
@@ -25,7 +32,7 @@
@foreach ($formula as $item) - +
{{ $item['quality'] }} ({{ $item['volume'] }} ml) @@ -80,8 +87,7 @@ Kualitas * - + @foreach ($qualities as $key => $value) {{ $value }} @@ -92,8 +98,7 @@ Ukuran * - + @foreach ($sizes as $key => $value) {{ $value }} ml @@ -105,8 +110,8 @@ Volume * - + ml diff --git a/routes/pages/studio.php b/routes/pages/studio.php index 6c2a77c..cdf1d9d 100644 --- a/routes/pages/studio.php +++ b/routes/pages/studio.php @@ -1,5 +1,6 @@ name('index')->middleware('can:view choose us'); }); }); + + // Export Routes + Route::prefix('export')->name('studio.export.')->group(function () { + Route::get('/perfumes', [ExportController::class, 'perfumes'])->name('perfumes')->middleware('can:view perfume'); + Route::get('/products', [ExportController::class, 'products'])->name('products')->middleware('can:view product'); + Route::get('/formulas', [ExportController::class, 'formulas'])->name('formulas')->middleware('can:view formula'); + Route::get('/categories', [ExportController::class, 'categories'])->name('categories')->middleware('can:view category'); + Route::get('/brands', [ExportController::class, 'brands'])->name('brands')->middleware('can:view brand'); + }); });