feat: Refactor FAQ, add category typing with enum, introduce article categories, and update various Livewire components and views.
This commit is contained in:
parent
37387fe124
commit
b469fbff53
30
app/Enums/CategoryType.php
Normal file
30
app/Enums/CategoryType.php
Normal 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',
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
@ -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'),
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
]);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
12
app/Models/ArticleCategory.php
Normal file
12
app/Models/ArticleCategory.php
Normal 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'];
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
@ -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>
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user