feat: Refactor FAQ, add category typing with enum, introduce article categories, and update various Livewire components and views.

This commit is contained in:
Yoga Pangestu 2025-12-21 14:10:33 +07:00
parent 37387fe124
commit b469fbff53
24 changed files with 227 additions and 193 deletions

View File

@ -0,0 +1,30 @@
<?php
namespace App\Enums;
use App\Traits\Enums\WithCommentEnum;
use App\Traits\Enums\WithValueEnum;
enum CategoryType: int
{
use WithCommentEnum, WithValueEnum;
case PERFUME = 1;
case ARTICLE = 2;
public function label(): string
{
return match ($this) {
self::PERFUME => 'Parfum',
self::ARTICLE => 'Artikel',
};
}
public function color(): string
{
return match ($this) {
self::PERFUME => 'emerald',
self::ARTICLE => 'blue',
};
}
}

View File

@ -1,135 +0,0 @@
<?php
namespace App\Exports;
use App\Models\Category;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithColumnWidths;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithTitle;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class CategoryExport implements FromCollection, ShouldAutoSize, WithColumnWidths, WithEvents, WithHeadings, WithMapping, WithStyles, WithTitle
{
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return Category::all();
}
public function title(): string
{
return 'Data Kategori';
}
public function headings(): array
{
return [
'Nama',
'Deskripsi',
'Tanggal Dibuat',
'Tanggal Diperbarui',
];
}
/**
* @param Category $category
*/
public function map($category): array
{
return [
$category->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);
},
];
}
}

View File

@ -3,7 +3,6 @@
namespace App\Http\Controllers;
use App\Exports\BrandExport;
use App\Exports\CategoryExport;
use App\Exports\FormulaExport;
use App\Exports\PerfumeExport;
use App\Exports\ProductExport;
@ -45,17 +44,6 @@ public function formulas(): BinaryFileResponse
);
}
/**
* 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
*/

View File

