diff --git a/BEST_PRACTICE.md b/BEST_PRACTICE.md index 5453cfe..a367ff1 100644 --- a/BEST_PRACTICE.md +++ b/BEST_PRACTICE.md @@ -416,6 +416,39 @@ ### Modifikasi Tabel (Alter Schema) ## JavaScript / Vue +### Komponen Reusable vs Page-Specific + +- `resources/js/components/` → komponen **reusable** lintas page (UI primitives, catalog POS, printer, display formatter, dll.). +- `resources/js/pages/{module}/form/` dan `table/` → komponen **khusus page** (modal form, columns, actions). +- Jika komponen dipakai **lebih dari satu modul/page**, pindahkan ke `resources/js/components/{domain}/`. + - Contoh: `PosCatalogCard`, `PosCatalogVariantThumb` → `components/catalog/` + - Contoh: `OrderPrintButton`, `ThermalPrinterConnectButton` → `components/order/` + +### Format Data dan Attribute Model + +- Untuk **tampilan read-only**, gunakan field `*_formatted` dari API/model accessor. +- **Jangan** format manual di view jika accessor model sudah tersedia (misal: `stock_formatted`, `price_formatted`). +- Format **live/calc** di form (cart total, input rupiah) boleh pakai `formatRupiah()` / `RupiahInput`. +- Komponen display: `RupiahText` (`amount` + optional `formatted` prop). + +### Types + +- Type domain di `resources/js/types/{domain}.ts`. +- Type shared di `resources/js/types/common.ts`: `Paginated`, `SelectOption`, `EnumOption`. +- Hindari duplikasi type di page; import dari `@/types/*`. + +### Form Request dan View Label + +- Label validasi harus **sama persis** dengan `` di view. +- Gunakan `attributes()` pada Form Request (locale `id` → `:Attribute wajib diisi.`). +- Contoh: view `Nama Produk` → `'name' => 'Nama Produk'`. +- Pesan custom untuk rule nested/business logic gunakan `messages()`. + +### Pemisahan File + +- Pecah file besar berdasarkan **fungsi**: catalog panel, cart panel, metadata form, composable logic. +- Target ideal: **< 300 baris** per komponen. + ### Struktur Folder Page-Specific Components Komponen yang hanya digunakan oleh satu page tertentu (misalnya modal form, kolom tabel, action button) **harus ditempatkan di folder page-nya langsung** di dalam subfolder sesuai fungsinya, bukan di `resources/js/components/`. diff --git a/app/Http/Controllers/Admin/Master/ProductController.php b/app/Http/Controllers/Admin/Master/ProductController.php index f9eeb80..f3515d3 100644 --- a/app/Http/Controllers/Admin/Master/ProductController.php +++ b/app/Http/Controllers/Admin/Master/ProductController.php @@ -9,6 +9,7 @@ use App\Http\Requests\Admin\ToggleStatusRequest; use App\Models\Product; use App\Services\Master\CategoryService; +use App\Services\Master\MasterInventoryService; use App\Services\Master\ProductService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -22,6 +23,7 @@ class ProductController extends Controller public function __construct( private readonly ProductService $productService, private readonly CategoryService $categoryService, + private readonly MasterInventoryService $masterInventoryService, ) {} public function index(Request $request): Response @@ -34,6 +36,7 @@ public function index(Request $request): Response return Inertia::render('admin/master/products/Index', [ 'products' => $this->productService->paginateForIndex($tableQuery, $isActive, $categoryId, $stockStatus), 'categories' => $this->categoryService->getSelectOptions(), + 'outOfStockGroups' => $this->masterInventoryService->outOfStockProductGroups(), 'filters' => $this->dataTableFilters($tableQuery, [ 'is_active' => $isActive, 'category_id' => $categoryId, diff --git a/app/Http/Controllers/Admin/Master/RawMaterialController.php b/app/Http/Controllers/Admin/Master/RawMaterialController.php index b4121f3..b26f3fd 100644 --- a/app/Http/Controllers/Admin/Master/RawMaterialController.php +++ b/app/Http/Controllers/Admin/Master/RawMaterialController.php @@ -9,6 +9,7 @@ use App\Http\Requests\Admin\Master\RawMaterialRequest; use App\Http\Requests\Admin\ToggleStatusRequest; use App\Models\RawMaterial; +use App\Services\Master\MasterInventoryService; use App\Services\Master\RawMaterialService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -21,6 +22,7 @@ class RawMaterialController extends Controller public function __construct( private readonly RawMaterialService $rawMaterialService, + private readonly MasterInventoryService $masterInventoryService, ) {} public function index(Request $request): Response @@ -31,6 +33,8 @@ public function index(Request $request): Response return Inertia::render('admin/master/raw-materials/Index', [ 'rawMaterials' => $this->rawMaterialService->paginateForIndex($tableQuery, $isActive, $stockStatus), + 'outOfStockGroups' => $this->masterInventoryService->outOfStockRawMaterialGroups(), + 'lowStockGroups' => $this->masterInventoryService->lowStockRawMaterialGroups(), 'filters' => $this->dataTableFilters($tableQuery, [ 'is_active' => $isActive, 'stock_status' => $stockStatus, diff --git a/app/Http/Requests/Admin/Account/UpdateProfileRequest.php b/app/Http/Requests/Admin/Account/UpdateProfileRequest.php index 70f69e4..0f0b7e9 100644 --- a/app/Http/Requests/Admin/Account/UpdateProfileRequest.php +++ b/app/Http/Requests/Admin/Account/UpdateProfileRequest.php @@ -39,14 +39,14 @@ public function rules(): array public function attributes(): array { return [ - 'email' => 'email', - 'username' => 'username', - 'full_name' => 'nama lengkap', - 'phone_number' => 'nomor telepon', - 'gender' => 'jenis kelamin', - 'birth_date' => 'tanggal lahir', - 'address' => 'alamat', - 'profile_photo' => 'foto profil', + 'email' => 'Email', + 'username' => 'Username', + 'full_name' => 'Nama Lengkap', + 'phone_number' => 'Nomor Telepon', + 'gender' => 'Jenis Kelamin', + 'birth_date' => 'Tanggal Lahir', + 'address' => 'Alamat', + 'profile_photo' => 'Foto Profil', ]; } } diff --git a/app/Http/Requests/Admin/Hr/EmployeeRequest.php b/app/Http/Requests/Admin/Hr/EmployeeRequest.php index 2e656a4..e8d7e0d 100644 --- a/app/Http/Requests/Admin/Hr/EmployeeRequest.php +++ b/app/Http/Requests/Admin/Hr/EmployeeRequest.php @@ -57,18 +57,18 @@ public function rules(): array public function attributes(): array { return [ - 'email' => 'email', - 'username' => 'username', - 'full_name' => 'nama lengkap', - 'phone_number' => 'nomor telepon', - 'gender' => 'jenis kelamin', - 'birth_date' => 'tanggal lahir', - 'address' => 'alamat', - 'profile_photo' => 'foto profil', - 'role' => 'role', - 'join_date' => 'tanggal bergabung', - 'employment_status' => 'status kepegawaian', - 'base_salary' => 'gaji pokok', + 'email' => 'Email', + 'username' => 'Username', + 'full_name' => 'Nama Lengkap', + 'phone_number' => 'Nomor Telepon', + 'gender' => 'Jenis Kelamin', + 'birth_date' => 'Tanggal Lahir', + 'address' => 'Alamat', + 'profile_photo' => 'Foto Profil', + 'role' => 'Role', + 'join_date' => 'Tanggal Bergabung', + 'employment_status' => 'Status Kepegawaian', + 'base_salary' => 'Gaji Pokok', ]; } } diff --git a/app/Http/Requests/Admin/Manage/CuttingRequest.php b/app/Http/Requests/Admin/Manage/CuttingRequest.php index 55d07da..b710323 100644 --- a/app/Http/Requests/Admin/Manage/CuttingRequest.php +++ b/app/Http/Requests/Admin/Manage/CuttingRequest.php @@ -62,17 +62,17 @@ public function rules(): array public function attributes(): array { return [ - 'description' => 'keterangan', - 'materials' => 'bahan baku', - 'materials.*.raw_material_price_id' => 'bahan baku', - 'materials.*.material_usage' => 'pemakaian', - 'results' => 'hasil produk', - 'results.*.product_variant_id' => 'varian produk', - 'results.*.cutting_result' => 'hasil', - 'results.*.sampel' => 'sampel', - 'results.*.hasil_cutting_diluar_sampel' => 'hasil cutting diluar sampel', - 'sewing_cost' => 'jasa jahit', - 'other_cost' => 'biaya lainnya', + 'description' => 'Keterangan', + 'materials' => 'Bahan Baku', + 'materials.*.raw_material_price_id' => 'Bahan Baku', + 'materials.*.material_usage' => 'Pemakaian', + 'results' => 'Hasil Produk', + 'results.*.product_variant_id' => 'Varian Produk', + 'results.*.cutting_result' => 'Hasil', + 'results.*.sampel' => 'Sampel', + 'results.*.hasil_cutting_diluar_sampel' => 'Diluar Sampel', + 'sewing_cost' => 'Jasa Jahit', + 'other_cost' => 'Biaya Lainnya', ]; } } diff --git a/app/Http/Requests/Admin/Manage/OrderRequest.php b/app/Http/Requests/Admin/Manage/OrderRequest.php index 5779a42..1fc0e0d 100644 --- a/app/Http/Requests/Admin/Manage/OrderRequest.php +++ b/app/Http/Requests/Admin/Manage/OrderRequest.php @@ -68,23 +68,23 @@ public function rules(): array public function attributes(): array { return [ - 'customer_id' => 'pelanggan', - 'marketing_id' => 'marketing', - 'channel' => 'channel', - 'price_type' => 'tipe harga', - 'payment_type' => 'tipe pembayaran', - 'is_affiliate' => 'afiliasi', - 'tiktok_order_id' => 'ID pesanan TikTok Shop', - 'shopee_order_id' => 'ID pesanan Shopee', - 'discount' => 'diskon', - 'nego_price' => 'harga nego', - 'notes' => 'keterangan', - 'status' => 'status pesanan', - 'items' => 'produk', - 'items.*.product_variant_id' => 'varian produk', - 'items.*.quantity' => 'jumlah', - 'items.*.stock_quality' => 'kualitas stok', - ...$this->photoUploadAttributes('bukti transaksi'), + 'customer_id' => 'Pelanggan', + 'marketing_id' => 'Marketing', + 'channel' => 'Channel', + 'price_type' => 'Tipe Harga', + 'payment_type' => 'Tipe Pembayaran', + 'is_affiliate' => 'Pesanan Afiliasi', + 'tiktok_order_id' => 'ID Pesanan TikTok Shop', + 'shopee_order_id' => 'ID Pesanan Shopee', + 'discount' => 'Diskon', + 'nego_price' => 'Harga Nego', + 'notes' => 'Keterangan', + 'status' => 'Status Pesanan', + 'items' => 'Produk', + 'items.*.product_variant_id' => 'Varian Produk', + 'items.*.quantity' => 'Jumlah', + 'items.*.stock_quality' => 'Kualitas Stok', + ...$this->photoUploadAttributes('Bukti Transaksi'), ]; } diff --git a/app/Http/Requests/Admin/Manage/PurchaseRequest.php b/app/Http/Requests/Admin/Manage/PurchaseRequest.php index 03f22a1..e909fc8 100644 --- a/app/Http/Requests/Admin/Manage/PurchaseRequest.php +++ b/app/Http/Requests/Admin/Manage/PurchaseRequest.php @@ -52,14 +52,14 @@ public function rules(): array public function attributes(): array { return [ - 'supplier_id' => 'supplier', - 'discount' => 'diskon', - 'shipping_cost' => 'ongkir', - 'notes' => 'keterangan', - 'items' => 'bahan baku', - 'items.*.raw_material_price_id' => 'bahan baku', - 'items.*.quantity' => 'jumlah', - ...$this->photoUploadAttributes('bukti transaksi'), + 'supplier_id' => 'Supplier', + 'discount' => 'Diskon', + 'shipping_cost' => 'Ongkir', + 'notes' => 'Keterangan', + 'items' => 'Bahan Baku', + 'items.*.raw_material_price_id' => 'Bahan Baku', + 'items.*.quantity' => 'Jumlah', + ...$this->photoUploadAttributes('Bukti Transaksi'), ]; } } diff --git a/app/Http/Requests/Admin/Manage/StokOpnameRequest.php b/app/Http/Requests/Admin/Manage/StokOpnameRequest.php index b15617e..01b6cfa 100644 --- a/app/Http/Requests/Admin/Manage/StokOpnameRequest.php +++ b/app/Http/Requests/Admin/Manage/StokOpnameRequest.php @@ -40,4 +40,16 @@ public function messages(): array 'items.*.physical_stock.min' => 'Stok fisik tidak boleh negatif.', ]; } + + public function attributes(): array + { + return [ + 'opname_date' => 'Tanggal Opname', + 'notes' => 'Catatan', + 'items' => 'Item Stok Opname', + 'items.*.product_variant_id' => 'Varian Produk', + 'items.*.physical_stock' => 'Stok Fisik', + 'items.*.notes' => 'Catatan', + ]; + } } diff --git a/app/Http/Requests/Admin/Master/CategoryRequest.php b/app/Http/Requests/Admin/Master/CategoryRequest.php index 8f76cd9..c41f512 100644 --- a/app/Http/Requests/Admin/Master/CategoryRequest.php +++ b/app/Http/Requests/Admin/Master/CategoryRequest.php @@ -32,7 +32,7 @@ public function rules(): array public function attributes(): array { return [ - 'name' => 'nama', + 'name' => 'Nama', ]; } } diff --git a/app/Http/Requests/Admin/Master/CustomerRequest.php b/app/Http/Requests/Admin/Master/CustomerRequest.php index cf42763..504d5b3 100644 --- a/app/Http/Requests/Admin/Master/CustomerRequest.php +++ b/app/Http/Requests/Admin/Master/CustomerRequest.php @@ -35,9 +35,9 @@ public function rules(): array public function attributes(): array { return [ - 'name' => 'nama', - 'phone_number' => 'nomor telepon', - 'address' => 'alamat', + 'name' => 'Nama', + 'phone_number' => 'Nomor Telepon', + 'address' => 'Alamat', ]; } } diff --git a/app/Http/Requests/Admin/Master/ProductRequest.php b/app/Http/Requests/Admin/Master/ProductRequest.php index 8840e49..c9baf20 100644 --- a/app/Http/Requests/Admin/Master/ProductRequest.php +++ b/app/Http/Requests/Admin/Master/ProductRequest.php @@ -53,15 +53,15 @@ public function rules(): array public function attributes(): array { return [ - 'name' => 'nama produk', - 'description' => 'deskripsi', - 'category_ids' => 'kategori', - 'category_ids.*' => 'kategori', - 'variants' => 'varian', - 'variants.*.name' => 'nama varian', - 'variants.*.stock' => 'stok', - 'variants.*.stock_retail' => 'stok ecer', - ...$this->variantImageAttributes('variants', 'foto varian'), + 'name' => 'Nama Produk', + 'description' => 'Deskripsi', + 'category_ids' => 'Kategori', + 'category_ids.*' => 'Kategori', + 'variants' => 'Varian', + 'variants.*.name' => 'Nama Varian', + 'variants.*.stock' => 'Stok', + 'variants.*.stock_retail' => 'Stok Ecer', + ...$this->variantImageAttributes('variants', 'Foto Varian'), ]; } } diff --git a/app/Http/Requests/Admin/Master/RawMaterialRequest.php b/app/Http/Requests/Admin/Master/RawMaterialRequest.php index bb32df8..c44b98b 100644 --- a/app/Http/Requests/Admin/Master/RawMaterialRequest.php +++ b/app/Http/Requests/Admin/Master/RawMaterialRequest.php @@ -51,13 +51,13 @@ public function rules(): array public function attributes(): array { return [ - 'name' => 'nama bahan baku', - 'unit' => 'satuan', - 'prices' => 'varian', - 'prices.*.variant' => 'nama varian', - 'prices.*.price' => 'harga', - 'prices.*.stock' => 'stok', - ...$this->variantImageAttributes('prices', 'foto varian'), + 'name' => 'Nama Bahan Baku', + 'unit' => 'Satuan', + 'prices' => 'Varian', + 'prices.*.variant' => 'Nama Varian', + 'prices.*.price' => 'Harga', + 'prices.*.stock' => 'Stok', + ...$this->variantImageAttributes('prices', 'Foto Varian'), ]; } } diff --git a/app/Http/Requests/Admin/Master/SupplierRequest.php b/app/Http/Requests/Admin/Master/SupplierRequest.php index 9be85ef..6525be1 100644 --- a/app/Http/Requests/Admin/Master/SupplierRequest.php +++ b/app/Http/Requests/Admin/Master/SupplierRequest.php @@ -35,9 +35,9 @@ public function rules(): array public function attributes(): array { return [ - 'name' => 'nama', - 'phone_number' => 'nomor telepon', - 'address' => 'alamat', + 'name' => 'Nama', + 'phone_number' => 'Nomor Telepon', + 'address' => 'Alamat', ]; } } diff --git a/app/Models/Product.php b/app/Models/Product.php index ade2419..d2dc89c 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -5,9 +5,11 @@ use App\Enums\OwnerVerificationStatus; use App\Models\Concerns\HasPendingOwnerVerification; use App\Models\Concerns\InteractsWithActivityLog; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -23,6 +25,7 @@ * @method static \Illuminate\Database\Eloquent\Builder inactive() */ #[Guarded(['id'])] +#[Appends(['total_stock_formatted', 'total_reject_stock_formatted', 'total_retail_stock_formatted'])] #[Sluggable(from: 'name', to: 'slug')] class Product extends Model { @@ -51,7 +54,29 @@ protected function inactive(Builder $query): void $query->where('is_active', false); } - // 4. Other Methods + // 4. Attribute + public function totalRejectStockFormatted(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->variants->sum('reject_stock'), 0, ',', '.'), + ); + } + + public function totalRetailStockFormatted(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->variants->sum('stock_retail'), 0, ',', '.'), + ); + } + + public function totalStockFormatted(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->variants->sum('stock'), 0, ',', '.'), + ); + } + + // 5. Other Methods public static function getActiveWithVariantsAndCategories(): Collection { return self::query() @@ -65,7 +90,7 @@ public static function getActiveWithVariantsAndCategories(): Collection ->get(); } - // 5. Relation + // 6. Relation public function categories(): BelongsToMany { return $this->belongsToMany(Category::class, 'product_categories'); diff --git a/app/Models/ProductVariant.php b/app/Models/ProductVariant.php index 32b8007..ca5b9d1 100644 --- a/app/Models/ProductVariant.php +++ b/app/Models/ProductVariant.php @@ -4,7 +4,9 @@ use App\Models\Concerns\HasModuleMedia; use App\Models\Concerns\InteractsWithActivityLog; +use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Guarded; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -13,6 +15,7 @@ use Spatie\MediaLibrary\HasMedia; #[Guarded(['id'])] +#[Appends(['stock_formatted', 'reject_stock_formatted', 'stock_retail_formatted'])] class ProductVariant extends Model implements HasMedia { // 1. Use Trait @@ -30,7 +33,29 @@ protected function casts(): array ]; } - // 3. Other Methods + // 4. Attribute + public function rejectStockFormatted(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->reject_stock, 0, ',', '.'), + ); + } + + public function stockFormatted(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->stock, 0, ',', '.'), + ); + } + + public function stockRetailFormatted(): Attribute + { + return Attribute::make( + get: fn () => number_format($this->stock_retail, 0, ',', '.'), + ); + } + + // 5. Other Methods public static function mediaModuleName(): string { return 'product'; @@ -46,7 +71,7 @@ public function registerMediaCollections(): void $this->addMediaCollection('images'); } - // 4. Relation + // 6. Relation public function cuttingResultPrices(): HasMany { return $this->hasMany(CuttingResultPrice::class); diff --git a/app/Models/RawMaterial.php b/app/Models/RawMaterial.php index 74cfafd..bab7b12 100644 --- a/app/Models/RawMaterial.php +++ b/app/Models/RawMaterial.php @@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; #[Guarded(['id'])] -#[Appends(['unit_abbreviation', 'unit_label'])] +#[Appends(['unit_abbreviation', 'unit_label', 'total_stock_formatted', 'total_inventory_value_formatted'])] class RawMaterial extends Model { // 1. Use Trait @@ -63,6 +63,30 @@ public function unitLabel(): Attribute ); } + public function totalInventoryValueFormatted(): Attribute + { + return Attribute::make( + get: fn () => 'Rp '.number_format( + $this->prices->sum(fn (RawMaterialPrice $price) => (float) $price->stock * (int) $price->price), + 0, + ',', + '.', + ), + ); + } + + public function totalStockFormatted(): Attribute + { + return Attribute::make( + get: function () { + $total = $this->prices->sum(fn (RawMaterialPrice $price) => (float) $price->stock); + $formatted = rtrim(rtrim(number_format($total, 4, ',', '.'), '0'), ','); + + return "{$formatted} {$this->unit->abbreviation()}"; + }, + ); + } + // 5. Relation public function ownerVerificationRequests(): MorphMany { diff --git a/app/Services/Manage/CuttingService.php b/app/Services/Manage/CuttingService.php index 4b42c7c..9e64999 100644 --- a/app/Services/Manage/CuttingService.php +++ b/app/Services/Manage/CuttingService.php @@ -890,10 +890,30 @@ private function formatQuantityInput(float $value): string return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.'); } + private function formatMaterialUsageSummary(Cutting $cutting): string + { + $groups = []; + + foreach ($cutting->materials as $material) { + $unit = $material->rawMaterialPrice?->rawMaterial?->unit?->abbreviation() ?? ''; + $groups[$unit] = ($groups[$unit] ?? 0) + (float) $material->material_usage; + } + + return collect($groups) + ->filter(fn (float $total) => $total > 0) + ->map(function (float $total, string $unit) { + $formatted = rtrim(rtrim(number_format($total, 2, ',', '.'), '0'), ','); + + return "{$formatted} {$unit}"; + }) + ->join(', '); + } + private function appendCostPreview(Cutting $cutting): void { $cutting->setAttribute('total_result_pieces', (int) $cutting->results->sum('cutting_result')); $cutting->setAttribute('total_material_usage', (float) $cutting->materials->sum(fn (CuttingMaterial $material) => (float) $material->material_usage)); + $cutting->setAttribute('total_material_usage_summary_formatted', $this->formatMaterialUsageSummary($cutting)); $totalMaterialCost = $cutting->total_material_cost ?? $this->calculateTotalMaterialCost($cutting); $sewingCost = (int) ($cutting->sewing_cost ?? 0); diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php index 38af14f..a221e8c 100644 --- a/app/Services/Manage/OrderService.php +++ b/app/Services/Manage/OrderService.php @@ -96,6 +96,10 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi $order->setAttribute('available_actions', $actions); $order->setAttribute('is_editable', $order->status->isEditable()); + $order->setAttribute( + 'marketplace_settings_snapshot', + $this->enrichMarketplaceSnapshot($order->marketplace_settings_snapshot), + ); return $order; }); @@ -271,6 +275,10 @@ public function findForShow(Order $order): Order $order->setAttribute('available_actions', $availableActions); $order->setAttribute('is_editable', $order->status->isEditable()); + $order->setAttribute( + 'marketplace_settings_snapshot', + $this->enrichMarketplaceSnapshot($order->marketplace_settings_snapshot), + ); return $order; } @@ -895,4 +903,17 @@ private function presentVariantPricesFromCollection(int $variantId, Collection $ ->values() ->all(); } + + private function enrichMarketplaceSnapshot(?array $snapshot): ?array + { + if ($snapshot === null) { + return null; + } + + $snapshot['base_amount_formatted'] ??= 'Rp '.number_format($snapshot['base_amount'] ?? 0, 0, ',', '.'); + $snapshot['total_fee_amount_formatted'] ??= 'Rp '.number_format($snapshot['total_fee_amount'] ?? 0, 0, ',', '.'); + $snapshot['net_amount_formatted'] ??= 'Rp '.number_format($snapshot['net_amount'] ?? 0, 0, ',', '.'); + + return $snapshot; + } } diff --git a/app/Services/Manage/StokOpnameService.php b/app/Services/Manage/StokOpnameService.php index 120d289..b018652 100644 --- a/app/Services/Manage/StokOpnameService.php +++ b/app/Services/Manage/StokOpnameService.php @@ -63,6 +63,7 @@ public function catalogItems(): array 'id' => $variant->id, 'name' => $variant->name, 'stock' => $variant->stock, + 'stock_formatted' => $variant->stock_formatted, ]), ]) ->toArray(); diff --git a/app/Services/Master/MasterInventoryService.php b/app/Services/Master/MasterInventoryService.php new file mode 100644 index 0000000..3261027 --- /dev/null +++ b/app/Services/Master/MasterInventoryService.php @@ -0,0 +1,108 @@ +where('is_active', true) + ->whereHas('variants', fn (Builder $query) => $query->where('stock', '<=', 0)) + ->with([ + 'variants' => fn ($query) => $query + ->where('stock', '<=', 0) + ->with('media') + ->orderBy('name'), + ]) + ->orderBy('name') + ->limit($limit) + ->get() + ->map(fn (Product $product) => [ + 'id' => $product->id, + 'name' => $product->name, + 'edit_url' => route('admin.master.products.edit', $product), + 'variants' => $product->variants->map(fn (ProductVariant $variant) => [ + 'id' => $variant->id, + 'name' => $variant->name, + 'stock_formatted' => $variant->stock_formatted, + 'images' => MediaPresenter::collection($variant, 'images'), + ])->values()->all(), + ]) + ->filter(fn (array $group) => $group['variants'] !== []) + ->values() + ->all(); + } + + public function outOfStockRawMaterialGroups(int $limit = 50): array + { + return $this->rawMaterialGroupsWithStockFilter( + fn (Builder $query) => $query->where('stock', '<=', 0), + $limit, + ); + } + + public function lowStockRawMaterialGroups(int $limit = 50): array + { + $minStock = [ + RawMaterialUnit::YARD->value => 20, + RawMaterialUnit::METER->value => 10, + RawMaterialUnit::KILOGRAM->value => 5, + ]; + + return $this->rawMaterialGroupsWithStockFilter( + function (Builder $query) use ($minStock): void { + $query->where('stock', '>', 0)->where(function (Builder $query) use ($minStock): void { + foreach (RawMaterialUnit::cases() as $unit) { + $query->orWhere(function (Builder $query) use ($unit, $minStock): void { + $query->whereHas( + 'rawMaterial', + fn (Builder $rawMaterialQuery) => $rawMaterialQuery->where('unit', $unit), + )->where('stock', '<', $minStock[$unit->value]); + }); + } + }); + }, + $limit, + ); + } + + private function rawMaterialGroupsWithStockFilter(callable $priceConstraint, int $limit): array + { + return RawMaterial::query() + ->where('is_active', true) + ->whereHas('prices', $priceConstraint) + ->with([ + 'prices' => function ($query) use ($priceConstraint): void { + $priceConstraint($query); + $query->with('media')->orderBy('variant'); + }, + ]) + ->orderBy('name') + ->limit($limit) + ->get() + ->map(fn (RawMaterial $rawMaterial) => [ + 'id' => $rawMaterial->id, + 'name' => $rawMaterial->name, + 'edit_url' => route('admin.master.raw_materials.edit', $rawMaterial), + 'unit_label' => $rawMaterial->unit_label, + 'variants' => $rawMaterial->prices->map(fn (RawMaterialPrice $price) => [ + 'id' => $price->id, + 'name' => $price->variant, + 'stock_formatted' => $price->stock_formatted, + 'images' => MediaPresenter::collection($price, 'images'), + ])->values()->all(), + ]) + ->filter(fn (array $group) => $group['variants'] !== []) + ->values() + ->all(); + } +} diff --git a/app/Services/System/Setting/MarketplaceService.php b/app/Services/System/Setting/MarketplaceService.php index f73284a..ecb9b14 100644 --- a/app/Services/System/Setting/MarketplaceService.php +++ b/app/Services/System/Setting/MarketplaceService.php @@ -177,9 +177,12 @@ public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, arra 'is_affiliate' => $isAffiliate, 'fees' => $feeSnapshot['fees'], 'base_amount' => $totalAmount, + 'base_amount_formatted' => 'Rp '.number_format($totalAmount, 0, ',', '.'), 'results' => $calculation['results'], 'total_fee_amount' => $calculation['total_fee_amount'], + 'total_fee_amount_formatted' => 'Rp '.number_format($calculation['total_fee_amount'], 0, ',', '.'), 'net_amount' => $calculation['net_amount'], + 'net_amount_formatted' => 'Rp '.number_format($calculation['net_amount'], 0, ',', '.'), ]; } diff --git a/resources/js/pages/admin/manage/shared/PosCatalogCard.vue b/resources/js/components/catalog/PosCatalogCard.vue similarity index 100% rename from resources/js/pages/admin/manage/shared/PosCatalogCard.vue rename to resources/js/components/catalog/PosCatalogCard.vue diff --git a/resources/js/pages/admin/manage/shared/PosCatalogVariantThumb.vue b/resources/js/components/catalog/PosCatalogVariantThumb.vue similarity index 100% rename from resources/js/pages/admin/manage/shared/PosCatalogVariantThumb.vue rename to resources/js/components/catalog/PosCatalogVariantThumb.vue diff --git a/resources/js/components/data-table/DataTable.vue b/resources/js/components/data-table/DataTable.vue index e3944cd..17f98a5 100644 --- a/resources/js/components/data-table/DataTable.vue +++ b/resources/js/components/data-table/DataTable.vue @@ -4,6 +4,7 @@ import type { ColumnDef } from '@tanstack/vue-table'; import { FlexRender, getCoreRowModel, useVueTable } from '@tanstack/vue-table'; import { computed, provide } from 'vue'; import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue'; +import { buildPaginationSummary } from '@/lib/grouped-table'; import { Button } from '@/components/ui/button'; import { Table, @@ -109,20 +110,13 @@ provide('data-table-sort', { const showingCount = computed(() => props.paginationDisplayedCount ?? props.data.length); -const paginationSummary = computed(() => { - if (!props.pagination) { - return null; - } - - const { total } = props.pagination; - const label = props.paginationItemLabel; - - if (total === 0) { - return `Menampilkan 0 ${label}`; - } - - return `Menampilkan ${showingCount.value} ${label} dari ${total}`; -}); +const paginationSummary = computed(() => + buildPaginationSummary( + props.pagination, + showingCount.value, + props.paginationItemLabel, + ), +); function resolveRowSpan(row: TData, columnId: string): number | undefined { const column = resolvedColumns.value.find((item) => { diff --git a/resources/js/components/data-table/GroupedTableFooter.vue b/resources/js/components/data-table/GroupedTableFooter.vue new file mode 100644 index 0000000..f6563ec --- /dev/null +++ b/resources/js/components/data-table/GroupedTableFooter.vue @@ -0,0 +1,38 @@ + + + diff --git a/resources/js/components/display/RupiahText.vue b/resources/js/components/display/RupiahText.vue new file mode 100644 index 0000000..2d6b55a --- /dev/null +++ b/resources/js/components/display/RupiahText.vue @@ -0,0 +1,25 @@ + + + diff --git a/resources/js/pages/admin/master/products/form/MasterOutOfStockCatalogSection.vue b/resources/js/components/master/MasterOutOfStockCatalogSection.vue similarity index 96% rename from resources/js/pages/admin/master/products/form/MasterOutOfStockCatalogSection.vue rename to resources/js/components/master/MasterOutOfStockCatalogSection.vue index 4d50156..5809ff6 100644 --- a/resources/js/pages/admin/master/products/form/MasterOutOfStockCatalogSection.vue +++ b/resources/js/components/master/MasterOutOfStockCatalogSection.vue @@ -2,8 +2,8 @@ import { router } from '@inertiajs/vue3'; import { ChevronDown, PackageX, TriangleAlert } from '@lucide/vue'; import { computed, ref } from 'vue'; -import PosCatalogCard from '@/pages/admin/manage/shared/PosCatalogCard.vue'; -import PosCatalogVariantThumb from '@/pages/admin/manage/shared/PosCatalogVariantThumb.vue'; +import PosCatalogCard from '@/components/catalog/PosCatalogCard.vue'; +import PosCatalogVariantThumb from '@/components/catalog/PosCatalogVariantThumb.vue'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent } from '@/components/ui/card'; import { diff --git a/resources/js/pages/admin/manage/orders/form/OrderPrintButton.vue b/resources/js/components/order/OrderPrintButton.vue similarity index 100% rename from resources/js/pages/admin/manage/orders/form/OrderPrintButton.vue rename to resources/js/components/order/OrderPrintButton.vue diff --git a/resources/js/pages/admin/manage/orders/form/ThermalPrinterConnectButton.vue b/resources/js/components/order/ThermalPrinterConnectButton.vue similarity index 100% rename from resources/js/pages/admin/manage/orders/form/ThermalPrinterConnectButton.vue rename to resources/js/components/order/ThermalPrinterConnectButton.vue diff --git a/resources/js/composables/usePaginationSummary.ts b/resources/js/composables/usePaginationSummary.ts new file mode 100644 index 0000000..354555f --- /dev/null +++ b/resources/js/composables/usePaginationSummary.ts @@ -0,0 +1,13 @@ +import { computed, type MaybeRefOrGetter, toValue } from 'vue'; +import { buildPaginationSummary } from '@/lib/grouped-table'; +import type { DataTablePagination } from '@/types/data-table'; + +export function usePaginationSummary( + pagination: MaybeRefOrGetter, + showingCount: MaybeRefOrGetter, + itemLabel: string, +) { + return computed(() => + buildPaginationSummary(toValue(pagination), toValue(showingCount), itemLabel), + ); +} diff --git a/resources/js/constants/cutting-status.ts b/resources/js/constants/cutting-status.ts index 94bd766..d17c14e 100644 --- a/resources/js/constants/cutting-status.ts +++ b/resources/js/constants/cutting-status.ts @@ -1,3 +1,7 @@ +import type { Component } from 'vue'; +import { Check, RotateCcw, Scissors, X } from '@lucide/vue'; +import type { BadgeVariant } from '@/lib/badge-variant'; + export const CuttingStatus = { IN_PROGRESS: 'in_progress', COMPLETED: 'completed', @@ -5,3 +9,53 @@ export const CuttingStatus = { VERIFIED: 'verified', REJECTED: 'rejected', } as const; + +export type CuttingStatusValue = (typeof CuttingStatus)[keyof typeof CuttingStatus]; + +export function cuttingStatusBadgeVariant(status: string): BadgeVariant { + if (status === CuttingStatus.VERIFIED) { + return 'default'; + } + + if (status === CuttingStatus.REJECTED) { + return 'destructive'; + } + + if (status === CuttingStatus.COMPLETED) { + return 'secondary'; + } + + return 'outline'; +} + +export function cuttingStatusTransitionConfirmDescription(targetStatus: string): string { + if (targetStatus === CuttingStatus.COMPLETED) { + return 'Cutting akan ditandai selesai. Stok bahan baku akan dipotong sesuai pemakaian. Menunggu verifikasi admin toko.'; + } + + if (targetStatus === CuttingStatus.VERIFIED) { + return 'Hasil cutting akan diverifikasi. Stok sampel dan hasil cutting diluar sampel akan ditambahkan ke produk.'; + } + + if (targetStatus === CuttingStatus.IN_PROGRESS) { + return 'Cutting dikembalikan ke proses untuk diperbaiki.'; + } + + return ''; +} + +export function cuttingStatusActionIcon(status: string): Component { + if (status === CuttingStatus.COMPLETED) { + return Scissors; + } + + if (status === CuttingStatus.VERIFIED) { + return Check; + } + + if (status === CuttingStatus.IN_PROGRESS) { + return RotateCcw; + } + + return X; +} diff --git a/resources/js/constants/employee-advance-status.ts b/resources/js/constants/employee-advance-status.ts index 70452f9..c367b03 100644 --- a/resources/js/constants/employee-advance-status.ts +++ b/resources/js/constants/employee-advance-status.ts @@ -1,3 +1,5 @@ +import type { BadgeVariant } from '@/lib/badge-variant'; + export const EmployeeAdvanceStatus = { PENDING: 'pending', APPROVED: 'approved', @@ -5,3 +7,18 @@ export const EmployeeAdvanceStatus = { PARTIALLY_PAID: 'partially_paid', PAID: 'paid', } as const; + +export function employeeAdvanceStatusBadgeVariant(status: string): BadgeVariant { + switch (status) { + case EmployeeAdvanceStatus.APPROVED: + return 'default'; + case EmployeeAdvanceStatus.PARTIALLY_PAID: + return 'outline'; + case EmployeeAdvanceStatus.PAID: + return 'secondary'; + case EmployeeAdvanceStatus.REJECTED: + return 'destructive'; + default: + return 'outline'; + } +} diff --git a/resources/js/constants/index.ts b/resources/js/constants/index.ts index df8d09f..3c0c475 100644 --- a/resources/js/constants/index.ts +++ b/resources/js/constants/index.ts @@ -16,4 +16,4 @@ export * from './marketplace-fee-value-type'; export * from './setting-section'; export * from './appearance-mode'; export * from './catalog-sort-option'; -export * from './paper-size'; +export * from './stok-opname-status'; diff --git a/resources/js/constants/leave-request-status.ts b/resources/js/constants/leave-request-status.ts index cbf0d2f..193b11c 100644 --- a/resources/js/constants/leave-request-status.ts +++ b/resources/js/constants/leave-request-status.ts @@ -1,5 +1,18 @@ +import type { BadgeVariant } from '@/lib/badge-variant'; + export const LeaveRequestStatus = { PENDING: 'pending', APPROVED: 'approved', REJECTED: 'rejected', } as const; + +export function leaveRequestStatusBadgeVariant(status: string): BadgeVariant { + switch (status) { + case LeaveRequestStatus.APPROVED: + return 'default'; + case LeaveRequestStatus.REJECTED: + return 'destructive'; + default: + return 'outline'; + } +} diff --git a/resources/js/constants/order-payment-type.ts b/resources/js/constants/order-payment-type.ts index fd94673..12e74ff 100644 --- a/resources/js/constants/order-payment-type.ts +++ b/resources/js/constants/order-payment-type.ts @@ -1,4 +1,8 @@ export const OrderPaymentType = { CASH: 'cash', + TRANSFER: 'transfer', + QRIS: 'qris', MARKETPLACE: 'marketplace', } as const; + +export type OrderPaymentTypeValue = (typeof OrderPaymentType)[keyof typeof OrderPaymentType]; diff --git a/resources/js/constants/order-status.ts b/resources/js/constants/order-status.ts index 50bcb15..9f7d3a8 100644 --- a/resources/js/constants/order-status.ts +++ b/resources/js/constants/order-status.ts @@ -1,6 +1,55 @@ +import type { Component } from 'vue'; +import { Check, Send, X } from '@lucide/vue'; +import type { BadgeVariant } from '@/lib/badge-variant'; + export const OrderStatus = { PENDING: 'pending', PROCESSING: 'processing', COMPLETED: 'completed', CANCELLED: 'cancelled', } as const; + +export type OrderStatusValue = (typeof OrderStatus)[keyof typeof OrderStatus]; + +export function orderStatusBadgeVariant(status: string): BadgeVariant { + if (status === OrderStatus.COMPLETED) { + return 'default'; + } + + if (status === OrderStatus.CANCELLED) { + return 'destructive'; + } + + if (status === OrderStatus.PROCESSING) { + return 'secondary'; + } + + return 'outline'; +} + +export function orderStatusTransitionConfirmDescription( + targetStatus: string, + orderNumber: string, +): string { + if (targetStatus === OrderStatus.PROCESSING) { + return `Pesanan ${orderNumber} akan dikirim dan diproses.`; + } + + if (targetStatus === OrderStatus.COMPLETED) { + return `Pesanan ${orderNumber} akan ditandai selesai.`; + } + + return `Pesanan ${orderNumber} akan dibatalkan. Stok produk akan dikembalikan.`; +} + +export function orderStatusActionIcon(status: string): Component { + if (status === OrderStatus.PROCESSING) { + return Send; + } + + if (status === OrderStatus.COMPLETED) { + return Check; + } + + return X; +} diff --git a/resources/js/constants/payroll-status.ts b/resources/js/constants/payroll-status.ts index 53f28f9..9f9bff2 100644 --- a/resources/js/constants/payroll-status.ts +++ b/resources/js/constants/payroll-status.ts @@ -1,4 +1,10 @@ +import type { BadgeVariant } from '@/lib/badge-variant'; + export const PayrollStatus = { UNPAID: 'unpaid', PAID: 'paid', } as const; + +export function payrollStatusBadgeVariant(status: string): BadgeVariant { + return status === PayrollStatus.PAID ? 'secondary' : 'outline'; +} diff --git a/resources/js/constants/stok-opname-status.ts b/resources/js/constants/stok-opname-status.ts new file mode 100644 index 0000000..c1ddc27 --- /dev/null +++ b/resources/js/constants/stok-opname-status.ts @@ -0,0 +1,21 @@ +import type { BadgeVariant } from '@/lib/badge-variant'; + +export const StokOpnameStatus = { + DRAFT: 'draft', + PENDING: 'pending', + VERIFIED: 'verified', + REJECTED: 'rejected', +} as const; + +export type StokOpnameStatusValue = (typeof StokOpnameStatus)[keyof typeof StokOpnameStatus]; + +const badgeVariants: Record = { + draft: 'secondary', + pending: 'outline', + verified: 'default', + rejected: 'destructive', +}; + +export function stokOpnameStatusBadgeVariant(status: StokOpnameStatusValue): BadgeVariant { + return badgeVariants[status]; +} diff --git a/resources/js/lib/badge-variant.ts b/resources/js/lib/badge-variant.ts new file mode 100644 index 0000000..0e29d4e --- /dev/null +++ b/resources/js/lib/badge-variant.ts @@ -0,0 +1 @@ +export type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline'; diff --git a/resources/js/lib/grouped-table.ts b/resources/js/lib/grouped-table.ts new file mode 100644 index 0000000..b712737 --- /dev/null +++ b/resources/js/lib/grouped-table.ts @@ -0,0 +1,23 @@ +import type { DataTablePagination } from '@/types/data-table'; + +export function groupedTableRowNumber(firstItem: number | undefined, index: number): number { + return (firstItem ?? 1) + index; +} + +export function buildPaginationSummary( + pagination: DataTablePagination | undefined, + showingCount: number, + itemLabel: string, +): string | null { + if (!pagination) { + return null; + } + + const { total } = pagination; + + if (total === 0) { + return `Menampilkan 0 ${itemLabel}`; + } + + return `Menampilkan ${showingCount} ${itemLabel} dari ${total}`; +} diff --git a/resources/js/lib/stok-opname-display.ts b/resources/js/lib/stok-opname-display.ts new file mode 100644 index 0000000..f0f351a --- /dev/null +++ b/resources/js/lib/stok-opname-display.ts @@ -0,0 +1,19 @@ +export function stokOpnameDifference(physicalStock: number, systemStock: number): number { + return physicalStock - systemStock; +} + +export function stokOpnameDifferenceClass(difference: number): string { + if (difference > 0) { + return 'text-green-600 font-semibold'; + } + + if (difference < 0) { + return 'text-red-600 font-semibold'; + } + + return 'text-muted-foreground'; +} + +export function stokOpnameDifferenceText(difference: number): string { + return `${difference > 0 ? '+' : ''}${difference}`; +} diff --git a/resources/js/pages/admin/finance/employee-advances/table/columns.ts b/resources/js/pages/admin/finance/employee-advances/table/columns.ts index 4061478..d241577 100644 --- a/resources/js/pages/admin/finance/employee-advances/table/columns.ts +++ b/resources/js/pages/admin/finance/employee-advances/table/columns.ts @@ -2,25 +2,10 @@ import type { ColumnDef } from '@tanstack/vue-table'; import { h } from 'vue'; import { DataTableColumnHeader } from '@/components/data-table'; import { Badge } from '@/components/ui/badge'; -import { EmployeeAdvanceStatus } from '@/constants/employee-advance-status'; +import { employeeAdvanceStatusBadgeVariant } from '@/constants/employee-advance-status'; import type { EmployeeAdvanceListItem } from '@/types/employee-advance'; import DataTableActions from './data-table-actions.vue'; -function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { - switch (status) { - case EmployeeAdvanceStatus.APPROVED: - return 'default'; - case EmployeeAdvanceStatus.PARTIALLY_PAID: - return 'outline'; - case EmployeeAdvanceStatus.PAID: - return 'secondary'; - case EmployeeAdvanceStatus.REJECTED: - return 'destructive'; - default: - return 'outline'; - } -} - export function createColumns( authEmployeeId: number | null, onEdit: (employeeAdvance: EmployeeAdvanceListItem) => void, @@ -78,7 +63,7 @@ export function createColumns( header: () => h(DataTableColumnHeader, { title: 'Status', column: 'status' }), cell: ({ row }) => h( Badge, - { variant: statusVariant(row.original.status) }, + { variant: employeeAdvanceStatusBadgeVariant(row.original.status) }, () => row.original.status_label, ), }, diff --git a/resources/js/pages/admin/finance/payroll/table/columns.ts b/resources/js/pages/admin/finance/payroll/table/columns.ts index ad17100..de0f69e 100644 --- a/resources/js/pages/admin/finance/payroll/table/columns.ts +++ b/resources/js/pages/admin/finance/payroll/table/columns.ts @@ -2,14 +2,10 @@ import type { ColumnDef } from '@tanstack/vue-table'; import { h } from 'vue'; import { DataTableColumnHeader } from '@/components/data-table'; import { Badge } from '@/components/ui/badge'; -import { PayrollStatus } from '@/constants/payroll-status'; +import { payrollStatusBadgeVariant } from '@/constants/payroll-status'; import type { PayrollListItem } from '@/types/payroll'; import DataTableActions from './data-table-actions.vue'; -function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { - return status === PayrollStatus.PAID ? 'secondary' : 'outline'; -} - export function createColumns( onAdjust: (payroll: PayrollListItem) => void, ): ColumnDef[] { @@ -45,7 +41,7 @@ export function createColumns( header: () => h(DataTableColumnHeader, { title: 'Status', column: 'status' }), cell: ({ row }) => h( Badge, - { variant: statusVariant(row.original.status) }, + { variant: payrollStatusBadgeVariant(row.original.status) }, () => row.original.status_label, ), }, diff --git a/resources/js/pages/admin/hr/leave-requests/table/columns.ts b/resources/js/pages/admin/hr/leave-requests/table/columns.ts index 5bc67f0..aeeaa89 100644 --- a/resources/js/pages/admin/hr/leave-requests/table/columns.ts +++ b/resources/js/pages/admin/hr/leave-requests/table/columns.ts @@ -2,21 +2,10 @@ import type { ColumnDef } from '@tanstack/vue-table'; import { h } from 'vue'; import { DataTableColumnHeader } from '@/components/data-table'; import { Badge } from '@/components/ui/badge'; -import { LeaveRequestStatus } from '@/constants/leave-request-status'; +import { leaveRequestStatusBadgeVariant } from '@/constants/leave-request-status'; import type { LeaveRequestListItem } from '@/types/leave-request'; import DataTableActions from './data-table-actions.vue'; -function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { - switch (status) { - case LeaveRequestStatus.APPROVED: - return 'default'; - case LeaveRequestStatus.REJECTED: - return 'destructive'; - default: - return 'outline'; - } -} - export function createColumns( authEmployeeId: number | null, onEdit: (leaveRequest: LeaveRequestListItem) => void, @@ -55,7 +44,7 @@ export function createColumns( header: () => h(DataTableColumnHeader, { title: 'Status', column: 'status' }), cell: ({ row }) => h( Badge, - { variant: statusVariant(row.original.status) }, + { variant: leaveRequestStatusBadgeVariant(row.original.status) }, () => row.original.status_label, ), }, diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingPosCartDetailDialog.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingPosCartDetailDialog.vue new file mode 100644 index 0000000..5992f39 --- /dev/null +++ b/resources/js/pages/admin/manage/cuttings/form/CuttingPosCartDetailDialog.vue @@ -0,0 +1,88 @@ + + + diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingPosForm.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingPosForm.vue index 802a775..938daa4 100644 --- a/resources/js/pages/admin/manage/cuttings/form/CuttingPosForm.vue +++ b/resources/js/pages/admin/manage/cuttings/form/CuttingPosForm.vue @@ -1,51 +1,19 @@ + + diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingPosMaterialSummaryItems.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingPosMaterialSummaryItems.vue new file mode 100644 index 0000000..41f5845 --- /dev/null +++ b/resources/js/pages/admin/manage/cuttings/form/CuttingPosMaterialSummaryItems.vue @@ -0,0 +1,71 @@ + + + diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingPosResultCatalogPanel.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingPosResultCatalogPanel.vue new file mode 100644 index 0000000..8a0dfb8 --- /dev/null +++ b/resources/js/pages/admin/manage/cuttings/form/CuttingPosResultCatalogPanel.vue @@ -0,0 +1,122 @@ + + + diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingPosResultSummaryItems.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingPosResultSummaryItems.vue new file mode 100644 index 0000000..2d972f1 --- /dev/null +++ b/resources/js/pages/admin/manage/cuttings/form/CuttingPosResultSummaryItems.vue @@ -0,0 +1,91 @@ + + + diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingPosSummaryPanel.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingPosSummaryPanel.vue new file mode 100644 index 0000000..9a94083 --- /dev/null +++ b/resources/js/pages/admin/manage/cuttings/form/CuttingPosSummaryPanel.vue @@ -0,0 +1,108 @@ + + +