@ -11,6 +11,7 @@
use Illuminate\Support\Facades\Blade;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn;
class ArticlesTable extends DataTableComponent
{
@ -25,6 +26,11 @@ public function columns(): array
Column::make('Judul', 'title')->searchable(),
ArrayColumn::make('Kategori')
->data(fn ($value, $row) => $row->categories->pluck('name')->toArray())
->outputFormat(fn ($index, $value) => Blade::render('<span class="text-xs text-gray-400 border border-gray-400 rounded px-1">'.$value.'</span>'))
->flexRow(['class' => 'gap-2 flex-wrap']),
Column::make('Status', 'status')
->format(
fn ($value) => Blade::render('<flux:badge color="'.$value->color().'">'.$value->label().'</flux:badge>')
@ -66,6 +72,6 @@ public function columns(): array
public function builder(): Builder
{
return Article::select('articles.id', 'author_id', 'title', 'articles.status', 'published_at', 'views')->with(['author', 'author.employee']);
return Article::select('articles.id', 'author_id', 'title', 'articles.status', 'published_at', 'views')->with(['author', 'author.employee', 'categories']);
}
}

View File

@ -1,12 +1,13 @@
<?php
namespace App\Livewire\Datatable\Catalog;
namespace App\Livewire\Datatable\Master;
use App\Models\Category;
use App\Traits\Datatable\WithConfiguration;
use App\Traits\Datatable\WithPrependColumn;
use App\Traits\Media\WithMediaHandler;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Blade;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\ImageColumn;
@ -22,6 +23,10 @@ public function columns(): array
return [
Column::make('Nama', 'name')->searchable(),
Column::make('Jenis', 'type')
->label(fn ($row, $column) => Blade::render('<flux:badge color="'.$row->type->color().'">'.$row->type->label().'</flux:badge>'))
->html(),
ImageColumn::make('Gambar')
->location(fn ($row) => optional($row->getMedia('image')->first())->getUrl() ?? asset('assets/images/logo.png'))
->attributes(fn ($row) => [
@ -99,6 +104,6 @@ public function columns(): array
public function builder(): Builder
{
return Category::select('id', 'name', 'sort_order')->orderBy('sort_order');
return Category::select('id', 'name', 'type', 'sort_order')->orderBy('sort_order');
}
}

View File

@ -76,7 +76,10 @@ public function rules(): array
'quota' => ['nullable', new UnsignedInteger],
'limit_per_user' => ['nullable', new UnsignedInteger],
'summary' => ['required', 'string', 'max:200'],
'start_date' => ['required', 'date', 'after:yesterday'],
'start_date' => ['required', 'date', Rule::when(
$this->start_date !== $this->voucher->start_date,
'after_or_equal:today'
)],
'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
'tier_ids' => ['required', 'array', 'min:1'],
'tier_ids.*' => Rule::exists('tiers', 'id'),

View File

@ -23,6 +23,8 @@ class ArticleForm extends Form
public string $status = '1';
public array $category_ids = [];
public array $thumbnail = [];
public function rules(): array
@ -32,6 +34,8 @@ public function rules(): array
'excerpt' => ['required', 'string'],
'content' => ['required', 'string'],
'status' => ['required', Rule::in(ArticleStatus::cases())],
'category_ids' => ['required', 'array', 'min:1'],
'category_ids.*' => Rule::exists('categories', 'id'),
'thumbnail' => ['required', 'array'],
];
}
@ -42,6 +46,8 @@ public function validationAttributes(): array
'title' => 'judul',
'excerpt' => 'cuplikan',
'content' => 'konten',
'category_ids' => 'kategori',
'category_ids.*' => 'kategori',
];
}
@ -53,6 +59,7 @@ public function setArticle(Article $article): void
$this->excerpt = $article->excerpt;
$this->content = $article->content;
$this->status = $article->status->value;
$this->category_ids = $article->categories->pluck('id')->toArray();
$this->thumbnail = $this->mapMediaCollection($article->getMedia('thumbnail'));
}
@ -70,6 +77,8 @@ public function store(): string
'published_at' => $this->status == ArticleStatus::PUBLISHED->value ? now() : null,
]);
$article->categories()->sync($this->category_ids);
$this->uploadMedia($this->thumbnail, $article, 'thumbnail');
});
@ -93,6 +102,8 @@ public function update(): void
: []
));
$this->article->categories()->sync($this->category_ids);
$this->syncMedia($this->thumbnail, $this->article, 'thumbnail');
$this->uploadMedia($this->thumbnail, $this->article, 'thumbnail');
});

View File

@ -1,7 +1,8 @@
<?php
namespace App\Livewire\Forms\Studio\Catalog;
namespace App\Livewire\Forms\Studio\Master;
use App\Enums\CategoryType;
use App\Models\Category;
use App\Traits\Media\WithMediaHandler;
use App\Traits\Utilities\WithSortOrder;
@ -19,14 +20,17 @@ class CategoryForm extends Form
public ?string $description = null;
public int $type = CategoryType::PERFUME->value;
public array $image = [];
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:20', Rule::unique('categories', 'name')->whereNull('deleted_at')->ignore($this->category)],
'description' => 'nullable',
'image' => 'array',
'description' => $this->type === CategoryType::PERFUME->value ? 'nullable' : 'prohibited',
'type' => ['required', Rule::in(CategoryType::values())],
'image' => $this->type === CategoryType::PERFUME->value ? 'array' : 'prohibited',
];
}
@ -35,6 +39,7 @@ public function validationAttributes(): array
return [
'name' => 'nama',
'description' => 'deskripsi',
'type' => 'tipe',
'image' => 'gambar',
];
}
@ -45,6 +50,7 @@ public function setCategory(Category $category): void
$this->name = $category->name;
$this->description = $category->description;
$this->type = $category->type->value;
$this->image = $this->mapMediaCollection($category->getMedia('image'));
}
@ -59,11 +65,14 @@ public function store(): void
$category = Category::create([
'name' => $this->name,
'description' => $this->description,
'description' => $this->type === CategoryType::PERFUME->value ? $this->description : null,
'type' => $this->type,
'sort_order' => 1,
]);
$this->uploadMedia($this->image, $category, 'image');
if ($this->type === CategoryType::PERFUME->value) {
$this->uploadMedia($this->image, $category, 'image');
}
});
}
@ -74,11 +83,16 @@ public function update(): void
DB::transaction(function () {
$this->category->update([
'name' => $this->name,
'description' => $this->description,
'description' => $this->type === CategoryType::PERFUME->value ? $this->description : null,
'type' => $this->type,
]);
$this->syncMedia($this->image, $this->category, 'image');
$this->uploadMedia($this->image, $this->category, 'image');
if ($this->type === CategoryType::PERFUME->value) {
$this->syncMedia($this->image, $this->category, 'image');
$this->uploadMedia($this->image, $this->category, 'image');
} else {
$this->category->clearMediaCollection('image');
}
});
}

View File

@ -36,7 +36,7 @@ public function mount(): void
$this->brands = Brand::pluck('name', 'id')->toArray();
$this->categories = Category::pluck('name', 'id')->toArray();
$this->categories = Category::perfume()->pluck('name', 'id')->toArray();
}
public function save(): void

View File

@ -2,10 +2,13 @@
namespace App\Livewire\Studio\Manage\Article;
use App\Enums\CategoryType;
use App\Jobs\StorePushNotification;
use App\Livewire\Forms\Studio\Manage\ArticleForm;
use App\Models\Category;
use App\Models\PushNotification;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithCategorySelector;
use App\Traits\Components\WithToast;
use App\Traits\Utilities\WithUpdatedData;
use Illuminate\Contracts\View\View;
@ -16,10 +19,17 @@
#[Title('Tambah Artikel')]
class Create extends Component
{
use WithAuthorization, WithToast, WithUpdatedData;
use WithAuthorization, WithCategorySelector, WithToast, WithUpdatedData;
public ArticleForm $form;
public array $categories = [];
public function mount(): void
{
$this->categories = Category::article()->pluck('name', 'id')->toArray();
}
public function save(): void
{
$this->canOrAbort('create article');
@ -43,6 +53,7 @@ public function render(): View
{
return view('livewire.studio.manage.article.form', [
'pageTitle' => 'Tambah Artikel',
'categories' => Category::query()->where('type', CategoryType::ARTICLE)->orderBy('sort_order')->get(),
]);
}
}

View File

@ -2,9 +2,12 @@
namespace App\Livewire\Studio\Manage\Article;
use App\Enums\CategoryType;
use App\Livewire\Forms\Studio\Manage\ArticleForm;
use App\Models\Article;
use App\Models\Category;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithCategorySelector;
use App\Traits\Components\WithToast;
use App\Traits\Utilities\WithUpdatedData;
use Illuminate\Contracts\View\View;
@ -14,13 +17,17 @@
#[Title('Ubah Artikel')]
class Edit extends Component
{
use WithAuthorization, WithToast, WithUpdatedData;
use WithAuthorization, WithCategorySelector, WithToast, WithUpdatedData;
public ArticleForm $form;
public array $categories = [];
public function mount(Article $article): void
{
$this->form->setArticle($article);
$this->categories = Category::article()->pluck('name', 'id')->toArray();
}
public function save(): void
@ -38,6 +45,7 @@ public function render(): View
{
return view('livewire.studio.manage.article.form', [
'pageTitle' => 'Ubah Artikel',
'categories' => Category::query()->where('type', CategoryType::ARTICLE)->orderBy('sort_order')->get(),
]);
}
}

View File

@ -1,8 +1,8 @@
<?php
namespace App\Livewire\Studio\Catalog;
namespace App\Livewire\Studio\Master;
use App\Livewire\Forms\Studio\Catalog\CategoryForm;
use App\Livewire\Forms\Studio\Master\CategoryForm;
use App\Models\Category as CategoryModel;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithCloseModal;
@ -94,7 +94,7 @@ public function openModal(string $method, string $modalTitle, ?string $id = null
public function render(): View
{
return view('livewire.studio.catalog.categories', [
return view('livewire.studio.master.categories', [
'pageTitle' => 'Kategori',
]);
}

View File

@ -6,6 +6,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
@ -39,4 +40,9 @@ public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'author_id');
}
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ArticleCategory extends Model
{
protected $table = 'article_category';
protected $guarded = ['id'];
}

View File

@ -2,6 +2,9 @@
namespace App\Models;
use App\Enums\CategoryType;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -22,9 +25,22 @@ protected function casts(): array
{
return [
'sort_order' => 'int',
'type' => CategoryType::class,
];
}
#[Scope]
protected function perfume(Builder $query): Builder
{
return $query->where('type', CategoryType::PERFUME);
}
#[Scope]
protected function article(Builder $query): Builder
{
return $query->where('type', CategoryType::ARTICLE);
}
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
@ -36,4 +52,9 @@ public function perfumes(): BelongsToMany
{
return $this->belongsToMany(Perfume::class);
}
public function articles(): BelongsToMany
{
return $this->belongsToMany(Article::class);
}
}

View File

@ -71,6 +71,13 @@ public function boot(): void
'match' => 'studio.master.formula.*',
'can' => 'view formula',
],
[
'label' => 'Kategori',
'icon' => 'list-bullet',
'route' => 'studio.master.category.index',
'match' => 'studio.master.category.*',
'can' => 'view category',
],
],
],
[
@ -116,13 +123,6 @@ public function boot(): void
[
'heading' => 'Katalog',
'items' => [
[
'label' => 'Kategori',
'icon' => 'list-bullet',
'route' => 'studio.catalog.category.index',
'match' => 'studio.catalog.category.*',
'can' => 'view category',
],
[
'label' => 'Merek',
'icon' => 'tag',

View File

@ -1,5 +1,6 @@
<?php
use App\Enums\CategoryType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
@ -16,6 +17,7 @@ public function up(): void
$table->string('name', 20);
$table->string('slug', 30);
$table->text('description')->nullable();
$table->integer('type')->default(CategoryType::PERFUME->value)->comment(CategoryType::comment());
$table->unsignedInteger('sort_order');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('article_category', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('article_category');
}
};

View File

@ -5,7 +5,7 @@
</div>
<div class="flex gap-2">
@can('view perfume')
<a href="{{ route('studio.export.perfumes') }}" class="inline-flex">
<a href="{{ route('studio.export.perfume') }}" class="inline-flex">
<flux:button variant="primary" color="zinc" class="text-sm">
Ekspor Ecxel
</flux:button>

View File

@ -5,7 +5,7 @@
</div>
<div class="flex gap-2">
@can('view product')
<a href="{{ route('studio.export.products') }}" class="inline-flex">
<a href="{{ route('studio.export.product') }}" class="inline-flex">
<flux:button variant="primary" color="zinc" class="text-sm">
Ekspor Ecxel
</flux:button>

View File

@ -53,6 +53,24 @@
</flux:field>
</flux:card>
<flux:card class="space-y-6 p-6">
<flux:field>
<flux:label>Kategori <span class="text-red-500 ms-1">*</span></flux:label>
<flux:select variant="listbox" multiple searchable placeholder="Pilih kategori"
wire:model="form.category_ids">
<flux:select.option wire:click="selectAllCategories" wire:ignore>Pilih Semua
</flux:select.option>
<flux:select.option wire:click="deselectAllCategories" wire:ignore>Hapus Semua
</flux:select.option>
@foreach ($categories as $key => $value)
<flux:select.option value="{{ $key }}">{{ $value }}
</flux:select.option>
@endforeach
</flux:select>
<flux:error name="form.category_ids" />
</flux:field>
</flux:card>
<flux:card class="space-y-6 p-6">
<div class="space-y-3">
<h3 class="text-sm font-medium">Thumbnail <span class="text-red-500 ms-1">*</span></h3>

View File

@ -4,13 +4,6 @@
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
</div>
<div class="flex gap-2">
@can('view category')
<a href="{{ route('studio.export.categories') }}" class="inline-flex">
<flux:button variant="primary" color="zinc" class="text-sm">
Ekspor Ecxel
</flux:button>
</a>
@endcan
@can('create category')
<flux:modal.trigger name="form-modal">
<flux:button variant="primary" class="text-sm"
@ -22,7 +15,7 @@
</div>
<div class="mt-6">
<livewire:datatable.catalog.categories-table />
<livewire:datatable.master.categories-table />
</div>
@include('components.modals.confirmation', [
@ -47,6 +40,18 @@
placeholder="Aromaterapi merupakan wewangian yang dapat membantu meredakan stress dan meredakan perasaan sakit kepala."
auotocomplete="off" class="**:data-[slot=content]:min-h-[100px]!" />
<flux:field>
<flux:label>Jenis <span class="text-red-500 ms-1">*</span></flux:label>
<flux:radio.group wire:model="form.type" variant="buttons" class="w-full *:flex-1">
@foreach (\App\Enums\CategoryType::cases() as $item)
<flux:radio value="{{ $item->value }}">
{{ $item->label() }}
</flux:radio>
@endforeach
</flux:radio.group>
<flux:error name="form.type" />
</flux:field>
<div class="space-y-3">
<h3 class="text-sm font-medium">Gambar</h3>
<div class="dropzone-wrapper">

View File

@ -5,7 +5,7 @@
</div>
<div class="flex gap-2">
@can('view formula')
<a href="{{ route('studio.export.formulas') }}" class="inline-flex">
<a href="{{ route('studio.export.formula') }}" class="inline-flex">
<flux:button variant="primary" color="zinc" class="text-sm">
Ekspor Ecxel
</flux:button>

View File

@ -6,7 +6,6 @@
use App\Livewire\Studio\Catalog\Bottle\Edit as BottleEdit;
use App\Livewire\Studio\Catalog\Bottle\Index as BottleIndex;
use App\Livewire\Studio\Catalog\Brand as BrandComponent;
use App\Livewire\Studio\Catalog\Category as CategoryComponent;
use App\Livewire\Studio\Catalog\Perfume\Audit as PerfumeAudit;
use App\Livewire\Studio\Catalog\Perfume\Create as PerfumeCreate;
use App\Livewire\Studio\Catalog\Perfume\Edit as PerfumeEdit;
@ -43,6 +42,7 @@
use App\Livewire\Studio\Manage\StockOpname\Index as StockOpnameIndex;
use App\Livewire\Studio\Manage\StockOpname\Manage as StockOpnameManage;
use App\Livewire\Studio\Manage\StockOpname\Show as StockOpnameShow;
use App\Livewire\Studio\Master\Category as CategoryComponent;
use App\Livewire\Studio\Master\Formula as FormulaComponent;
use App\Livewire\Studio\Master\Outlet\Create as OutletCreate;
use App\Livewire\Studio\Master\Outlet\Edit as OutletEdit;
@ -81,6 +81,10 @@
Route::prefix('formulas')->name('formula.')->group(function () {
Route::get('/', FormulaComponent::class)->name('index')->middleware('can:view formula');
});
Route::prefix('categories')->name('category.')->group(function () {
Route::get('/', CategoryComponent::class)->name('index')->middleware('can:view category');
});
});
Route::prefix('loyalty')->name('studio.loyalty.')->group(function () {
@ -106,10 +110,6 @@
});
Route::prefix('catalog')->name('studio.catalog.')->group(function () {
Route::prefix('categories')->name('category.')->group(function () {
Route::get('/', CategoryComponent::class)->name('index')->middleware('can:view category');
});
Route::prefix('brands')->name('brand.')->group(function () {
Route::get('/', BrandComponent::class)->name('index')->middleware('can:view brand');
});
@ -203,10 +203,9 @@
// 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');
Route::get('/perfumes', [ExportController::class, 'perfumes'])->name('perfume')->middleware('can:view perfume');
Route::get('/products', [ExportController::class, 'products'])->name('product')->middleware('can:view product');
Route::get('/formulas', [ExportController::class, 'formulas'])->name('formula')->middleware('can:view formula');
Route::get('/brands', [ExportController::class, 'brands'])->name('brand')->middleware('can:view brand');
});
});