Compare commits
10 Commits
fbdad6923c
...
71a7863244
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71a7863244 | ||
|
|
a778e2f499 | ||
|
|
5182dcc012 | ||
|
|
5a89a0d1e3 | ||
|
|
9f46b6d6be | ||
|
|
1f41f04199 | ||
|
|
e1a0d25c3c | ||
|
|
1e1f195f2d | ||
|
|
ed93fd3eac | ||
|
|
6c1c14c588 |
@ -439,7 +439,6 @@ ### Relationship Naming
|
|||||||
|
|
||||||
// Custom FK → pastikan ada parameter
|
// Custom FK → pastikan ada parameter
|
||||||
createdCuttings() // → HasMany Cutting, 'created_by_id'
|
createdCuttings() // → HasMany Cutting, 'created_by_id'
|
||||||
submittedCuttings() // → HasMany Cutting, 'submitted_by_id'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Eloquent Select & Eager Loading — Selalu Select yang Dibutuhkan
|
### Eloquent Select & Eager Loading — Selalu Select yang Dibutuhkan
|
||||||
|
|||||||
@ -12,7 +12,7 @@ ### `users` → User
|
|||||||
- Implements: HasMedia (Spatie Media Library)
|
- Implements: HasMedia (Spatie Media Library)
|
||||||
- Media Collections: photos (single photo, with thumb conversion)
|
- Media Collections: photos (single photo, with thumb conversion)
|
||||||
- Scopes: active()
|
- Scopes: active()
|
||||||
- Relations: userProfile(HasOne→UserProfile), employee(HasOne→Employee), attendances(HasMany→Attendance), cashAccounts(HasMany→CashAccount,created_by_id), cashTransactions(HasMany→CashTransaction,created_by_id), createdCuttings(HasMany→Cutting,created_by_id), submittedCuttings(HasMany→Cutting,submitted_by_id), createdExpenses(HasMany→Expense,created_by_id), createdOrders(HasMany→Order,created_by_id), marketingOrders(HasMany→Order,marketing_id), orderItems(HasMany→OrderItem), createdPurchases(HasMany→Purchase,created_by_id), createdRestocks(HasMany→Restock,created_by_id), stokOpnamesCreated(HasMany→StokOpname,created_by_id), stokOpnamesVerified(HasMany→StokOpname,verified_by_id), employeeAdvancesPaid(HasMany→EmployeeAdvance,paid_by_id), employeeAdvancesVerified(HasMany→EmployeeAdvance,verified_by_id), paidPayrolls(HasMany→Payroll,paid_by_id), payrollPeriodsClosed(HasMany→PayrollPeriod,closed_by_id), notifications(HasMany→AppNotification), rejections(HasMany→Rejection,rejected_by_id), pushSubscriptions(HasMany→PushSubscription,morph)
|
- Relations: userProfile(HasOne→UserProfile), employee(HasOne→Employee), attendances(HasMany→Attendance), cashAccounts(HasMany→CashAccount,created_by_id), cashTransactions(HasMany→CashTransaction,created_by_id), createdCuttings(HasMany→Cutting,created_by_id), createdExpenses(HasMany→Expense,created_by_id), createdOrders(HasMany→Order,created_by_id), marketingOrders(HasMany→Order,marketing_id), orderItems(HasMany→OrderItem), createdPurchases(HasMany→Purchase,created_by_id), createdRestocks(HasMany→Restock,created_by_id), stokOpnamesCreated(HasMany→StokOpname,created_by_id), stokOpnamesVerified(HasMany→StokOpname,verified_by_id), employeeAdvancesPaid(HasMany→EmployeeAdvance,paid_by_id), employeeAdvancesVerified(HasMany→EmployeeAdvance,verified_by_id), paidPayrolls(HasMany→Payroll,paid_by_id), payrollPeriodsClosed(HasMany→PayrollPeriod,closed_by_id), notifications(HasMany→AppNotification), rejections(HasMany→Rejection,rejected_by_id), pushSubscriptions(HasMany→PushSubscription,morph)
|
||||||
- Accessor: avatar → temporary S3 URL dari media 'photos' (atau null)
|
- Accessor: avatar → temporary S3 URL dari media 'photos' (atau null)
|
||||||
|
|
||||||
### `user_profiles` → UserProfile
|
### `user_profiles` → UserProfile
|
||||||
@ -187,10 +187,10 @@ ### `rejections` → Rejection
|
|||||||
## Production
|
## Production
|
||||||
|
|
||||||
### `cuttings` → Cutting
|
### `cuttings` → Cutting
|
||||||
`id` `created_by_id`(FK→users) `submitted_by_id`(FK→users,null) `status`(enum,default:in_progress) `description`(100,null) `total_material_cost`(ubig,null) `sewing_cost`(ubig,default:0) `other_cost`(ubig,default:0) `cost_per_unit`(ubig,null) `created_at` `updated_at` `deleted_at`
|
`id` `created_by_id`(FK→users) `status`(enum,default:in_progress) `description`(100,null) `total_material_cost`(ubig,null) `cost_per_unit`(ubig,null) `created_at` `updated_at` `deleted_at`
|
||||||
- Casts: status(CuttingStatus), total_material_cost(int), cost_per_unit(int), sewing_cost(int), other_cost(int)
|
- Casts: status(CuttingStatus), total_material_cost(int), cost_per_unit(int)
|
||||||
- Scopes: cancelled(), completed(), inProgress()
|
- Scopes: cancelled(), completed(), inProgress()
|
||||||
- Relations: createdBy(BelongsTo→User), submittedBy(BelongsTo→User), cuttingMaterialCombinations(HasMany→CuttingMaterialCombination), cuttingMaterials(HasMany→CuttingMaterial), cuttingResults(HasMany→CuttingResult)
|
- Relations: createdBy(BelongsTo→User), cuttingMaterialCombinations(HasMany→CuttingMaterialCombination), cuttingMaterials(HasMany→CuttingMaterial), cuttingResults(HasMany→CuttingResult)
|
||||||
|
|
||||||
### `cutting_material_combinations` → CuttingMaterialCombination
|
### `cutting_material_combinations` → CuttingMaterialCombination
|
||||||
`id` `user_id`(FK→users,null) `cutting_id`(FK→cuttings,null) `material_result`(int,null) `created_at` `updated_at` `deleted_at`
|
`id` `user_id`(FK→users,null) `cutting_id`(FK→cuttings,null) `material_result`(int,null) `created_at` `updated_at` `deleted_at`
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Manage;
|
namespace App\Http\Controllers\Admin\Manage;
|
||||||
|
|
||||||
|
use App\Enums\CuttingStatus;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Admin\Manage\CuttingRequest;
|
use App\Http\Requests\Admin\Manage\CuttingRequest;
|
||||||
use App\Http\Requests\PaginatedRequest;
|
use App\Http\Requests\PaginatedRequest;
|
||||||
@ -24,7 +25,13 @@ public function index(PaginatedRequest $request): Response
|
|||||||
return Inertia::render('admin/manage/cutting/index', [
|
return Inertia::render('admin/manage/cutting/index', [
|
||||||
'cuttings' => $this->service->paginated(
|
'cuttings' => $this->service->paginated(
|
||||||
...$request->validatedWithDefaults(),
|
...$request->validatedWithDefaults(),
|
||||||
|
filters: $request->only(['product_name', 'status']),
|
||||||
),
|
),
|
||||||
|
'filters' => $request->only(['product_name', 'status']),
|
||||||
|
'filterOptions' => [
|
||||||
|
'statusOptions' => CuttingStatus::toSelect(),
|
||||||
|
'productNames' => $this->service->getProductNames(),
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +42,13 @@ public function create(): Response
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function show(Cutting $cutting): Response
|
||||||
|
{
|
||||||
|
return Inertia::render('admin/manage/cutting/show', [
|
||||||
|
'cutting' => $this->service->getForShow($cutting),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function store(CuttingRequest $request): RedirectResponse
|
public function store(CuttingRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
return $this->handleAction(
|
return $this->handleAction(
|
||||||
|
|||||||
@ -64,18 +64,18 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email' => 'Email',
|
'email' => 'email',
|
||||||
'username' => 'Username',
|
'username' => 'username',
|
||||||
'role' => 'Role',
|
'role' => 'role',
|
||||||
'full_name' => 'Nama Lengkap',
|
'full_name' => 'nama lengkap',
|
||||||
'phone_number' => 'No. Telepon',
|
'phone_number' => 'no. telepon',
|
||||||
'gender' => 'Jenis Kelamin',
|
'gender' => 'jenis kelamin',
|
||||||
'birth_date' => 'Tanggal Lahir',
|
'birth_date' => 'tanggal lahir',
|
||||||
'address' => 'Alamat',
|
'address' => 'alamat',
|
||||||
'join_date' => 'Tanggal Masuk',
|
'join_date' => 'tanggal masuk',
|
||||||
'resign_date' => 'Tanggal Keluar',
|
'resign_date' => 'tanggal keluar',
|
||||||
'employment_status' => 'Status Kepegawaian',
|
'employment_status' => 'status kepegawaian',
|
||||||
'base_salary' => 'Gaji Pokok',
|
'base_salary' => 'gaji pokok',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,19 +35,19 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'description' => 'Keterangan',
|
'description' => 'keterangan',
|
||||||
'product_name' => 'Nama Produk',
|
'product_name' => 'nama produk',
|
||||||
'sample' => 'Sample',
|
'sample' => 'sample',
|
||||||
'original_outside_sample' => 'Diluar Sample',
|
'original_outside_sample' => 'diluar sample',
|
||||||
'cutting_result' => 'Hasil',
|
'cutting_result' => 'hasil',
|
||||||
'materials' => 'Bahan Baku',
|
'materials' => 'bahan baku',
|
||||||
'materials.*.raw_material_price_id' => 'Varian Bahan Baku',
|
'materials.*.raw_material_price_id' => 'varian bahan baku',
|
||||||
'materials.*.material_usage' => 'Pemakaian',
|
'materials.*.material_usage' => 'pemakaian',
|
||||||
'materials.*.material_result' => 'Hasil Material',
|
'materials.*.material_result' => 'hasil material',
|
||||||
'materials.*.combination_index' => 'Indeks Kombinasi',
|
'materials.*.combination_index' => 'indeks kombinasi',
|
||||||
'combinations' => 'Kombinasi',
|
'combinations' => 'kombinasi',
|
||||||
'combinations.*.material_result' => 'Hasil Kombinasi',
|
'combinations.*.material_result' => 'hasil kombinasi',
|
||||||
'photo_key' => 'Foto',
|
'photo_key' => 'foto',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,22 +48,22 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'Nama Bahan Baku',
|
'name' => 'nama bahan baku',
|
||||||
'unit' => 'Satuan',
|
'unit' => 'satuan',
|
||||||
'variants' => 'Varian',
|
'variants' => 'varian',
|
||||||
'variants.*.variant' => 'Nama Varian',
|
'variants.*.variant' => 'nama varian',
|
||||||
'variants.*.price' => 'Harga',
|
'variants.*.price' => 'harga',
|
||||||
'variants.*.stock' => 'Stok',
|
'variants.*.stock' => 'stok',
|
||||||
'variants.*.photo_key' => 'Foto Varian',
|
'variants.*.photo_key' => 'foto varian',
|
||||||
'existing_items' => 'Item Bahan Baku',
|
'existing_items' => 'item bahan baku',
|
||||||
'existing_items.*.raw_material_price_id' => 'Varian Bahan Baku',
|
'existing_items.*.raw_material_price_id' => 'varian bahan baku',
|
||||||
'existing_items.*.quantity' => 'Jumlah',
|
'existing_items.*.quantity' => 'jumlah',
|
||||||
'existing_items.*.unit_price' => 'Harga Beli',
|
'existing_items.*.unit_price' => 'harga beli',
|
||||||
'supplier_id' => 'Supplier',
|
'supplier_id' => 'supplier',
|
||||||
'discount' => 'Diskon',
|
'discount' => 'diskon',
|
||||||
'shipping_cost' => 'Ongkir',
|
'shipping_cost' => 'ongkir',
|
||||||
'notes' => 'Keterangan',
|
'notes' => 'keterangan',
|
||||||
'photo_key' => 'Foto',
|
'photo_key' => 'foto',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,12 +29,12 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'stock_type' => 'Jenis Stok',
|
'stock_type' => 'jenis stok',
|
||||||
'items' => 'Item Produk',
|
'items' => 'item produk',
|
||||||
'items.*.product_variant_id' => 'Varian Produk',
|
'items.*.product_variant_id' => 'varian produk',
|
||||||
'items.*.quantity' => 'Jumlah',
|
'items.*.quantity' => 'jumlah',
|
||||||
'notes' => 'Keterangan',
|
'notes' => 'keterangan',
|
||||||
'photo_key' => 'Foto',
|
'photo_key' => 'foto',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,23 +57,23 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'stock_type' => 'Tipe Stok',
|
'stock_type' => 'tipe stok',
|
||||||
'channel' => 'Channel',
|
'channel' => 'channel',
|
||||||
'price_type' => 'Tipe Harga',
|
'price_type' => 'tipe harga',
|
||||||
'payment_type' => 'Tipe Pembayaran',
|
'payment_type' => 'tipe pembayaran',
|
||||||
'customer_id' => 'Pelanggan',
|
'customer_id' => 'pelanggan',
|
||||||
'marketing_id' => 'Marketing',
|
'marketing_id' => 'marketing',
|
||||||
'discount' => 'Diskon',
|
'discount' => 'diskon',
|
||||||
'nego_price' => 'Harga Nego',
|
'nego_price' => 'harga nego',
|
||||||
'is_completed' => 'Pesanan Selesai',
|
'is_completed' => 'pesanan selesai',
|
||||||
'is_affiliate' => 'Affiliasi',
|
'is_affiliate' => 'affiliasi',
|
||||||
'tiktok_order_id' => 'ID Pesanan TikTok',
|
'tiktok_order_id' => 'id pesanan tiktok',
|
||||||
'shopee_order_id' => 'ID Pesanan Shopee',
|
'shopee_order_id' => 'id pesanan shopee',
|
||||||
'items' => 'Item Produk',
|
'items' => 'item produk',
|
||||||
'items.*.product_variant_id' => 'Varian Produk',
|
'items.*.product_variant_id' => 'varian produk',
|
||||||
'items.*.quantity' => 'Jumlah',
|
'items.*.quantity' => 'jumlah',
|
||||||
'notes' => 'Keterangan',
|
'notes' => 'keterangan',
|
||||||
'photo_key' => 'Foto',
|
'photo_key' => 'foto',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,20 +61,20 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'Nama Produk',
|
'name' => 'nama produk',
|
||||||
'description' => 'Deskripsi',
|
'description' => 'deskripsi',
|
||||||
'status' => 'Status',
|
'status' => 'status',
|
||||||
'category_ids' => 'Kategori',
|
'category_ids' => 'kategori',
|
||||||
'variants' => 'Varian',
|
'variants' => 'varian',
|
||||||
'variants.*.name' => 'Nama Varian',
|
'variants.*.name' => 'nama varian',
|
||||||
'variants.*.stock' => 'Stok',
|
'variants.*.stock' => 'stok',
|
||||||
'variants.*.reject_stock' => 'Stok Reject',
|
'variants.*.reject_stock' => 'stok reject',
|
||||||
'variants.*.retail_stock' => 'Stok Retail',
|
'variants.*.retail_stock' => 'stok retail',
|
||||||
'variants.*.photo_keys' => 'Foto',
|
'variants.*.photo_keys' => 'foto',
|
||||||
'variants.*.photo_keys.*' => 'Foto',
|
'variants.*.photo_keys.*' => 'foto',
|
||||||
'variants.*.prices' => 'Harga',
|
'variants.*.prices' => 'harga',
|
||||||
'variants.*.prices.*.type' => 'Tipe Harga',
|
'variants.*.prices.*.type' => 'tipe harga',
|
||||||
'variants.*.prices.*.price' => 'Harga',
|
'variants.*.prices.*.price' => 'harga',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,15 +40,15 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'Nama Varian',
|
'name' => 'nama varian',
|
||||||
'stock' => 'Stok Bagus',
|
'stock' => 'stok bagus',
|
||||||
'reject_stock' => 'Stok Reject',
|
'reject_stock' => 'stok reject',
|
||||||
'retail_stock' => 'Stok Ecer',
|
'retail_stock' => 'stok ecer',
|
||||||
'photo_keys' => 'Foto',
|
'photo_keys' => 'foto',
|
||||||
'photo_keys.*' => 'Foto',
|
'photo_keys.*' => 'foto',
|
||||||
'prices' => 'Harga',
|
'prices' => 'harga',
|
||||||
'prices.*.type' => 'Tipe Harga',
|
'prices.*.type' => 'tipe harga',
|
||||||
'prices.*.price' => 'Harga',
|
'prices.*.price' => 'harga',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,8 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'quantity' => 'Jumlah Transfer',
|
'quantity' => 'jumlah transfer',
|
||||||
'description' => 'Keterangan',
|
'description' => 'keterangan',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,14 +40,14 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'Nama Bahan Baku',
|
'name' => 'nama bahan baku',
|
||||||
'unit' => 'Satuan',
|
'unit' => 'satuan',
|
||||||
'is_active' => 'Status',
|
'is_active' => 'status',
|
||||||
'variants' => 'Varian',
|
'variants' => 'varian',
|
||||||
'variants.*.variant' => 'Nama Varian',
|
'variants.*.variant' => 'nama varian',
|
||||||
'variants.*.price' => 'Harga',
|
'variants.*.price' => 'harga',
|
||||||
'variants.*.stock' => 'Stok',
|
'variants.*.stock' => 'stok',
|
||||||
'variants.*.photo_key' => 'Foto Varian',
|
'variants.*.photo_key' => 'foto varian',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,10 +33,10 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'variant' => 'Nama Varian',
|
'variant' => 'nama varian',
|
||||||
'price' => 'Harga',
|
'price' => 'harga',
|
||||||
'stock' => 'Stok',
|
'stock' => 'stok',
|
||||||
'photo_key' => 'Foto',
|
'photo_key' => 'foto',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,8 +32,8 @@ public function rules(): array
|
|||||||
public function attributes(): array
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'Nama Role',
|
'name' => 'nama role',
|
||||||
'permissions' => 'Permission',
|
'permissions' => 'permission',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,9 +73,9 @@ public function attributes(): array
|
|||||||
'tiktok_shop_affiliate.base' => 'dasar affiliate tiktok shop',
|
'tiktok_shop_affiliate.base' => 'dasar affiliate tiktok shop',
|
||||||
'tiktok_shop_affiliate.type' => 'tipe affiliate tiktok shop',
|
'tiktok_shop_affiliate.type' => 'tipe affiliate tiktok shop',
|
||||||
'tiktok_shop_affiliate.value' => 'nilai affiliate tiktok shop',
|
'tiktok_shop_affiliate.value' => 'nilai affiliate tiktok shop',
|
||||||
'tiktok_shop_pre_order_service_fee.base' => 'dasar layanan PO tiktok shop',
|
'tiktok_shop_pre_order_service_fee.base' => 'dasar layanan po tiktok shop',
|
||||||
'tiktok_shop_pre_order_service_fee.type' => 'tipe layanan PO tiktok shop',
|
'tiktok_shop_pre_order_service_fee.type' => 'tipe layanan po tiktok shop',
|
||||||
'tiktok_shop_pre_order_service_fee.value' => 'nilai layanan PO tiktok shop',
|
'tiktok_shop_pre_order_service_fee.value' => 'nilai layanan po tiktok shop',
|
||||||
'shopee_admin_fee.base' => 'dasar biaya administrasi shopee',
|
'shopee_admin_fee.base' => 'dasar biaya administrasi shopee',
|
||||||
'shopee_admin_fee.type' => 'tipe biaya administrasi shopee',
|
'shopee_admin_fee.type' => 'tipe biaya administrasi shopee',
|
||||||
'shopee_admin_fee.value' => 'nilai biaya administrasi shopee',
|
'shopee_admin_fee.value' => 'nilai biaya administrasi shopee',
|
||||||
@ -94,12 +94,12 @@ public function attributes(): array
|
|||||||
'shopee_order_processing_fee.base' => 'dasar biaya proses pesanan shopee',
|
'shopee_order_processing_fee.base' => 'dasar biaya proses pesanan shopee',
|
||||||
'shopee_order_processing_fee.type' => 'tipe biaya proses pesanan shopee',
|
'shopee_order_processing_fee.type' => 'tipe biaya proses pesanan shopee',
|
||||||
'shopee_order_processing_fee.value' => 'nilai biaya proses pesanan shopee',
|
'shopee_order_processing_fee.value' => 'nilai biaya proses pesanan shopee',
|
||||||
'shopee_ams_commission_fee.base' => 'dasar biaya komisi AMS shopee',
|
'shopee_ams_commission_fee.base' => 'dasar biaya komisi ams shopee',
|
||||||
'shopee_ams_commission_fee.type' => 'tipe biaya komisi AMS shopee',
|
'shopee_ams_commission_fee.type' => 'tipe biaya komisi ams shopee',
|
||||||
'shopee_ams_commission_fee.value' => 'nilai biaya komisi AMS shopee',
|
'shopee_ams_commission_fee.value' => 'nilai biaya komisi ams shopee',
|
||||||
'shopee_pre_order.base' => 'dasar PO shopee',
|
'shopee_pre_order.base' => 'dasar po shopee',
|
||||||
'shopee_pre_order.type' => 'tipe PO shopee',
|
'shopee_pre_order.type' => 'tipe po shopee',
|
||||||
'shopee_pre_order.value' => 'nilai PO shopee',
|
'shopee_pre_order.value' => 'nilai po shopee',
|
||||||
'shopee_live_extra.base' => 'dasar live extra shopee',
|
'shopee_live_extra.base' => 'dasar live extra shopee',
|
||||||
'shopee_live_extra.type' => 'tipe live extra shopee',
|
'shopee_live_extra.type' => 'tipe live extra shopee',
|
||||||
'shopee_live_extra.value' => 'nilai live extra shopee',
|
'shopee_live_extra.value' => 'nilai live extra shopee',
|
||||||
|
|||||||
96
app/Jobs/CleanupOrphanedMediaJob.php
Normal file
96
app/Jobs/CleanupOrphanedMediaJob.php
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||||
|
|
||||||
|
class CleanupOrphanedMediaJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public int $tries = 3;
|
||||||
|
|
||||||
|
public int $chunkSize = 100;
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$orphanedMedia = $this->getOrphanedMedia();
|
||||||
|
|
||||||
|
if ($orphanedMedia->isEmpty()) {
|
||||||
|
Log::info('CleanupOrphanedMediaJob: Tidak ada media orphaned.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalDeleted = 0;
|
||||||
|
|
||||||
|
$orphanedMedia->each(function (Media $media) use (&$totalDeleted) {
|
||||||
|
$this->deleteMedia($media);
|
||||||
|
$totalDeleted++;
|
||||||
|
});
|
||||||
|
|
||||||
|
Log::info("CleanupOrphanedMediaJob: Berhasil hapus {$totalDeleted} media orphaned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getOrphanedMedia()
|
||||||
|
{
|
||||||
|
$allMedia = Media::select(['id', 'model_type', 'model_id', 'file_name', 'custom_properties', 'disk'])
|
||||||
|
->get()
|
||||||
|
->groupBy(fn (Media $m) => $m->model_type.'|'.$m->model_id);
|
||||||
|
|
||||||
|
$orphanedIds = [];
|
||||||
|
|
||||||
|
foreach ($allMedia as $key => $mediaItems) {
|
||||||
|
[$modelType, $modelId] = explode('|', $key);
|
||||||
|
|
||||||
|
if ($this->isOrphaned($modelType, (int) $modelId)) {
|
||||||
|
foreach ($mediaItems as $media) {
|
||||||
|
$orphanedIds[] = $media->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($orphanedIds)) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Media::whereIn('id', $orphanedIds)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isOrphaned(string $modelType, int $modelId): bool
|
||||||
|
{
|
||||||
|
if (! class_exists($modelType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = $modelType::withTrashed()->find($modelId);
|
||||||
|
|
||||||
|
if (! $model) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_exists($model, 'trashed') && $model->trashed()) {
|
||||||
|
return $model->deleted_at->lt(now()->subMonth());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteMedia(Media $media): void
|
||||||
|
{
|
||||||
|
$s3Key = $media->getCustomProperty('s3_key');
|
||||||
|
|
||||||
|
if ($s3Key && Storage::disk($media->disk)->exists($s3Key)) {
|
||||||
|
Storage::disk($media->disk)->delete($s3Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
$media->forceDelete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@
|
|||||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||||
|
|
||||||
#[Appends(['formatted_cost_per_unit', 'formatted_other_cost', 'formatted_sewing_cost', 'status_label', 'formatted_total_material_cost'])]
|
#[Appends(['formatted_cost_per_unit', 'status_label', 'formatted_total_material_cost', 'formatted_created_at'])]
|
||||||
#[Guarded(['id'])]
|
#[Guarded(['id'])]
|
||||||
class Cutting extends Model implements HasMedia
|
class Cutting extends Model implements HasMedia
|
||||||
{
|
{
|
||||||
@ -30,8 +30,6 @@ protected function casts(): array
|
|||||||
'status' => CuttingStatus::class,
|
'status' => CuttingStatus::class,
|
||||||
'total_material_cost' => 'integer',
|
'total_material_cost' => 'integer',
|
||||||
'cost_per_unit' => 'integer',
|
'cost_per_unit' => 'integer',
|
||||||
'sewing_cost' => 'integer',
|
|
||||||
'other_cost' => 'integer',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,20 +40,6 @@ protected function formattedCostPerUnit(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function formattedOtherCost(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->other_cost, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function formattedSewingCost(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->sewing_cost, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function statusLabel(): Attribute
|
protected function statusLabel(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@ -70,6 +54,13 @@ protected function formattedTotalMaterialCost(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function formattedCreatedAt(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => $this->created_at?->translatedFormat('l, d F Y'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[Scope]
|
#[Scope]
|
||||||
protected function cancelled(Builder $query): void
|
protected function cancelled(Builder $query): void
|
||||||
{
|
{
|
||||||
@ -108,11 +99,6 @@ public function cuttingResults(): HasMany
|
|||||||
return $this->hasMany(CuttingResult::class);
|
return $this->hasMany(CuttingResult::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submittedBy(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class, 'submitted_by_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerMediaCollections(): void
|
public function registerMediaCollections(): void
|
||||||
{
|
{
|
||||||
$this->addMediaCollection('photos');
|
$this->addMediaCollection('photos');
|
||||||
|
|||||||
@ -50,7 +50,7 @@ public function cutting(): BelongsTo
|
|||||||
|
|
||||||
public function rawMaterialPrice(): BelongsTo
|
public function rawMaterialPrice(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(RawMaterialPrice::class);
|
return $this->belongsTo(RawMaterialPrice::class)->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
|
|||||||
@ -69,7 +69,7 @@ public function order(): BelongsTo
|
|||||||
|
|
||||||
public function productVariant(): BelongsTo
|
public function productVariant(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(ProductVariant::class);
|
return $this->belongsTo(ProductVariant::class)->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
|
|||||||
@ -68,7 +68,7 @@ public function orderItems(): HasMany
|
|||||||
|
|
||||||
public function product(): BelongsTo
|
public function product(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Product::class);
|
return $this->belongsTo(Product::class)->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function productPrices(): HasMany
|
public function productPrices(): HasMany
|
||||||
|
|||||||
@ -41,7 +41,7 @@ protected function formattedUnitPrice(): Attribute
|
|||||||
|
|
||||||
public function productVariant(): BelongsTo
|
public function productVariant(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(ProductVariant::class);
|
return $this->belongsTo(ProductVariant::class)->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function restock(): BelongsTo
|
public function restock(): BelongsTo
|
||||||
|
|||||||
@ -215,11 +215,6 @@ public function stokOpnamesVerified(): HasMany
|
|||||||
return $this->hasMany(StokOpname::class, 'verified_by_id');
|
return $this->hasMany(StokOpname::class, 'verified_by_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submittedCuttings(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(Cutting::class, 'submitted_by_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function userProfile(): HasOne
|
public function userProfile(): HasOne
|
||||||
{
|
{
|
||||||
return $this->hasOne(UserProfile::class);
|
return $this->hasOne(UserProfile::class);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public function migrate(): array
|
|||||||
{
|
{
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
$results['cuttings'] = $this->migrateTable('cuttings');
|
$results['cuttings'] = $this->migrateTableWithoutColumns('cuttings', ['other_cost', 'sewing_cost', 'submitted_by_id']);
|
||||||
$results['cutting_material_combinations'] = $this->migrateTable('cutting_material_combinations');
|
$results['cutting_material_combinations'] = $this->migrateTable('cutting_material_combinations');
|
||||||
$results['cutting_materials'] = $this->migrateTable('cutting_materials', function ($row) {
|
$results['cutting_materials'] = $this->migrateTable('cutting_materials', function ($row) {
|
||||||
if (isset($row['material_usage']) && is_string($row['material_usage'])) {
|
if (isset($row['material_usage']) && is_string($row['material_usage'])) {
|
||||||
|
|||||||
@ -168,8 +168,6 @@ public function destroy(CashTransaction $transaction): bool
|
|||||||
Cache::forget("cash_transaction_receipt_{$media->id}");
|
Cache::forget("cash_transaction_receipt_{$media->id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$transaction->clearMediaCollection('photos');
|
|
||||||
|
|
||||||
$deleted = $transaction->delete();
|
$deleted = $transaction->delete();
|
||||||
|
|
||||||
if ($deleted) {
|
if ($deleted) {
|
||||||
|
|||||||
@ -130,8 +130,6 @@ public function destroy(Expense $expense): bool
|
|||||||
Cache::forget("expense_receipt_{$media->id}");
|
Cache::forget("expense_receipt_{$media->id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$expense->clearMediaCollection('photos');
|
|
||||||
|
|
||||||
$deleted = $expense->delete();
|
$deleted = $expense->delete();
|
||||||
|
|
||||||
if ($deleted) {
|
if ($deleted) {
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
use App\Services\Concerns\RegistersMedia;
|
use App\Services\Concerns\RegistersMedia;
|
||||||
use App\Services\S3PresignedService;
|
use App\Services\S3PresignedService;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
@ -21,16 +22,16 @@ public function __construct(
|
|||||||
private S3PresignedService $s3Service,
|
private S3PresignedService $s3Service,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$paginator = Cutting::query()
|
$paginator = Cutting::query()
|
||||||
->select(['id', 'created_by_id', 'status', 'description', 'total_material_cost', 'cost_per_unit', 'created_at'])
|
->select(['id', 'created_by_id', 'status', 'description', 'total_material_cost', 'cost_per_unit', 'created_at'])
|
||||||
->with([
|
->with([
|
||||||
'createdBy:id',
|
'createdBy:id',
|
||||||
'createdBy.userProfile:id,user_id,full_name',
|
'createdBy.userProfile:id,user_id,full_name',
|
||||||
'cuttingResults:id,cutting_id,product_name,cutting_result,sample,original_outside_sample',
|
'cuttingResults:id,cutting_id,product_name,cutting_result',
|
||||||
'cuttingMaterials:id,cutting_id,raw_material_price_id,material_usage,material_result,combination_id',
|
'cuttingMaterials:id,cutting_id,raw_material_price_id,material_usage,material_result,combination_id',
|
||||||
'cuttingMaterials.rawMaterialPrice:id,raw_material_id,variant,price,stock',
|
'cuttingMaterials.rawMaterialPrice:id,raw_material_id,variant',
|
||||||
'cuttingMaterials.rawMaterialPrice.rawMaterial:id,name,unit',
|
'cuttingMaterials.rawMaterialPrice.rawMaterial:id,name,unit',
|
||||||
'cuttingMaterialCombinations:id,cutting_id,material_result',
|
'cuttingMaterialCombinations:id,cutting_id,material_result',
|
||||||
])
|
])
|
||||||
@ -38,6 +39,8 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
$q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', 'like', "%{$search}%"))
|
$q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', 'like', "%{$search}%"))
|
||||||
->orWhere('description', 'like', "%{$search}%");
|
->orWhere('description', 'like', "%{$search}%");
|
||||||
})
|
})
|
||||||
|
->when($filters['product_name'] ?? null, fn ($q, $productName) => $q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', $productName)))
|
||||||
|
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
|
|
||||||
@ -66,6 +69,100 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
return $paginator;
|
return $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getProductNames(): Collection
|
||||||
|
{
|
||||||
|
return CuttingResult::query()
|
||||||
|
->select('product_name')
|
||||||
|
->whereNotNull('product_name')
|
||||||
|
->where('product_name', '!=', '')
|
||||||
|
->distinct()
|
||||||
|
->orderBy('product_name')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getForShow(Cutting $cutting): array
|
||||||
|
{
|
||||||
|
$cutting->load([
|
||||||
|
'createdBy:id',
|
||||||
|
'createdBy.userProfile:id,user_id,full_name',
|
||||||
|
'cuttingResults:id,cutting_id,product_name,cutting_result,sample,original_outside_sample',
|
||||||
|
'cuttingMaterials:id,cutting_id,raw_material_price_id,material_usage,material_result,combination_id',
|
||||||
|
'cuttingMaterials.rawMaterialPrice:id,raw_material_id,variant',
|
||||||
|
'cuttingMaterials.rawMaterialPrice.rawMaterial:id,name,unit',
|
||||||
|
'cuttingMaterialCombinations:id,cutting_id,material_result',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cuttingMedia = $cutting->getFirstMedia('images');
|
||||||
|
$cutting->photo_url = $cuttingMedia
|
||||||
|
? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath())
|
||||||
|
: null;
|
||||||
|
$cutting->photo_conversion_url = $cuttingMedia
|
||||||
|
? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath('thumb'))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
$cutting->cuttingMaterials->each(function (CuttingMaterial $material) {
|
||||||
|
$media = $material->rawMaterialPrice?->getFirstMedia('images');
|
||||||
|
if ($material->rawMaterialPrice) {
|
||||||
|
$material->rawMaterialPrice->photo_url = $media
|
||||||
|
? $this->s3Service->getTemporaryUrl($media->getPath())
|
||||||
|
: null;
|
||||||
|
$material->rawMaterialPrice->photo_conversion_url = $media
|
||||||
|
? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $cutting->id,
|
||||||
|
'status' => $cutting->status->value,
|
||||||
|
'status_label' => $cutting->status_label,
|
||||||
|
'description' => $cutting->description,
|
||||||
|
'total_material_cost' => $cutting->total_material_cost,
|
||||||
|
'formatted_total_material_cost' => $cutting->formatted_total_material_cost,
|
||||||
|
'cost_per_unit' => $cutting->cost_per_unit,
|
||||||
|
'formatted_cost_per_unit' => $cutting->formatted_cost_per_unit,
|
||||||
|
'created_at' => $cutting->created_at,
|
||||||
|
'formatted_created_at' => $cutting->formatted_created_at,
|
||||||
|
'photo_url' => $cutting->photo_url,
|
||||||
|
'photo_conversion_url' => $cutting->photo_conversion_url,
|
||||||
|
'created_by' => [
|
||||||
|
'id' => $cutting->createdBy->id,
|
||||||
|
'user_profile' => [
|
||||||
|
'full_name' => $cutting->createdBy->userProfile->full_name ?? '-',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'cutting_results' => $cutting->cuttingResults->map(fn ($r) => [
|
||||||
|
'id' => $r->id,
|
||||||
|
'product_name' => $r->product_name,
|
||||||
|
'cutting_result' => $r->cutting_result,
|
||||||
|
'sample' => $r->sample,
|
||||||
|
'original_outside_sample' => $r->original_outside_sample,
|
||||||
|
]),
|
||||||
|
'cutting_materials' => $cutting->cuttingMaterials->map(fn ($m) => [
|
||||||
|
'id' => $m->id,
|
||||||
|
'material_usage' => $m->material_usage,
|
||||||
|
'material_result' => $m->material_result,
|
||||||
|
'combination_id' => $m->combination_id,
|
||||||
|
'raw_material_price' => [
|
||||||
|
'id' => $m->rawMaterialPrice->id,
|
||||||
|
'variant' => $m->rawMaterialPrice->variant,
|
||||||
|
'price' => $m->rawMaterialPrice->price,
|
||||||
|
'photo_url' => $m->rawMaterialPrice->photo_url,
|
||||||
|
'photo_conversion_url' => $m->rawMaterialPrice->photo_conversion_url,
|
||||||
|
'raw_material' => [
|
||||||
|
'id' => $m->rawMaterialPrice->rawMaterial->id,
|
||||||
|
'name' => $m->rawMaterialPrice->rawMaterial->name,
|
||||||
|
'unit' => $m->rawMaterialPrice->rawMaterial->unit,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
'cutting_material_combinations' => $cutting->cuttingMaterialCombinations->map(fn ($c) => [
|
||||||
|
'id' => $c->id,
|
||||||
|
'material_result' => $c->material_result,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function getForEdit(Cutting $cutting): array
|
public function getForEdit(Cutting $cutting): array
|
||||||
{
|
{
|
||||||
$cutting->load([
|
$cutting->load([
|
||||||
@ -343,7 +440,6 @@ public function destroy(Cutting $cutting): bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$cutting->clearMediaCollection('images');
|
|
||||||
$cutting->cuttingResults()->delete();
|
$cutting->cuttingResults()->delete();
|
||||||
$cutting->cuttingMaterials()->delete();
|
$cutting->cuttingMaterials()->delete();
|
||||||
$cutting->cuttingMaterialCombinations()->delete();
|
$cutting->cuttingMaterialCombinations()->delete();
|
||||||
|
|||||||
@ -480,7 +480,6 @@ public function destroy(Purchase $purchase): bool
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$purchase->clearMediaCollection('photos');
|
|
||||||
$purchase->purchaseItems()->delete();
|
$purchase->purchaseItems()->delete();
|
||||||
$purchase->delete();
|
$purchase->delete();
|
||||||
|
|
||||||
|
|||||||
@ -144,7 +144,6 @@ public function destroy(Restock $restock): bool
|
|||||||
});
|
});
|
||||||
|
|
||||||
$restock->restockItems()->delete();
|
$restock->restockItems()->delete();
|
||||||
$restock->clearMediaCollection('photos');
|
|
||||||
$restock->delete();
|
$restock->delete();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -299,7 +299,6 @@ public function destroy(Order $order): bool
|
|||||||
});
|
});
|
||||||
|
|
||||||
$order->orderItems()->delete();
|
$order->orderItems()->delete();
|
||||||
$order->clearMediaCollection('photos');
|
|
||||||
$order->delete();
|
$order->delete();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -41,7 +41,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
'productVariants:id,product_id,name,stock,reject_stock,retail_stock',
|
'productVariants:id,product_id,name,stock,reject_stock,retail_stock',
|
||||||
'productVariants.productPrices:id,variant_id,type,price',
|
'productVariants.productPrices:id,variant_id,type,price',
|
||||||
])
|
])
|
||||||
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")
|
||||||
|
->orWhereHas('productVariants', fn ($vq) => $vq->where('name', 'like', "%{$search}%"))
|
||||||
|
)
|
||||||
->when($filters['name'] ?? null, fn ($q, $name) => $q->where('name', 'like', "%{$name}%"))
|
->when($filters['name'] ?? null, fn ($q, $name) => $q->where('name', 'like', "%{$name}%"))
|
||||||
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
|
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
|
||||||
->when($filters['category'] ?? null, fn ($q, $categoryId) => $q->whereHas('categories', function ($cq) use ($categoryId) {
|
->when($filters['category'] ?? null, fn ($q, $categoryId) => $q->whereHas('categories', function ($cq) use ($categoryId) {
|
||||||
@ -404,7 +406,6 @@ public function destroy(Product $product): bool
|
|||||||
$result = DB::transaction(function () use ($product) {
|
$result = DB::transaction(function () use ($product) {
|
||||||
$product->productVariants->each(function (ProductVariant $variant) {
|
$product->productVariants->each(function (ProductVariant $variant) {
|
||||||
$variant->productPrices()->delete();
|
$variant->productPrices()->delete();
|
||||||
$variant->clearMediaCollection('images');
|
|
||||||
$variant->delete();
|
$variant->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -156,7 +156,6 @@ public function destroy(Product $product, ProductVariant $variant): bool
|
|||||||
|
|
||||||
$result = DB::transaction(function () use ($variant) {
|
$result = DB::transaction(function () use ($variant) {
|
||||||
$variant->productPrices()->delete();
|
$variant->productPrices()->delete();
|
||||||
$variant->clearMediaCollection('images');
|
|
||||||
|
|
||||||
return $variant->delete();
|
return $variant->delete();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -32,7 +32,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->with([
|
->with([
|
||||||
'rawMaterialPrices:id,raw_material_id,variant,price,stock',
|
'rawMaterialPrices:id,raw_material_id,variant,price,stock',
|
||||||
])
|
])
|
||||||
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")
|
||||||
|
->orWhereHas('rawMaterialPrices', fn ($vq) => $vq->where('variant', 'like', "%{$search}%"))
|
||||||
|
)
|
||||||
->when($filters['name'] ?? null, fn ($q, $name) => $q->where('name', 'like', "%{$name}%"))
|
->when($filters['name'] ?? null, fn ($q, $name) => $q->where('name', 'like', "%{$name}%"))
|
||||||
->when(($filters['is_active'] ?? null) !== null && ($filters['is_active'] ?? null) !== '', function ($q) use ($filters) {
|
->when(($filters['is_active'] ?? null) !== null && ($filters['is_active'] ?? null) !== '', function ($q) use ($filters) {
|
||||||
$q->where('is_active', $filters['is_active'] === 'true');
|
$q->where('is_active', $filters['is_active'] === 'true');
|
||||||
@ -213,7 +215,6 @@ public function destroy(RawMaterial $rawMaterial): bool
|
|||||||
{
|
{
|
||||||
return DB::transaction(function () use ($rawMaterial) {
|
return DB::transaction(function () use ($rawMaterial) {
|
||||||
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
|
$rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
|
||||||
$price->clearMediaCollection('images');
|
|
||||||
$price->delete();
|
$price->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -93,8 +93,6 @@ public function update(RawMaterialPrice $variant, array $data): RawMaterialPrice
|
|||||||
public function destroy(RawMaterial $rawMaterial, RawMaterialPrice $variant): bool
|
public function destroy(RawMaterial $rawMaterial, RawMaterialPrice $variant): bool
|
||||||
{
|
{
|
||||||
$result = DB::transaction(function () use ($variant) {
|
$result = DB::transaction(function () use ($variant) {
|
||||||
$variant->clearMediaCollection('images');
|
|
||||||
|
|
||||||
return $variant->delete();
|
return $variant->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.24",
|
"fakerphp/faker": "^1.24",
|
||||||
"larastan/larastan": "^3.9",
|
"larastan/larastan": "^3.9",
|
||||||
|
"laravel-lang/lang": "^15.34",
|
||||||
"laravel/pail": "^1.2.5",
|
"laravel/pail": "^1.2.5",
|
||||||
"laravel/pao": "^1.0.6",
|
"laravel/pao": "^1.0.6",
|
||||||
"laravel/pint": "^1.27",
|
"laravel/pint": "^1.27",
|
||||||
|
|||||||
915
composer.lock
generated
915
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "b8312d5622d94884c0607ebccdfd2ed8",
|
"content-hash": "c8587897f88cd85097351d4f9acdd0bd",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
@ -9212,6 +9212,59 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
|
{
|
||||||
|
"name": "archtechx/enums",
|
||||||
|
"version": "v1.1.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/archtechx/enums.git",
|
||||||
|
"reference": "81375b71c176f680880a95e7448d84258cfb5c72"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/archtechx/enums/zipball/81375b71c176f680880a95e7448d84258cfb5c72",
|
||||||
|
"reference": "81375b71c176f680880a95e7448d84258cfb5c72",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"larastan/larastan": "^2.4",
|
||||||
|
"orchestra/testbench": "^8.0 || ^9.0",
|
||||||
|
"pestphp/pest": "^2.0",
|
||||||
|
"pestphp/pest-plugin-laravel": "^2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"ArchTech\\Enums\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Samuel Štancl",
|
||||||
|
"email": "samuel@archte.ch"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Helpers for making PHP enums more lovable.",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/archtechx/enums/issues",
|
||||||
|
"source": "https://github.com/archtechx/enums/tree/v1.1.2"
|
||||||
|
},
|
||||||
|
"time": "2025-06-06T23:15:09+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "brianium/paratest",
|
"name": "brianium/paratest",
|
||||||
"version": "v7.20.0",
|
"version": "v7.20.0",
|
||||||
@ -9447,6 +9500,233 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-05-06T16:37:16+00:00"
|
"time": "2024-05-06T16:37:16+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "dragon-code/contracts",
|
||||||
|
"version": "2.25.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/TheDragonCode/contracts.git",
|
||||||
|
"reference": "13d1254801026be5ba33cf1309a414953869175f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/TheDragonCode/contracts/zipball/13d1254801026be5ba33cf1309a414953869175f",
|
||||||
|
"reference": "13d1254801026be5ba33cf1309a414953869175f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"psr/http-message": "^1.0.1 || ^2.0",
|
||||||
|
"symfony/http-kernel": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/polyfill-php80": "^1.23"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"andrey-helldar/contracts": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/database": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phpdocumentor/reflection-docblock": "^5.0 || ^6.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"DragonCode\\Contracts\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro",
|
||||||
|
"homepage": "https://dragon-code.pro"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A set of contracts for any project",
|
||||||
|
"keywords": [
|
||||||
|
"contracts",
|
||||||
|
"interfaces"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/TheDragonCode/contracts"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-03-17T21:50:20+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dragon-code/pretty-array",
|
||||||
|
"version": "4.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/TheDragonCode/pretty-array.git",
|
||||||
|
"reference": "b94034d92172a5d14a578822d68b2a8f8b5388e0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/TheDragonCode/pretty-array/zipball/b94034d92172a5d14a578822d68b2a8f8b5388e0",
|
||||||
|
"reference": "b94034d92172a5d14a578822d68b2a8f8b5388e0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"dragon-code/contracts": "^2.20",
|
||||||
|
"dragon-code/support": "^6.11.2",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^10.0 || ^11.0 || ^12.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"DragonCode\\PrettyArray\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro",
|
||||||
|
"homepage": "https://dragon-code.pro"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Simple conversion of an array to a pretty view",
|
||||||
|
"keywords": [
|
||||||
|
"andrey helldar",
|
||||||
|
"array",
|
||||||
|
"dragon",
|
||||||
|
"dragon code",
|
||||||
|
"pretty",
|
||||||
|
"pretty array"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/TheDragonCode/pretty-array/issues",
|
||||||
|
"source": "https://github.com/TheDragonCode/pretty-array"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-24T15:35:24+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dragon-code/support",
|
||||||
|
"version": "6.17.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/TheDragonCode/support.git",
|
||||||
|
"reference": "82a465953267989883d64b921e9725600a5073b5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/TheDragonCode/support/zipball/82a465953267989883d64b921e9725600a5073b5",
|
||||||
|
"reference": "82a465953267989883d64b921e9725600a5073b5",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"dragon-code/contracts": "^2.22.0",
|
||||||
|
"ext-bcmath": "*",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/http-message": "^1.0.1 || ^2.0",
|
||||||
|
"voku/portable-ascii": "^1.4.8 || ^2.0.1"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"andrey-helldar/support": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/contracts": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"phpunit/phpunit": "^9.6 || ^11.0 || ^12.0",
|
||||||
|
"symfony/var-dumper": "^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"dragon-code/laravel-support": "Various helper files for the Laravel and Lumen frameworks",
|
||||||
|
"symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"dragon-code": {
|
||||||
|
"docs-generator": {
|
||||||
|
"preview": {
|
||||||
|
"brand": "php",
|
||||||
|
"vendor": "The Dragon Code"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"DragonCode\\Support\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro",
|
||||||
|
"homepage": "https://dragon-code.pro"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Support package is a collection of helpers and tools for any project.",
|
||||||
|
"keywords": [
|
||||||
|
"dragon",
|
||||||
|
"dragon-code",
|
||||||
|
"framework",
|
||||||
|
"helper",
|
||||||
|
"helpers",
|
||||||
|
"laravel",
|
||||||
|
"php",
|
||||||
|
"support",
|
||||||
|
"symfony",
|
||||||
|
"yii",
|
||||||
|
"yii2"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/TheDragonCode/support/issues",
|
||||||
|
"source": "https://github.com/TheDragonCode/support"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-03T15:06:29+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "fakerphp/faker",
|
"name": "fakerphp/faker",
|
||||||
"version": "v1.24.1",
|
"version": "v1.24.1",
|
||||||
@ -9884,6 +10164,639 @@
|
|||||||
],
|
],
|
||||||
"time": "2026-05-28T08:00:58+00:00"
|
"time": "2026-05-28T08:00:58+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/config",
|
||||||
|
"version": "1.17.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/config.git",
|
||||||
|
"reference": "77ad089234aa74961ca30c7e6d13db9a62654c87"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/config/zipball/77ad089234aa74961ca30c7e6d13db9a62654c87",
|
||||||
|
"reference": "77ad089234aa74961ca30c7e6d13db9a62654c87",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"archtechx/enums": "^1.0",
|
||||||
|
"illuminate/config": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"laravel-lang/locale-list": "^1.6",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^8.23 || ^9.1 || ^10.0 || ^11.0",
|
||||||
|
"pestphp/pest": "^2.34 || ^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"LaravelLang\\Config\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\Config\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro",
|
||||||
|
"homepage": "https://dragon-code.pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://laravel-lang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The Laravel-Lang config package",
|
||||||
|
"keywords": [
|
||||||
|
"Laravel-lang",
|
||||||
|
"Settings",
|
||||||
|
"config",
|
||||||
|
"lang",
|
||||||
|
"languages",
|
||||||
|
"laravel",
|
||||||
|
"locale",
|
||||||
|
"locales",
|
||||||
|
"localization",
|
||||||
|
"localizations",
|
||||||
|
"translation",
|
||||||
|
"translations"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/config/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/config/tree/1.17.0"
|
||||||
|
},
|
||||||
|
"time": "2026-03-17T21:23:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/lang",
|
||||||
|
"version": "15.34.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/lang.git",
|
||||||
|
"reference": "3149a2cea69aa16469ebf44bcb58f3d25920a620"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/3149a2cea69aa16469ebf44bcb58f3d25920a620",
|
||||||
|
"reference": "3149a2cea69aa16469ebf44bcb58f3d25920a620",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"laravel-lang/publisher": "^16.0",
|
||||||
|
"php": "^8.2"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"laravel/framework": "<11.0.7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dragon-code/codestyler": "^6.0",
|
||||||
|
"laravel-lang/status-generator": "^2.11",
|
||||||
|
"phpunit/phpunit": "^11.0 || ^12.0",
|
||||||
|
"symfony/var-dumper": "^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"LaravelLang\\Lang\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\Lang\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://github.com/Laravel-Lang"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List of 126 languages for Laravel Framework, Laravel Jetstream, Laravel Fortify, Laravel Breeze, Laravel Cashier, Laravel Nova, Laravel Spark and Laravel UI",
|
||||||
|
"keywords": [
|
||||||
|
"lang",
|
||||||
|
"languages",
|
||||||
|
"laravel",
|
||||||
|
"lpm"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/lang/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/lang"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-08-11T18:30:36+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/locale-list",
|
||||||
|
"version": "1.7.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/locale-list.git",
|
||||||
|
"reference": "48e61c7f0a957420d4aaf5d35653889c25c4e2d4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/locale-list/zipball/48e61c7f0a957420d4aaf5d35653889c25c4e2d4",
|
||||||
|
"reference": "48e61c7f0a957420d4aaf5d35653889c25c4e2d4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"archtechx/enums": "^0.3.2 || ^1.0",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\LocaleList\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro",
|
||||||
|
"homepage": "https://dragon-code.pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://laravel-lang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List of localizations available in Laravel Lang projects",
|
||||||
|
"keywords": [
|
||||||
|
"Laravel-lang",
|
||||||
|
"lang",
|
||||||
|
"languages",
|
||||||
|
"laravel",
|
||||||
|
"locale",
|
||||||
|
"locales",
|
||||||
|
"localization",
|
||||||
|
"translation",
|
||||||
|
"translations"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/locale-list/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/locale-list"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-01-20T08:17:15+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/locales",
|
||||||
|
"version": "2.11.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/locales.git",
|
||||||
|
"reference": "761aa3cfbc5bbe29eb958c9839e7dd3806193bac"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/locales/zipball/761aa3cfbc5bbe29eb958c9839e7dd3806193bac",
|
||||||
|
"reference": "761aa3cfbc5bbe29eb958c9839e7dd3806193bac",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"archtechx/enums": "^0.3.2 || ^1.0",
|
||||||
|
"dragon-code/support": "^6.11.3",
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"laravel-lang/config": "^1.12",
|
||||||
|
"laravel-lang/locale-list": "^1.5",
|
||||||
|
"laravel-lang/native-country-names": "^1.5",
|
||||||
|
"laravel-lang/native-currency-names": "^1.6",
|
||||||
|
"laravel-lang/native-locale-names": "^2.5",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^8.0 || ^9.0 || ^10.0 || ^11.0",
|
||||||
|
"pestphp/pest": "^2.24.1 || ^3.0 || ^4.0",
|
||||||
|
"symfony/var-dumper": "^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"LaravelLang\\Locales\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\Locales\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://laravel-lang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Basic functionality for working with localizations",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"locale",
|
||||||
|
"locales",
|
||||||
|
"localization",
|
||||||
|
"translation",
|
||||||
|
"translations"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/locales/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/locales"
|
||||||
|
},
|
||||||
|
"time": "2026-03-17T22:40:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/native-country-names",
|
||||||
|
"version": "1.8.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/native-country-names.git",
|
||||||
|
"reference": "1d293138e34eb9e914bc4568cdebac2cb0a2eb0e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/native-country-names/zipball/1d293138e34eb9e914bc4568cdebac2cb0a2eb0e",
|
||||||
|
"reference": "1d293138e34eb9e914bc4568cdebac2cb0a2eb0e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"dragon-code/support": "^6.11",
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"laravel-lang/locale-list": "^1.5",
|
||||||
|
"pestphp/pest": "^2.0 || ^3.0 || ^4.0",
|
||||||
|
"punic/punic": "^3.8",
|
||||||
|
"symfony/console": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/process": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"symfony/var-dumper": "^6.0 || ^7.0 || ^8.0",
|
||||||
|
"vlucas/phpdotenv": "^5.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\NativeCountryNames\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://laravel-lang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The project contains native translations of country names",
|
||||||
|
"keywords": [
|
||||||
|
"Laravel-lang",
|
||||||
|
"countries",
|
||||||
|
"country",
|
||||||
|
"lang",
|
||||||
|
"languages",
|
||||||
|
"laravel",
|
||||||
|
"locale",
|
||||||
|
"locales",
|
||||||
|
"localization",
|
||||||
|
"territories",
|
||||||
|
"territory",
|
||||||
|
"translation",
|
||||||
|
"translations"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/native-country-names/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/native-country-names"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-03-17T22:01:15+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/native-currency-names",
|
||||||
|
"version": "1.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/native-currency-names.git",
|
||||||
|
"reference": "7ebe95a9942bebf6afd61986fa18c82dade3d583"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/native-currency-names/zipball/7ebe95a9942bebf6afd61986fa18c82dade3d583",
|
||||||
|
"reference": "7ebe95a9942bebf6afd61986fa18c82dade3d583",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"dragon-code/support": "^6.11",
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"laravel-lang/locale-list": "^1.2",
|
||||||
|
"pestphp/pest": "^2.0 || ^3.0 || ^4.0",
|
||||||
|
"punic/punic": "^3.8",
|
||||||
|
"symfony/console": "^6.0 || ^7.0",
|
||||||
|
"symfony/process": "^6.0 || ^7.0",
|
||||||
|
"symfony/var-dumper": "^6.0 || ^7.0",
|
||||||
|
"vlucas/phpdotenv": "^5.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\NativeCurrencyNames\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://laravel-lang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The project contains native translations of currency names",
|
||||||
|
"keywords": [
|
||||||
|
"Laravel-lang",
|
||||||
|
"currency",
|
||||||
|
"lang",
|
||||||
|
"languages",
|
||||||
|
"laravel",
|
||||||
|
"locale",
|
||||||
|
"locales",
|
||||||
|
"localization",
|
||||||
|
"translation",
|
||||||
|
"translations"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/native-currency-names/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/native-currency-names"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-06-20T16:50:37+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/native-locale-names",
|
||||||
|
"version": "2.9.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/native-locale-names.git",
|
||||||
|
"reference": "e5925182bad34654203a5d93544357ca00a6d9c0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/native-locale-names/zipball/e5925182bad34654203a5d93544357ca00a6d9c0",
|
||||||
|
"reference": "e5925182bad34654203a5d93544357ca00a6d9c0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"dragon-code/support": "^6.11",
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"laravel-lang/locale-list": "^1.2",
|
||||||
|
"pestphp/pest": "^2.24.3 || ^3.0 || ^4.0",
|
||||||
|
"punic/punic": "^3.8",
|
||||||
|
"symfony/console": "^6.0 || ^7.0",
|
||||||
|
"symfony/process": "^6.0 || ^7.0",
|
||||||
|
"symfony/var-dumper": "^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\NativeLocaleNames\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://laravel-lang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The project contains native translations of locale names",
|
||||||
|
"keywords": [
|
||||||
|
"Laravel-lang",
|
||||||
|
"lang",
|
||||||
|
"languages",
|
||||||
|
"laravel",
|
||||||
|
"locale",
|
||||||
|
"locales",
|
||||||
|
"localization",
|
||||||
|
"translation",
|
||||||
|
"translations"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/native-locale-names/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/native-locale-names"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-06-20T16:52:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel-lang/publisher",
|
||||||
|
"version": "16.8.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Laravel-Lang/publisher.git",
|
||||||
|
"reference": "e5d3383f5385c2102f8a0d3dbe488ed86cd0250f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Laravel-Lang/publisher/zipball/e5d3383f5385c2102f8a0d3dbe488ed86cd0250f",
|
||||||
|
"reference": "e5d3383f5385c2102f8a0d3dbe488ed86cd0250f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer/semver": "^3.4",
|
||||||
|
"dragon-code/pretty-array": "^4.1",
|
||||||
|
"dragon-code/support": "^6.11.3",
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"illuminate/console": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0",
|
||||||
|
"laravel-lang/config": "^1.12",
|
||||||
|
"laravel-lang/locales": "^2.10",
|
||||||
|
"league/commonmark": "^2.4.1",
|
||||||
|
"league/config": "^1.2",
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"laravel-lang/attributes": "<2.0",
|
||||||
|
"laravel-lang/http-statuses": "<3.0",
|
||||||
|
"laravel-lang/lang": "<11.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel-lang/json-fallback": "^2.2",
|
||||||
|
"orchestra/testbench": "^8.14 || ^9.0 || ^10.0 || ^11.0",
|
||||||
|
"phpunit/phpunit": "^10.4.2 || ^11.0 || ^12.0",
|
||||||
|
"symfony/var-dumper": "^6.3.6 || ^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"LaravelLang\\Publisher\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelLang\\Publisher\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrey Helldar",
|
||||||
|
"email": "helldar@dragon-code.pro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Laravel-Lang Team",
|
||||||
|
"homepage": "https://laravel-lang.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Localization publisher for your Laravel application",
|
||||||
|
"keywords": [
|
||||||
|
"Laravel-lang",
|
||||||
|
"breeze",
|
||||||
|
"cashier",
|
||||||
|
"fortify",
|
||||||
|
"framework",
|
||||||
|
"i18n",
|
||||||
|
"jetstream",
|
||||||
|
"lang",
|
||||||
|
"languages",
|
||||||
|
"laravel",
|
||||||
|
"locale",
|
||||||
|
"locales",
|
||||||
|
"localization",
|
||||||
|
"localizations",
|
||||||
|
"lpm",
|
||||||
|
"lumen",
|
||||||
|
"nova",
|
||||||
|
"publisher",
|
||||||
|
"spark",
|
||||||
|
"trans",
|
||||||
|
"translation",
|
||||||
|
"translations",
|
||||||
|
"validations"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Laravel-Lang/publisher/issues",
|
||||||
|
"source": "https://github.com/Laravel-Lang/publisher"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://boosty.to/dragon-code",
|
||||||
|
"type": "boosty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://yoomoney.ru/to/410012608840929",
|
||||||
|
"type": "yoomoney"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-03-17T22:56:20+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/agent-detector",
|
"name": "laravel/agent-detector",
|
||||||
"version": "v2.0.2",
|
"version": "v2.0.2",
|
||||||
|
|||||||
@ -13,14 +13,11 @@ public function up(): void
|
|||||||
$table->id();
|
$table->id();
|
||||||
|
|
||||||
$table->foreignId('created_by_id')->constrained('users')->restrictOnDelete();
|
$table->foreignId('created_by_id')->constrained('users')->restrictOnDelete();
|
||||||
$table->foreignId('submitted_by_id')->nullable()->constrained('users')->nullOnDelete();
|
|
||||||
|
|
||||||
$table->enum('status', CuttingStatus::values())->default(CuttingStatus::IN_PROGRESS->value);
|
$table->enum('status', CuttingStatus::values())->default(CuttingStatus::IN_PROGRESS->value);
|
||||||
$table->string('description', 100)->nullable();
|
$table->string('description', 100)->nullable();
|
||||||
|
|
||||||
$table->unsignedBigInteger('total_material_cost')->nullable();
|
$table->unsignedBigInteger('total_material_cost')->nullable();
|
||||||
$table->unsignedBigInteger('sewing_cost')->default(0);
|
|
||||||
$table->unsignedBigInteger('other_cost')->default(0);
|
|
||||||
$table->unsignedBigInteger('cost_per_unit')->nullable();
|
$table->unsignedBigInteger('cost_per_unit')->nullable();
|
||||||
|
|
||||||
$table->timestamp('created_at')->useCurrent();
|
$table->timestamp('created_at')->useCurrent();
|
||||||
|
|||||||
69
lang/en.json
Normal file
69
lang/en.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"(and :count more error)": "(and :count more error)",
|
||||||
|
"(and :count more errors)": "(and :count more error)|(and :count more errors)|(and :count more errors)",
|
||||||
|
"A decryption key is required.": "A decryption key is required.",
|
||||||
|
"All rights reserved.": "All rights reserved.",
|
||||||
|
"Encrypted environment file already exists.": "Encrypted environment file already exists.",
|
||||||
|
"Encrypted environment file not found.": "Encrypted environment file not found.",
|
||||||
|
"Environment file already exists.": "Environment file already exists.",
|
||||||
|
"Environment file not found.": "Environment file not found.",
|
||||||
|
"errors": "errors",
|
||||||
|
"Forbidden": "Forbidden",
|
||||||
|
"Go to page :page": "Go to page :page",
|
||||||
|
"Hello!": "Hello!",
|
||||||
|
"If you did not create an account, no further action is required.": "If you did not create an account, no further action is required.",
|
||||||
|
"If you did not request a password reset, no further action is required.": "If you did not request a password reset, no further action is required.",
|
||||||
|
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:",
|
||||||
|
"Invalid credential format.": "Invalid credential format.",
|
||||||
|
"Invalid filename.": "Invalid filename.",
|
||||||
|
"Invalid JSON was returned from the route.": "Invalid JSON was returned from the route.",
|
||||||
|
"Location": "Location",
|
||||||
|
"Login": "Login",
|
||||||
|
"Logout": "Logout",
|
||||||
|
"Not Found": "Not Found",
|
||||||
|
"of": "of",
|
||||||
|
"Page Expired": "Page Expired",
|
||||||
|
"Pagination Navigation": "Pagination Navigation",
|
||||||
|
"Passkey not recognized. It may have been removed from your account.": "Passkey not recognized. It may have been removed from your account.",
|
||||||
|
"Passkey registration session expired. Please try again.": "Passkey registration session expired. Please try again.",
|
||||||
|
"Passkey verification session expired. Please try again.": "Passkey verification session expired. Please try again.",
|
||||||
|
"Payment Required": "Payment Required",
|
||||||
|
"Please click the button below to verify your email address.": "Please click the button below to verify your email address.",
|
||||||
|
"Regards,": "Regards,",
|
||||||
|
"Register": "Register",
|
||||||
|
"Reset Password": "Reset Password",
|
||||||
|
"Reset your password": "Reset your password",
|
||||||
|
"results": "results",
|
||||||
|
"Server Error": "Server Error",
|
||||||
|
"Service Unavailable": "Service Unavailable",
|
||||||
|
"Showing": "Showing",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one number.": "The :attribute must be at least :length characters and contain at least one number.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one special character and one number.": "The :attribute must be at least :length characters and contain at least one special character and one number.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one special character.": "The :attribute must be at least :length characters and contain at least one special character.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character and one number.": "The :attribute must be at least :length characters and contain at least one uppercase character and one number.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character and one special character.": "The :attribute must be at least :length characters and contain at least one uppercase character and one special character.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character, one number, and one special character.": "The :attribute must be at least :length characters and contain at least one uppercase character, one number, and one special character.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character.": "The :attribute must be at least :length characters and contain at least one uppercase character.",
|
||||||
|
"The :attribute must be at least :length characters.": "The :attribute must be at least :length characters.",
|
||||||
|
"The given data was invalid.": "The given data was invalid.",
|
||||||
|
"The provided password does not match your current password.": "The provided password does not match your current password.",
|
||||||
|
"The provided password was incorrect.": "The provided password was incorrect.",
|
||||||
|
"The provided two factor authentication code was invalid.": "The provided two factor authentication code was invalid.",
|
||||||
|
"The provided two factor recovery code was invalid.": "The provided two factor recovery code was invalid.",
|
||||||
|
"The response is not a streamed response.": "The response is not a streamed response.",
|
||||||
|
"The response is not a view.": "The response is not a view.",
|
||||||
|
"This action is unauthorized.": "This action is unauthorized.",
|
||||||
|
"This password reset link will expire in :count minutes.": "This password reset link will expire in :count minutes.",
|
||||||
|
"to": "to",
|
||||||
|
"Toggle navigation": "Toggle navigation",
|
||||||
|
"Too Many Requests": "Too Many Requests",
|
||||||
|
"Unable to register passkey. Please try again.": "Unable to register passkey. Please try again.",
|
||||||
|
"Unable to register this passkey.": "Unable to register this passkey.",
|
||||||
|
"Unable to sign in with this account.": "Unable to sign in with this account.",
|
||||||
|
"Unable to verify passkey. Please try again.": "Unable to verify passkey. Please try again.",
|
||||||
|
"Unauthorized": "Unauthorized",
|
||||||
|
"Verify Email Address": "Verify Email Address",
|
||||||
|
"Verify your email address": "Verify your email address",
|
||||||
|
"Whoops!": "Whoops!",
|
||||||
|
"You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account."
|
||||||
|
}
|
||||||
9
lang/en/auth.php
Normal file
9
lang/en/auth.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'failed' => 'These credentials do not match our records.',
|
||||||
|
'password' => 'The provided password is incorrect.',
|
||||||
|
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
|
||||||
|
];
|
||||||
8
lang/en/pagination.php
Normal file
8
lang/en/pagination.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'next' => 'Next »',
|
||||||
|
'previous' => '« Previous',
|
||||||
|
];
|
||||||
11
lang/en/passwords.php
Normal file
11
lang/en/passwords.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'reset' => 'Your password has been reset.',
|
||||||
|
'sent' => 'We have emailed your password reset link.',
|
||||||
|
'throttled' => 'Please wait before retrying.',
|
||||||
|
'token' => 'This password reset token is invalid.',
|
||||||
|
'user' => 'We can\'t find a user with that email address.',
|
||||||
|
];
|
||||||
161
lang/en/validation.php
Normal file
161
lang/en/validation.php
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'accepted' => 'The :attribute field must be accepted.',
|
||||||
|
'accepted_if' => 'The :attribute field must be accepted when :other is :value.',
|
||||||
|
'active_url' => 'The :attribute field must be a valid URL.',
|
||||||
|
'after' => 'The :attribute field must be a date after :date.',
|
||||||
|
'after_or_equal' => 'The :attribute field must be a date after or equal to :date.',
|
||||||
|
'alpha' => 'The :attribute field must only contain letters.',
|
||||||
|
'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.',
|
||||||
|
'alpha_num' => 'The :attribute field must only contain letters and numbers.',
|
||||||
|
'any_of' => 'The :attribute field is invalid.',
|
||||||
|
'array' => 'The :attribute field must be an array.',
|
||||||
|
'array_keys' => 'The :attribute field must only contain the following keys: :values.',
|
||||||
|
'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.',
|
||||||
|
'base64' => 'The :attribute field must be a valid Base64 string.',
|
||||||
|
'before' => 'The :attribute field must be a date before :date.',
|
||||||
|
'before_or_equal' => 'The :attribute field must be a date before or equal to :date.',
|
||||||
|
'between' => [
|
||||||
|
'array' => 'The :attribute field must have between :min and :max items.',
|
||||||
|
'file' => 'The :attribute field must be between :min and :max kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must be between :min and :max.',
|
||||||
|
'string' => 'The :attribute field must be between :min and :max characters.',
|
||||||
|
],
|
||||||
|
'boolean' => 'The :attribute field must be true or false.',
|
||||||
|
'can' => 'The :attribute field contains an unauthorized value.',
|
||||||
|
'confirmed' => 'The :attribute field confirmation does not match.',
|
||||||
|
'contains' => 'The :attribute field is missing a required value.',
|
||||||
|
'current_password' => 'The password is incorrect.',
|
||||||
|
'date' => 'The :attribute field must be a valid date.',
|
||||||
|
'date_equals' => 'The :attribute field must be a date equal to :date.',
|
||||||
|
'date_format' => 'The :attribute field must match the format :format.',
|
||||||
|
'decimal' => 'The :attribute field must have :decimal decimal places.',
|
||||||
|
'declined' => 'The :attribute field must be declined.',
|
||||||
|
'declined_if' => 'The :attribute field must be declined when :other is :value.',
|
||||||
|
'different' => 'The :attribute field and :other must be different.',
|
||||||
|
'digits' => 'The :attribute field must be :digits digits.',
|
||||||
|
'digits_between' => 'The :attribute field must be between :min and :max digits.',
|
||||||
|
'dimensions' => 'The :attribute field has invalid image dimensions.',
|
||||||
|
'distinct' => 'The :attribute field has a duplicate value.',
|
||||||
|
'doesnt_contain' => 'The :attribute field must not contain any of the following: :values.',
|
||||||
|
'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.',
|
||||||
|
'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.',
|
||||||
|
'email' => 'The :attribute field must be a valid email address.',
|
||||||
|
'encoding' => 'The :attribute field must be encoded in :encoding.',
|
||||||
|
'ends_with' => 'The :attribute field must end with one of the following: :values.',
|
||||||
|
'enum' => 'The selected :attribute is invalid.',
|
||||||
|
'exists' => 'The selected :attribute is invalid.',
|
||||||
|
'extensions' => 'The :attribute field must have one of the following extensions: :values.',
|
||||||
|
'file' => 'The :attribute field must be a file.',
|
||||||
|
'filled' => 'The :attribute field must have a value.',
|
||||||
|
'gt' => [
|
||||||
|
'array' => 'The :attribute field must have more than :value items.',
|
||||||
|
'file' => 'The :attribute field must be greater than :value kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must be greater than :value.',
|
||||||
|
'string' => 'The :attribute field must be greater than :value characters.',
|
||||||
|
],
|
||||||
|
'gte' => [
|
||||||
|
'array' => 'The :attribute field must have :value items or more.',
|
||||||
|
'file' => 'The :attribute field must be greater than or equal to :value kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must be greater than or equal to :value.',
|
||||||
|
'string' => 'The :attribute field must be greater than or equal to :value characters.',
|
||||||
|
],
|
||||||
|
'hex_color' => 'The :attribute field must be a valid hexadecimal color.',
|
||||||
|
'image' => 'The :attribute field must be an image.',
|
||||||
|
'in' => 'The selected :attribute is invalid.',
|
||||||
|
'in_array' => 'The :attribute field must exist in :other.',
|
||||||
|
'in_array_keys' => 'The :attribute field must contain at least one of the following keys: :values.',
|
||||||
|
'integer' => 'The :attribute field must be an integer.',
|
||||||
|
'ip' => 'The :attribute field must be a valid IP address.',
|
||||||
|
'ipv4' => 'The :attribute field must be a valid IPv4 address.',
|
||||||
|
'ipv6' => 'The :attribute field must be a valid IPv6 address.',
|
||||||
|
'json' => 'The :attribute field must be a valid JSON string.',
|
||||||
|
'list' => 'The :attribute field must be a list.',
|
||||||
|
'lowercase' => 'The :attribute field must be lowercase.',
|
||||||
|
'lt' => [
|
||||||
|
'array' => 'The :attribute field must have less than :value items.',
|
||||||
|
'file' => 'The :attribute field must be less than :value kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must be less than :value.',
|
||||||
|
'string' => 'The :attribute field must be less than :value characters.',
|
||||||
|
],
|
||||||
|
'lte' => [
|
||||||
|
'array' => 'The :attribute field must not have more than :value items.',
|
||||||
|
'file' => 'The :attribute field must be less than or equal to :value kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must be less than or equal to :value.',
|
||||||
|
'string' => 'The :attribute field must be less than or equal to :value characters.',
|
||||||
|
],
|
||||||
|
'mac_address' => 'The :attribute field must be a valid MAC address.',
|
||||||
|
'max' => [
|
||||||
|
'array' => 'The :attribute field must not have more than :max items.',
|
||||||
|
'file' => 'The :attribute field must not be greater than :max kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must not be greater than :max.',
|
||||||
|
'string' => 'The :attribute field must not be greater than :max characters.',
|
||||||
|
],
|
||||||
|
'max_digits' => 'The :attribute field must not have more than :max digits.',
|
||||||
|
'mimes' => 'The :attribute field must be a file of type: :values.',
|
||||||
|
'mimetypes' => 'The :attribute field must be a file of type: :values.',
|
||||||
|
'min' => [
|
||||||
|
'array' => 'The :attribute field must have at least :min items.',
|
||||||
|
'file' => 'The :attribute field must be at least :min kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must be at least :min.',
|
||||||
|
'string' => 'The :attribute field must be at least :min characters.',
|
||||||
|
],
|
||||||
|
'min_digits' => 'The :attribute field must have at least :min digits.',
|
||||||
|
'missing' => 'The :attribute field must be missing.',
|
||||||
|
'missing_if' => 'The :attribute field must be missing when :other is :value.',
|
||||||
|
'missing_unless' => 'The :attribute field must be missing unless :other is :value.',
|
||||||
|
'missing_with' => 'The :attribute field must be missing when :values is present.',
|
||||||
|
'missing_with_all' => 'The :attribute field must be missing when :values are present.',
|
||||||
|
'multiple_of' => 'The :attribute field must be a multiple of :value.',
|
||||||
|
'not_in' => 'The selected :attribute is invalid.',
|
||||||
|
'not_regex' => 'The :attribute field format is invalid.',
|
||||||
|
'numeric' => 'The :attribute field must be a number.',
|
||||||
|
'password' => [
|
||||||
|
'letters' => 'The :attribute field must contain at least one letter.',
|
||||||
|
'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.',
|
||||||
|
'numbers' => 'The :attribute field must contain at least one number.',
|
||||||
|
'symbols' => 'The :attribute field must contain at least one symbol.',
|
||||||
|
'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
|
||||||
|
],
|
||||||
|
'present' => 'The :attribute field must be present.',
|
||||||
|
'present_if' => 'The :attribute field must be present when :other is :value.',
|
||||||
|
'present_unless' => 'The :attribute field must be present unless :other is :value.',
|
||||||
|
'present_with' => 'The :attribute field must be present when :values is present.',
|
||||||
|
'present_with_all' => 'The :attribute field must be present when :values are present.',
|
||||||
|
'prohibited' => 'The :attribute field is prohibited.',
|
||||||
|
'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
|
||||||
|
'prohibited_if_accepted' => 'The :attribute field is prohibited when :other is accepted.',
|
||||||
|
'prohibited_if_declined' => 'The :attribute field is prohibited when :other is declined.',
|
||||||
|
'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
|
||||||
|
'prohibits' => 'The :attribute field prohibits :other from being present.',
|
||||||
|
'regex' => 'The :attribute field format is invalid.',
|
||||||
|
'required' => 'The :attribute field is required.',
|
||||||
|
'required_array_keys' => 'The :attribute field must contain entries for: :values.',
|
||||||
|
'required_if' => 'The :attribute field is required when :other is :value.',
|
||||||
|
'required_if_accepted' => 'The :attribute field is required when :other is accepted.',
|
||||||
|
'required_if_declined' => 'The :attribute field is required when :other is declined.',
|
||||||
|
'required_unless' => 'The :attribute field is required unless :other is in :values.',
|
||||||
|
'required_with' => 'The :attribute field is required when :values is present.',
|
||||||
|
'required_with_all' => 'The :attribute field is required when :values are present.',
|
||||||
|
'required_without' => 'The :attribute field is required when :values is not present.',
|
||||||
|
'required_without_all' => 'The :attribute field is required when none of :values are present.',
|
||||||
|
'same' => 'The :attribute field must match :other.',
|
||||||
|
'size' => [
|
||||||
|
'array' => 'The :attribute field must contain :size items.',
|
||||||
|
'file' => 'The :attribute field must be :size kilobytes.',
|
||||||
|
'numeric' => 'The :attribute field must be :size.',
|
||||||
|
'string' => 'The :attribute field must be :size characters.',
|
||||||
|
],
|
||||||
|
'starts_with' => 'The :attribute field must start with one of the following: :values.',
|
||||||
|
'string' => 'The :attribute field must be a string.',
|
||||||
|
'timezone' => 'The :attribute field must be a valid timezone.',
|
||||||
|
'ulid' => 'The :attribute field must be a valid ULID.',
|
||||||
|
'unique' => 'The :attribute has already been taken.',
|
||||||
|
'uploaded' => 'The :attribute failed to upload.',
|
||||||
|
'uppercase' => 'The :attribute field must be uppercase.',
|
||||||
|
'url' => 'The :attribute field must be a valid URL.',
|
||||||
|
'uuid' => 'The :attribute field must be a valid UUID.',
|
||||||
|
];
|
||||||
69
lang/id.json
Normal file
69
lang/id.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"(and :count more error)": "(dan :count kesalahan lainnya)",
|
||||||
|
"(and :count more errors)": "(dan :count kesalahan lainnya)|(dan :count kesalahan lainnya)|(dan :count kesalahan lainnya)",
|
||||||
|
"A decryption key is required.": "Kunci dekripsi diperlukan.",
|
||||||
|
"All rights reserved.": "Hak Cipta Dilindungi.",
|
||||||
|
"Encrypted environment file already exists.": "Enkripsi file environment sudah ada.",
|
||||||
|
"Encrypted environment file not found.": "Enkripsi file environment tidak ditemukan.",
|
||||||
|
"Environment file already exists.": "File environment sudah ada.",
|
||||||
|
"Environment file not found.": "file environment tidak ditemukan.",
|
||||||
|
"errors": "kesalahan",
|
||||||
|
"Forbidden": "Dilarang",
|
||||||
|
"Go to page :page": "Ke halaman :page",
|
||||||
|
"Hello!": "Halo!",
|
||||||
|
"If you did not create an account, no further action is required.": "Jika Anda tidak membuat akun, Anda tidak perlu melakukan apapun.",
|
||||||
|
"If you did not request a password reset, no further action is required.": "Jika Anda tidak meminta pengaturan ulang kata sandi, Anda tidak perlu melakukan apapun.",
|
||||||
|
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Jika Anda mengalami kesulitan mengklik tombol \":actionText\", salin dan tempel URL di bawah\nke browser web Anda:",
|
||||||
|
"Invalid credential format.": "Invalid credential format.",
|
||||||
|
"Invalid filename.": "Nama file tidak valid.",
|
||||||
|
"Invalid JSON was returned from the route.": "JSON yang tidak valid dikembalikan dari rute.",
|
||||||
|
"Location": "Lokasi",
|
||||||
|
"Login": "Masuk",
|
||||||
|
"Logout": "Keluar",
|
||||||
|
"Not Found": "Tidak ditemukan",
|
||||||
|
"of": "dari",
|
||||||
|
"Page Expired": "Halaman Kadaluwarsa",
|
||||||
|
"Pagination Navigation": "Navigasi Paginasi",
|
||||||
|
"Passkey not recognized. It may have been removed from your account.": "Passkey not recognized. It may have been removed from your account.",
|
||||||
|
"Passkey registration session expired. Please try again.": "Passkey registration session expired. Please try again.",
|
||||||
|
"Passkey verification session expired. Please try again.": "Passkey verification session expired. Please try again.",
|
||||||
|
"Payment Required": "Pembayaran Diperlukan",
|
||||||
|
"Please click the button below to verify your email address.": "Silakan klik tombol di bawah untuk memverifikasi alamat surel Anda.",
|
||||||
|
"Regards,": "Salam,",
|
||||||
|
"Register": "Daftar",
|
||||||
|
"Reset Password": "Atur Ulang Kata Sandi",
|
||||||
|
"Reset your password": "Setel ulang kata sandi Anda",
|
||||||
|
"results": "hasil",
|
||||||
|
"Server Error": "Terjadi Kesalahan Server",
|
||||||
|
"Service Unavailable": "Layanan Tidak Tersedia",
|
||||||
|
"Showing": "Menampilkan",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one number.": ":Attribute minimal berisi :length karakter dan mengandung setidaknya satu angka.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one special character and one number.": ":Attribute minimal berisi :length karakter dan mengandung setidaknya satu karakter spesial dan satu angka.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one special character.": ":Attribute minimal berisi :length karakter dan mengandung setidaknya satu karakter spesial.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character and one number.": ":Attribute minimal berisi :length karakter dan mengandung setidaknya satu huruf besar dan satu angka.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character and one special character.": ":Attribute minimal berisi :length karakter dan mengandung setidaknya satu huruf besar dan satu karakter spesial.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character, one number, and one special character.": ":Attribute minimal berisi :length karakter dan mengandung setidaknya satu huruf besar, satu angka, dan satu karakter spesial.",
|
||||||
|
"The :attribute must be at least :length characters and contain at least one uppercase character.": ":Attribute minimal berisi :length karakter dan mengandung setidaknya satu huruf besar.",
|
||||||
|
"The :attribute must be at least :length characters.": ":Attribute minimal berisi :length karakter.",
|
||||||
|
"The given data was invalid.": "Data yang diberikan tidak valid.",
|
||||||
|
"The provided password does not match your current password.": "Kata sandi yang dimasukkan tidak cocok dengan kata sandi saat ini.",
|
||||||
|
"The provided password was incorrect.": "Kata sandi yang dimasukkan salah.",
|
||||||
|
"The provided two factor authentication code was invalid.": "Kode autentikasi dua faktor yang dimasukkan tidak valid.",
|
||||||
|
"The provided two factor recovery code was invalid.": "Kode pemulihan dua faktor yang diberikan tidak valid.",
|
||||||
|
"The response is not a streamed response.": "Responsnya bukan respons yang dialirkan.",
|
||||||
|
"The response is not a view.": "Responsnya bukanlah pandangan.",
|
||||||
|
"This action is unauthorized.": "Tindakan ini tidak sah.",
|
||||||
|
"This password reset link will expire in :count minutes.": "Tautan pengaturan ulang kata sandi ini akan kedaluwarsa dalam :count menit.",
|
||||||
|
"to": "kepada",
|
||||||
|
"Toggle navigation": "Alihkan navigasi",
|
||||||
|
"Too Many Requests": "Terlalu Banyak Permintaan",
|
||||||
|
"Unable to register passkey. Please try again.": "Unable to register passkey. Please try again.",
|
||||||
|
"Unable to register this passkey.": "Unable to register this passkey.",
|
||||||
|
"Unable to sign in with this account.": "Unable to sign in with this account.",
|
||||||
|
"Unable to verify passkey. Please try again.": "Unable to verify passkey. Please try again.",
|
||||||
|
"Unauthorized": "Tidak Diizinkan",
|
||||||
|
"Verify Email Address": "Verifikasi Alamat Surel",
|
||||||
|
"Verify your email address": "Verifikasi alamat email Anda",
|
||||||
|
"Whoops!": "Aduh!",
|
||||||
|
"You are receiving this email because we received a password reset request for your account.": "Anda menerima surel ini karena kami menerima permintaan pengaturan ulang kata sandi untuk akun anda."
|
||||||
|
}
|
||||||
9
lang/id/auth.php
Normal file
9
lang/id/auth.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'failed' => 'Identitas tersebut tidak cocok dengan data kami.',
|
||||||
|
'password' => 'Kata sandi salah.',
|
||||||
|
'throttle' => 'Terlalu banyak upaya masuk. Silahkan coba lagi dalam :seconds detik.',
|
||||||
|
];
|
||||||
8
lang/id/pagination.php
Normal file
8
lang/id/pagination.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'next' => 'Berikutnya »',
|
||||||
|
'previous' => '« Sebelumnya',
|
||||||
|
];
|
||||||
11
lang/id/passwords.php
Normal file
11
lang/id/passwords.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'reset' => 'Kata sandi Anda sudah direset!',
|
||||||
|
'sent' => 'Kami sudah mengirim surel yang berisi tautan untuk mereset kata sandi Anda!',
|
||||||
|
'throttled' => 'Harap tunggu sebelum mencoba lagi.',
|
||||||
|
'token' => 'Token pengaturan ulang kata sandi tidak sah.',
|
||||||
|
'user' => 'Kami tidak dapat menemukan pengguna dengan alamat surel tersebut.',
|
||||||
|
];
|
||||||
161
lang/id/validation.php
Normal file
161
lang/id/validation.php
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'accepted' => ':Attribute harus diterima.',
|
||||||
|
'accepted_if' => ':Attribute harus diterima ketika :other berisi :value.',
|
||||||
|
'active_url' => ':Attribute bukan URL yang valid.',
|
||||||
|
'after' => ':Attribute harus berisi tanggal setelah :date.',
|
||||||
|
'after_or_equal' => ':Attribute harus berisi tanggal setelah atau sama dengan :date.',
|
||||||
|
'alpha' => ':Attribute hanya boleh berisi huruf.',
|
||||||
|
'alpha_dash' => ':Attribute hanya boleh berisi huruf, angka, strip, dan garis bawah.',
|
||||||
|
'alpha_num' => ':Attribute hanya boleh berisi huruf dan angka.',
|
||||||
|
'any_of' => 'Bidang :attribute tidak valid.',
|
||||||
|
'array' => ':Attribute harus berisi sebuah array.',
|
||||||
|
'array_keys' => 'The :attribute field must only contain the following keys: :values.',
|
||||||
|
'ascii' => ':Attribute hanya boleh berisi karakter dan simbol alfanumerik single-byte.',
|
||||||
|
'base64' => 'The :attribute field must be a valid Base64 string.',
|
||||||
|
'before' => ':Attribute harus berisi tanggal sebelum :date.',
|
||||||
|
'before_or_equal' => ':Attribute harus berisi tanggal sebelum atau sama dengan :date.',
|
||||||
|
'between' => [
|
||||||
|
'array' => ':Attribute harus memiliki :min sampai :max anggota.',
|
||||||
|
'file' => ':Attribute harus berukuran antara :min sampai :max kilobita.',
|
||||||
|
'numeric' => ':Attribute harus bernilai antara :min sampai :max.',
|
||||||
|
'string' => ':Attribute harus berisi antara :min sampai :max karakter.',
|
||||||
|
],
|
||||||
|
'boolean' => ':Attribute harus bernilai true atau false',
|
||||||
|
'can' => 'Bidang :attribute berisi nilai yang tidak sah.',
|
||||||
|
'confirmed' => 'Konfirmasi :attribute tidak cocok.',
|
||||||
|
'contains' => 'Bidang :attribute tidak memiliki nilai yang diperlukan.',
|
||||||
|
'current_password' => 'Kata sandi salah.',
|
||||||
|
'date' => ':Attribute bukan tanggal yang valid.',
|
||||||
|
'date_equals' => ':Attribute harus berisi tanggal yang sama dengan :date.',
|
||||||
|
'date_format' => ':Attribute tidak cocok dengan format :format.',
|
||||||
|
'decimal' => ':Attribute harus memiliki :decimal tempat desimal.',
|
||||||
|
'declined' => ':Attribute ini harus ditolak.',
|
||||||
|
'declined_if' => ':Attribute ini harus ditolak ketika :other bernilai :value.',
|
||||||
|
'different' => ':Attribute dan :other harus berbeda.',
|
||||||
|
'digits' => ':Attribute harus terdiri dari :digits angka.',
|
||||||
|
'digits_between' => ':Attribute harus terdiri dari :min sampai :max angka.',
|
||||||
|
'dimensions' => ':Attribute tidak memiliki dimensi gambar yang valid.',
|
||||||
|
'distinct' => ':Attribute memiliki nilai yang duplikat.',
|
||||||
|
'doesnt_contain' => 'Bidang :attribute tidak boleh berisi salah satu dari yang berikut: :values.',
|
||||||
|
'doesnt_end_with' => ':Attribute tidak boleh diakhiri dengan salah satu dari berikut ini: :values.',
|
||||||
|
'doesnt_start_with' => ':Attribute tidak boleh dimulai dengan salah satu dari berikut ini: :values.',
|
||||||
|
'email' => ':Attribute harus berupa alamat surel yang valid.',
|
||||||
|
'encoding' => 'Bidang :attribute harus dikodekan dalam :encoding.',
|
||||||
|
'ends_with' => ':Attribute harus diakhiri salah satu dari berikut: :values',
|
||||||
|
'enum' => ':Attribute yang dipilih tidak valid.',
|
||||||
|
'exists' => ':Attribute yang dipilih tidak valid.',
|
||||||
|
'extensions' => 'Bidang :attribute harus memiliki salah satu ekstensi berikut: :values.',
|
||||||
|
'file' => ':Attribute harus berupa sebuah berkas.',
|
||||||
|
'filled' => ':Attribute harus memiliki nilai.',
|
||||||
|
'gt' => [
|
||||||
|
'array' => ':Attribute harus memiliki lebih dari :value anggota.',
|
||||||
|
'file' => ':Attribute harus berukuran lebih besar dari :value kilobita.',
|
||||||
|
'numeric' => ':Attribute harus bernilai lebih besar dari :value.',
|
||||||
|
'string' => ':Attribute harus berisi lebih besar dari :value karakter.',
|
||||||
|
],
|
||||||
|
'gte' => [
|
||||||
|
'array' => ':Attribute harus terdiri dari :value anggota atau lebih.',
|
||||||
|
'file' => ':Attribute harus berukuran lebih besar dari atau sama dengan :value kilobita.',
|
||||||
|
'numeric' => ':Attribute harus bernilai lebih besar dari atau sama dengan :value.',
|
||||||
|
'string' => ':Attribute harus berisi lebih besar dari atau sama dengan :value karakter.',
|
||||||
|
],
|
||||||
|
'hex_color' => 'Bidang :attribute harus berupa warna heksadesimal yang valid.',
|
||||||
|
'image' => ':Attribute harus berupa gambar.',
|
||||||
|
'in' => ':Attribute yang dipilih tidak valid.',
|
||||||
|
'in_array' => ':Attribute tidak ada di dalam :other.',
|
||||||
|
'in_array_keys' => ':attribute bidang harus berisi setidaknya satu dari tombol berikut: :values.',
|
||||||
|
'integer' => ':Attribute harus berupa bilangan bulat.',
|
||||||
|
'ip' => ':Attribute harus berupa alamat IP yang valid.',
|
||||||
|
'ipv4' => ':Attribute harus berupa alamat IPv4 yang valid.',
|
||||||
|
'ipv6' => ':Attribute harus berupa alamat IPv6 yang valid.',
|
||||||
|
'json' => ':Attribute harus berupa JSON string yang valid.',
|
||||||
|
'list' => 'Bidang :attribute harus berupa daftar.',
|
||||||
|
'lowercase' => ':Attribute harus berupa huruf kecil.',
|
||||||
|
'lt' => [
|
||||||
|
'array' => ':Attribute harus memiliki kurang dari :value anggota.',
|
||||||
|
'file' => ':Attribute harus berukuran kurang dari :value kilobita.',
|
||||||
|
'numeric' => ':Attribute harus bernilai kurang dari :value.',
|
||||||
|
'string' => ':Attribute harus berisi kurang dari :value karakter.',
|
||||||
|
],
|
||||||
|
'lte' => [
|
||||||
|
'array' => ':Attribute harus tidak lebih dari :value anggota.',
|
||||||
|
'file' => ':Attribute harus berukuran kurang dari atau sama dengan :value kilobita.',
|
||||||
|
'numeric' => ':Attribute harus bernilai kurang dari atau sama dengan :value.',
|
||||||
|
'string' => ':Attribute harus berisi kurang dari atau sama dengan :value karakter.',
|
||||||
|
],
|
||||||
|
'mac_address' => ':Attribute harus berupa alamat MAC yang valid.',
|
||||||
|
'max' => [
|
||||||
|
'array' => ':Attribute maksimal terdiri dari :max anggota.',
|
||||||
|
'file' => ':Attribute maksimal berukuran :max kilobita.',
|
||||||
|
'numeric' => ':Attribute maksimal bernilai :max.',
|
||||||
|
'string' => ':Attribute maksimal berisi :max karakter.',
|
||||||
|
],
|
||||||
|
'max_digits' => ':Attribute tidak boleh memiliki lebih dari :max digit.',
|
||||||
|
'mimes' => ':Attribute harus berupa berkas berjenis: :values.',
|
||||||
|
'mimetypes' => ':Attribute harus berupa berkas berjenis: :values.',
|
||||||
|
'min' => [
|
||||||
|
'array' => ':Attribute minimal terdiri dari :min anggota.',
|
||||||
|
'file' => ':Attribute minimal berukuran :min kilobita.',
|
||||||
|
'numeric' => ':Attribute minimal bernilai :min.',
|
||||||
|
'string' => ':Attribute minimal berisi :min karakter.',
|
||||||
|
],
|
||||||
|
'min_digits' => ':Attribute tidak boleh memiliki kurang dari :min digit.',
|
||||||
|
'missing' => 'Bidang :attribute harus hilang.',
|
||||||
|
'missing_if' => 'Bidang :attribute harus hilang ketika :other adalah :value.',
|
||||||
|
'missing_unless' => 'Bidang :attribute harus hilang kecuali :other adalah :value.',
|
||||||
|
'missing_with' => 'Kolom :attribute harus hilang saat ada :values.',
|
||||||
|
'missing_with_all' => 'Kolom :attribute harus hilang jika ada :values.',
|
||||||
|
'multiple_of' => ':Attribute harus merupakan kelipatan dari :value',
|
||||||
|
'not_in' => ':Attribute yang dipilih tidak valid.',
|
||||||
|
'not_regex' => 'Format :attribute tidak valid.',
|
||||||
|
'numeric' => ':Attribute harus berupa angka.',
|
||||||
|
'password' => [
|
||||||
|
'letters' => ':Attribute ini harus memiliki setidaknya satu karakter.',
|
||||||
|
'mixed' => ':Attribute ini harus memiliki setidaknya satu huruf kapital dan satu huruf kecil.',
|
||||||
|
'numbers' => ':Attribute ini harus memiliki setidaknya satu angka.',
|
||||||
|
'symbols' => ':Attribute ini harus memiliki setidaknya satu simbol.',
|
||||||
|
'uncompromised' => ':Attribute ini telah muncul di kebocoran data. Silahkan memilih :attribute yang berbeda.',
|
||||||
|
],
|
||||||
|
'present' => ':Attribute wajib ada.',
|
||||||
|
'present_if' => 'Bidang :attribute harus ada ketika :other adalah :value.',
|
||||||
|
'present_unless' => 'Bidang :attribute harus ada kecuali :other adalah :value.',
|
||||||
|
'present_with' => 'Bidang :attribute harus ada bila ada :values.',
|
||||||
|
'present_with_all' => 'Bidang :attribute harus ada ketika ada :values.',
|
||||||
|
'prohibited' => ':Attribute tidak boleh ada.',
|
||||||
|
'prohibited_if' => ':Attribute tidak boleh ada bila :other adalah :value.',
|
||||||
|
'prohibited_if_accepted' => ':attribute bidang dilarang ketika :other diterima.',
|
||||||
|
'prohibited_if_declined' => ':attribute bidang dilarang ketika :other ditolak.',
|
||||||
|
'prohibited_unless' => ':Attribute tidak boleh ada kecuali :other memiliki nilai :values.',
|
||||||
|
'prohibits' => ':Attribute melarang isian :other untuk ditampilkan.',
|
||||||
|
'regex' => 'Format :attribute tidak valid.',
|
||||||
|
'required' => ':Attribute wajib diisi.',
|
||||||
|
'required_array_keys' => ':Attribute wajib berisi entri untuk: :values.',
|
||||||
|
'required_if' => ':Attribute wajib diisi bila :other adalah :value.',
|
||||||
|
'required_if_accepted' => ':Attribute wajib diisi bila :other sesuai.',
|
||||||
|
'required_if_declined' => 'Bidang :attribute wajib diisi bila :other ditolak.',
|
||||||
|
'required_unless' => ':Attribute wajib diisi kecuali :other memiliki nilai :values.',
|
||||||
|
'required_with' => ':Attribute wajib diisi bila terdapat :values.',
|
||||||
|
'required_with_all' => ':Attribute wajib diisi bila terdapat :values.',
|
||||||
|
'required_without' => ':Attribute wajib diisi bila tidak terdapat :values.',
|
||||||
|
'required_without_all' => ':Attribute wajib diisi bila sama sekali tidak terdapat :values.',
|
||||||
|
'same' => ':Attribute dan :other harus sama.',
|
||||||
|
'size' => [
|
||||||
|
'array' => ':Attribute harus mengandung :size anggota.',
|
||||||
|
'file' => ':Attribute harus berukuran :size kilobyte.',
|
||||||
|
'numeric' => ':Attribute harus berukuran :size.',
|
||||||
|
'string' => ':Attribute harus berukuran :size karakter.',
|
||||||
|
],
|
||||||
|
'starts_with' => ':Attribute harus diawali salah satu dari berikut: :values',
|
||||||
|
'string' => ':Attribute harus berupa string.',
|
||||||
|
'timezone' => ':Attribute harus berisi zona waktu yang valid.',
|
||||||
|
'ulid' => ':Attribute harus merupakan ULID yang valid.',
|
||||||
|
'unique' => ':Attribute sudah ada sebelumnya.',
|
||||||
|
'uploaded' => ':Attribute gagal diunggah.',
|
||||||
|
'uppercase' => ':Attribute harus berupa huruf kapital.',
|
||||||
|
'url' => 'Format :attribute tidak valid.',
|
||||||
|
'uuid' => ':Attribute harus merupakan UUID yang valid.',
|
||||||
|
];
|
||||||
@ -24,7 +24,7 @@ createInertiaApp({
|
|||||||
title: (title) => (title ? `${title} - ${appName}` : appName),
|
title: (title) => (title ? `${title} - ${appName}` : appName),
|
||||||
layout: (name) => {
|
layout: (name) => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case name === 'welcome':
|
case name === 'admin/manage/cutting/show':
|
||||||
return null;
|
return null;
|
||||||
case name.startsWith('auth/'):
|
case name.startsWith('auth/'):
|
||||||
return AuthLayout;
|
return AuthLayout;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ImagePreviewModal } from '@/components/dialogs';
|
import { ImagePreviewModal } from '@/components/dialogs';
|
||||||
|
import type { ImagePreviewItem } from '@/components/dialogs/image-preview-modal';
|
||||||
|
|
||||||
type ImagePreviewButtonProps = {
|
type ImagePreviewButtonProps = {
|
||||||
srcs: string[];
|
srcs: string[];
|
||||||
@ -9,6 +10,8 @@ type ImagePreviewButtonProps = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
items?: ImagePreviewItem[];
|
||||||
|
startIndex?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ImagePreviewButton({
|
export function ImagePreviewButton({
|
||||||
@ -19,6 +22,8 @@ export function ImagePreviewButton({
|
|||||||
description,
|
description,
|
||||||
alt,
|
alt,
|
||||||
className = 'h-10 w-10',
|
className = 'h-10 w-10',
|
||||||
|
items,
|
||||||
|
startIndex = 0,
|
||||||
}: ImagePreviewButtonProps) {
|
}: ImagePreviewButtonProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
@ -49,6 +54,8 @@ export function ImagePreviewButton({
|
|||||||
sources={modalSources ?? srcs}
|
sources={modalSources ?? srcs}
|
||||||
title={title}
|
title={title}
|
||||||
description={description}
|
description={description}
|
||||||
|
items={items}
|
||||||
|
startIndex={startIndex}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,13 +1,22 @@
|
|||||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export type ImagePreviewItem = {
|
||||||
|
id?: string | number;
|
||||||
|
src: string | null;
|
||||||
|
sources?: string[];
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type ImagePreviewModalProps = {
|
type ImagePreviewModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -17,6 +26,8 @@ type ImagePreviewModalProps = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
sources?: string[];
|
sources?: string[];
|
||||||
|
items?: ImagePreviewItem[];
|
||||||
|
startIndex?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ImagePreviewModal({
|
export function ImagePreviewModal({
|
||||||
@ -27,23 +38,49 @@ export function ImagePreviewModal({
|
|||||||
description,
|
description,
|
||||||
alt = 'Preview',
|
alt = 'Preview',
|
||||||
sources,
|
sources,
|
||||||
|
items,
|
||||||
|
startIndex = 0,
|
||||||
}: ImagePreviewModalProps) {
|
}: ImagePreviewModalProps) {
|
||||||
const allImages = sources && sources.length > 0 ? sources : src ? [src] : [];
|
const [itemIndex, setItemIndex] = useState(startIndex);
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [imageIndex, setImageIndex] = useState(0);
|
||||||
|
|
||||||
const currentSrc = allImages[currentIndex] ?? src;
|
useEffect(() => {
|
||||||
const hasMultiple = allImages.length > 1;
|
if (open) {
|
||||||
|
setItemIndex(startIndex);
|
||||||
|
setImageIndex(0);
|
||||||
|
}
|
||||||
|
}, [open, startIndex]);
|
||||||
|
|
||||||
function handlePrev() {
|
useEffect(() => {
|
||||||
setCurrentIndex((prev) =>
|
setImageIndex(0);
|
||||||
prev === 0 ? allImages.length - 1 : prev - 1,
|
}, [itemIndex]);
|
||||||
);
|
|
||||||
|
const isMultiItem = items && items.length > 1;
|
||||||
|
const currentItem = isMultiItem ? items[itemIndex] : null;
|
||||||
|
|
||||||
|
const allImages = currentItem
|
||||||
|
? (currentItem.sources?.length ? currentItem.sources : currentItem.src ? [currentItem.src] : [])
|
||||||
|
: (sources?.length ? sources : src ? [src] : []);
|
||||||
|
|
||||||
|
const currentSrc = allImages[imageIndex] ?? allImages[0] ?? currentItem?.src ?? src;
|
||||||
|
const currentTitle = currentItem?.title ?? title;
|
||||||
|
const currentDescription = currentItem?.description ?? description;
|
||||||
|
const hasMultipleImages = allImages.length > 1;
|
||||||
|
|
||||||
|
function handleImagePrev() {
|
||||||
|
setImageIndex((prev) => prev === 0 ? allImages.length - 1 : prev - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNext() {
|
function handleImageNext() {
|
||||||
setCurrentIndex((prev) =>
|
setImageIndex((prev) => prev === allImages.length - 1 ? 0 : prev + 1);
|
||||||
prev === allImages.length - 1 ? 0 : prev + 1,
|
}
|
||||||
);
|
|
||||||
|
function handleItemPrev() {
|
||||||
|
setItemIndex((prev) => prev === 0 ? items.length - 1 : prev - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleItemNext() {
|
||||||
|
setItemIndex((prev) => prev === items.length - 1 ? 0 : prev + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -51,17 +88,17 @@ export function ImagePreviewModal({
|
|||||||
open={open}
|
open={open}
|
||||||
onOpenChange={(v) => {
|
onOpenChange={(v) => {
|
||||||
if (!v) {
|
if (!v) {
|
||||||
setCurrentIndex(0);
|
setItemIndex(startIndex);
|
||||||
}
|
setImageIndex(0);
|
||||||
|
}
|
||||||
onOpenChange(v);
|
onOpenChange(v);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent showCloseButton>
|
<DialogContent showCloseButton>
|
||||||
{(title || description) && (
|
{(currentTitle || currentDescription) && (
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
{title && <DialogTitle>{title}</DialogTitle>}
|
{currentTitle && <DialogTitle>{currentTitle}</DialogTitle>}
|
||||||
{description && <DialogDescription>{description}</DialogDescription>}
|
{currentDescription && <DialogDescription>{currentDescription}</DialogDescription>}
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
)}
|
)}
|
||||||
{currentSrc && (
|
{currentSrc && (
|
||||||
@ -71,13 +108,13 @@ setCurrentIndex(0);
|
|||||||
alt={alt}
|
alt={alt}
|
||||||
className="max-h-[80vh] w-full rounded-lg object-contain"
|
className="max-h-[80vh] w-full rounded-lg object-contain"
|
||||||
/>
|
/>
|
||||||
{hasMultiple && (
|
{hasMultipleImages && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute left-2 top-1/2 h-8 w-8 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
className="absolute left-2 top-1/2 h-8 w-8 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
||||||
onClick={handlePrev}
|
onClick={handleImagePrev}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -85,17 +122,29 @@ setCurrentIndex(0);
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute right-2 top-1/2 h-8 w-8 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
className="absolute right-2 top-1/2 h-8 w-8 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
||||||
onClick={handleNext}
|
onClick={handleImageNext}
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 rounded-full bg-black/50 px-2 py-1 text-xs text-white">
|
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 rounded-full bg-black/50 px-2 py-1 text-xs text-white">
|
||||||
{currentIndex + 1} / {allImages.length}
|
{imageIndex + 1} / {allImages.length}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isMultiItem && (
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={handleItemPrev}>
|
||||||
|
<ChevronLeft className="h-4 w-4 mr-1" />
|
||||||
|
Sebelumnya
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={handleItemNext}>
|
||||||
|
Selanjutnya
|
||||||
|
<ChevronRight className="h-4 w-4 ml-1" />
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,3 +3,4 @@ export { DeleteConfirmDialog } from './delete-confirm-dialog';
|
|||||||
export { FormDialog } from './form-dialog';
|
export { FormDialog } from './form-dialog';
|
||||||
export { ImagePreviewButton } from './image-preview-button';
|
export { ImagePreviewButton } from './image-preview-button';
|
||||||
export { ImagePreviewModal } from './image-preview-modal';
|
export { ImagePreviewModal } from './image-preview-modal';
|
||||||
|
export type { ImagePreviewItem } from './image-preview-modal';
|
||||||
|
|||||||
@ -599,7 +599,7 @@ export default function Analysis({
|
|||||||
<StatCard
|
<StatCard
|
||||||
title="Bahan Baku"
|
title="Bahan Baku"
|
||||||
icon={Package}
|
icon={Package}
|
||||||
mainLabel="Total Belanja"
|
mainLabel="Total Stok"
|
||||||
mainValue={rawMaterialStock.total_stock.toLocaleString('id-ID')}
|
mainValue={rawMaterialStock.total_stock.toLocaleString('id-ID')}
|
||||||
subLabel={`Rp${formatRupiah(rawMaterialStock.total_value)}`}
|
subLabel={`Rp${formatRupiah(rawMaterialStock.total_value)}`}
|
||||||
description="Tidak terpengaruh filter tanggal"
|
description="Tidak terpengaruh filter tanggal"
|
||||||
@ -615,7 +615,7 @@ export default function Analysis({
|
|||||||
<StatCard
|
<StatCard
|
||||||
title="Stok Produk"
|
title="Stok Produk"
|
||||||
icon={ShoppingCart}
|
icon={ShoppingCart}
|
||||||
mainLabel="Total Restock"
|
mainLabel="Total Stok"
|
||||||
mainValue={productStock.total_stock.toLocaleString('id-ID')}
|
mainValue={productStock.total_stock.toLocaleString('id-ID')}
|
||||||
subLabel={`Rp${formatRupiah(productStock.total_value)}`}
|
subLabel={`Rp${formatRupiah(productStock.total_value)}`}
|
||||||
description="Tidak terpengaruh filter tanggal"
|
description="Tidak terpengaruh filter tanggal"
|
||||||
|
|||||||
@ -26,10 +26,13 @@ export type Cutting = {
|
|||||||
status: string;
|
status: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
total_material_cost: number | null;
|
total_material_cost: number | null;
|
||||||
|
formatted_total_material_cost: string | null;
|
||||||
cost_per_unit: number | null;
|
cost_per_unit: number | null;
|
||||||
|
formatted_cost_per_unit: string | null;
|
||||||
photo_url: string | null;
|
photo_url: string | null;
|
||||||
photo_conversion_url: string | null;
|
photo_conversion_url: string | null;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
formatted_created_at: string;
|
||||||
created_by: {
|
created_by: {
|
||||||
id: number;
|
id: number;
|
||||||
user_profile: {
|
user_profile: {
|
||||||
@ -46,8 +49,6 @@ export type Cutting = {
|
|||||||
raw_material_price: {
|
raw_material_price: {
|
||||||
id: number;
|
id: number;
|
||||||
variant: string;
|
variant: string;
|
||||||
price: number;
|
|
||||||
stock: number;
|
|
||||||
photo_url: string | null;
|
photo_url: string | null;
|
||||||
photo_conversion_url: string | null;
|
photo_conversion_url: string | null;
|
||||||
raw_material: {
|
raw_material: {
|
||||||
|
|||||||
@ -1,13 +1,90 @@
|
|||||||
import { ChevronDown, Pencil, Trash2 } from 'lucide-react';
|
|
||||||
import { ImagePreviewButton } from '@/components/dialogs';
|
|
||||||
import { RowActions } from '@/components/data-display';
|
import { RowActions } from '@/components/data-display';
|
||||||
|
import { ImagePreviewButton } from '@/components/dialogs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { useCan } from '@/hooks/use-can';
|
import { useCan } from '@/hooks/use-can';
|
||||||
import { formatDateTime, formatNumber } from '@/lib/format';
|
import { formatNumber } from '@/lib/format';
|
||||||
import { formatCurrency } from '@/lib/utils';
|
import { show as cuttingShow } from '@/routes/admin/manage/cuttings';
|
||||||
|
import { Briefcase, ChevronDown, Copy, MessageCircle, Pencil, Share2, Trash2 } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
import type { Cutting } from './columns';
|
import type { Cutting } from './columns';
|
||||||
|
|
||||||
|
function generateShareLink(cuttingId: number): string {
|
||||||
|
return window.location.origin + cuttingShow.url(cuttingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateWhatsappText(cutting: Cutting): string {
|
||||||
|
const result = cutting.cutting_results?.[0];
|
||||||
|
const items = cutting.cutting_materials ?? [];
|
||||||
|
const shareLink = generateShareLink(cutting.id);
|
||||||
|
|
||||||
|
let text = `*Cutting #${cutting.id}*\n`;
|
||||||
|
text += `Status: ${cutting.status === 'completed' ? 'Selesai' : cutting.status === 'cancelled' ? 'Dibatalkan' : 'Dikerjakan'}\n`;
|
||||||
|
text += `Tanggal: ${cutting.formatted_created_at}\n`;
|
||||||
|
text += `Oleh: ${cutting.created_by?.user_profile?.full_name ?? '-'}\n`;
|
||||||
|
|
||||||
|
if (cutting.description) {
|
||||||
|
text += `Deskripsi: ${cutting.description}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const productNames = new Set<string>();
|
||||||
|
(cutting.cutting_results ?? []).forEach((r) => {
|
||||||
|
if (r.product_name) {
|
||||||
|
productNames.add(r.product_name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (productNames.size > 0) {
|
||||||
|
text += `\nNama Produk: ${Array.from(productNames).join(', ')}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalUsage = items.reduce(
|
||||||
|
(sum, item) => sum + Number(item.material_usage),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
text += `Total Pemakaian: ${formatNumber(totalUsage)}\n`;
|
||||||
|
|
||||||
|
if (result?.cutting_result) {
|
||||||
|
text += `Total Hasil Cutting: ${formatNumber(result.cutting_result)} pcs\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
text += `\nLihat detail lengkap:\n${shareLink}`;
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWAWeb(text: string) {
|
||||||
|
const encoded = encodeURIComponent(text);
|
||||||
|
window.open(`https://wa.me/?text=${encoded}`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWAAndroid(text: string, pkg: string) {
|
||||||
|
const encoded = encodeURIComponent(text);
|
||||||
|
window.open(`intent://send?text=${encoded}#Intent;scheme=whatsapp;package=${pkg};end`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function shareViaWhatsAppMessenger(text: string) {
|
||||||
|
if (/android/i.test(navigator.userAgent)) {
|
||||||
|
openWAAndroid(text, 'com.whatsapp');
|
||||||
|
} else {
|
||||||
|
openWAWeb(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shareViaWhatsAppBusiness(text: string) {
|
||||||
|
if (/android/i.test(navigator.userAgent)) {
|
||||||
|
openWAAndroid(text, 'com.whatsapp.w4b');
|
||||||
|
} else {
|
||||||
|
openWAWeb(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type CuttingCardRowParams = {
|
export type CuttingCardRowParams = {
|
||||||
cutting: Cutting;
|
cutting: Cutting;
|
||||||
index: number;
|
index: number;
|
||||||
@ -26,6 +103,8 @@ export function CuttingCardRow({
|
|||||||
onDelete,
|
onDelete,
|
||||||
}: CuttingCardRowParams) {
|
}: CuttingCardRowParams) {
|
||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
|
const [shareOpen, setShareOpen] = useState(false);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
const items = cutting.cutting_materials ?? [];
|
const items = cutting.cutting_materials ?? [];
|
||||||
const result = cutting.cutting_results?.[0];
|
const result = cutting.cutting_results?.[0];
|
||||||
const singleCount = items.filter(
|
const singleCount = items.filter(
|
||||||
@ -43,6 +122,18 @@ export function CuttingCardRow({
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const shareText = generateWhatsappText(cutting);
|
||||||
|
|
||||||
|
function handleShareWAMessenger() {
|
||||||
|
shareViaWhatsAppMessenger(shareText);
|
||||||
|
setShareOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShareWABusiness() {
|
||||||
|
shareViaWhatsAppBusiness(shareText);
|
||||||
|
setShareOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
@ -84,7 +175,7 @@ export function CuttingCardRow({
|
|||||||
|
|
||||||
<div className="mt-2 flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
|
<div className="mt-2 flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
|
||||||
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 font-medium text-foreground">
|
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 font-medium text-foreground">
|
||||||
{formatDateTime(cutting.created_at)}
|
{cutting.formatted_created_at}
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 font-medium text-foreground">
|
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 font-medium text-foreground">
|
||||||
{cutting.status === 'completed'
|
{cutting.status === 'completed'
|
||||||
@ -110,20 +201,20 @@ export function CuttingCardRow({
|
|||||||
{formatNumber(result.cutting_result)}
|
{formatNumber(result.cutting_result)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{cutting.cost_per_unit && (
|
{cutting.formatted_cost_per_unit && (
|
||||||
<span className="font-semibold">
|
<span className="font-semibold">
|
||||||
<span className="font-normal text-muted-foreground">
|
<span className="font-normal text-muted-foreground">
|
||||||
Per Produk:{' '}
|
Per Produk:{' '}
|
||||||
</span>
|
</span>
|
||||||
{formatCurrency(cutting.cost_per_unit)}
|
{cutting.formatted_cost_per_unit}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{cutting.total_material_cost && (
|
{cutting.formatted_total_material_cost && (
|
||||||
<span className="font-semibold">
|
<span className="font-semibold">
|
||||||
<span className="font-normal text-muted-foreground">
|
<span className="font-normal text-muted-foreground">
|
||||||
Biaya Keseluruhan:{' '}
|
Biaya Keseluruhan:{' '}
|
||||||
</span>
|
</span>
|
||||||
{formatCurrency(cutting.total_material_cost)}
|
{cutting.formatted_total_material_cost}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -134,13 +225,44 @@ export function CuttingCardRow({
|
|||||||
srcs={[cutting.photo_conversion_url ?? cutting.photo_url]}
|
srcs={[cutting.photo_conversion_url ?? cutting.photo_url]}
|
||||||
modalSrc={cutting.photo_url}
|
modalSrc={cutting.photo_url}
|
||||||
title={productName}
|
title={productName}
|
||||||
description={cutting.description ?? formatDateTime(cutting.created_at)}
|
description={cutting.description ?? cutting.formatted_created_at}
|
||||||
className="h-16 w-16"
|
className="h-16 w-16"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<Popover open={shareOpen} onOpenChange={setShareOpen}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||||
|
<Share2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Bagikan</TooltipContent>
|
||||||
|
<PopoverContent align="end" className="w-48 space-y-1 p-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="w-full justify-start gap-2"
|
||||||
|
onClick={handleShareWAMessenger}
|
||||||
|
>
|
||||||
|
WhatsApp Messenger
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="w-full justify-start gap-2"
|
||||||
|
onClick={handleShareWABusiness}
|
||||||
|
>
|
||||||
|
WhatsApp Business
|
||||||
|
</Button>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</Tooltip>
|
||||||
{(can('cuttings.update') || can('cuttings.delete')) && (
|
{(can('cuttings.update') || can('cuttings.delete')) && (
|
||||||
<RowActions
|
<RowActions
|
||||||
actions={[
|
actions={[
|
||||||
@ -163,6 +285,7 @@ export function CuttingCardRow({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { Fragment } from 'react';
|
|
||||||
import { ImagePreviewButton } from '@/components/dialogs';
|
import { ImagePreviewButton } from '@/components/dialogs';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -9,6 +8,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { formatNumber } from '@/lib/format';
|
import { formatNumber } from '@/lib/format';
|
||||||
|
import { Fragment } from 'react';
|
||||||
import type { Cutting } from './columns';
|
import type { Cutting } from './columns';
|
||||||
|
|
||||||
export function CuttingItemSubRow({ cutting }: { cutting: Cutting }) {
|
export function CuttingItemSubRow({ cutting }: { cutting: Cutting }) {
|
||||||
@ -23,8 +23,8 @@ export function CuttingItemSubRow({ cutting }: { cutting: Cutting }) {
|
|||||||
const comboId = item.combination_id!;
|
const comboId = item.combination_id!;
|
||||||
|
|
||||||
if (!comboGroups[comboId]) {
|
if (!comboGroups[comboId]) {
|
||||||
comboGroups[comboId] = [];
|
comboGroups[comboId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
comboGroups[comboId].push(item);
|
comboGroups[comboId].push(item);
|
||||||
});
|
});
|
||||||
@ -35,8 +35,8 @@ comboGroups[comboId] = [];
|
|||||||
item.raw_material_price?.raw_material?.name ?? 'BING';
|
item.raw_material_price?.raw_material?.name ?? 'BING';
|
||||||
|
|
||||||
if (!acc[name]) {
|
if (!acc[name]) {
|
||||||
acc[name] = [];
|
acc[name] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[name].push(item);
|
acc[name].push(item);
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ acc[name] = [];
|
|||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
|||||||
@ -1,11 +1,27 @@
|
|||||||
import { Head, Link, router } from '@inertiajs/react';
|
import { Head, Link, router } from '@inertiajs/react';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { CardTable } from '@/components/data-display';
|
import { CardTable } from '@/components/data-display';
|
||||||
import { DeleteConfirmDialog } from '@/components/dialogs';
|
import { DeleteConfirmDialog } from '@/components/dialogs';
|
||||||
|
import { FilterPopover } from '@/components/data-display';
|
||||||
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
||||||
import { PageHeader } from '@/components/layout';
|
import { PageHeader } from '@/components/layout';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Combobox,
|
||||||
|
ComboboxContent,
|
||||||
|
ComboboxEmpty,
|
||||||
|
ComboboxInput,
|
||||||
|
ComboboxItem,
|
||||||
|
ComboboxList,
|
||||||
|
} from '@/components/ui/combobox';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
import { useCan } from '@/hooks/use-can';
|
import { useCan } from '@/hooks/use-can';
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
@ -26,9 +42,21 @@ type Props = {
|
|||||||
per_page: number;
|
per_page: number;
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
filters: {
|
||||||
|
product_name?: string;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
filterOptions: {
|
||||||
|
statusOptions: Array<{ value: string; label: string }>;
|
||||||
|
productNames: Array<{ product_name: string }>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CuttingIndex({ cuttings }: Props) {
|
export default function CuttingIndex({
|
||||||
|
cuttings,
|
||||||
|
filters,
|
||||||
|
filterOptions,
|
||||||
|
}: Props) {
|
||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
const [deleting, setDeleting] = useState<Cutting | null>(null);
|
const [deleting, setDeleting] = useState<Cutting | null>(null);
|
||||||
const expand = useCardTableExpand(true);
|
const expand = useCardTableExpand(true);
|
||||||
@ -42,14 +70,22 @@ export default function CuttingIndex({ cuttings }: Props) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
search,
|
search,
|
||||||
|
filterOpen,
|
||||||
|
setFilterOpen,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilter,
|
||||||
|
clearFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => cuttingIndex.url(),
|
route: () => cuttingIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
|
filterWithParams: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasActiveFilters = Boolean(filters.product_name || filters.status);
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
return;
|
return;
|
||||||
@ -60,6 +96,80 @@ export default function CuttingIndex({ cuttings }: Props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedProductName = useMemo(
|
||||||
|
() =>
|
||||||
|
filterOptions.productNames.find(
|
||||||
|
(p) => p.product_name === filters.product_name,
|
||||||
|
) ?? null,
|
||||||
|
[filterOptions.productNames, filters.product_name],
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterToolbar = (
|
||||||
|
<FilterPopover
|
||||||
|
open={filterOpen}
|
||||||
|
onOpenChange={setFilterOpen}
|
||||||
|
filters={filters}
|
||||||
|
hasActiveFilters={hasActiveFilters}
|
||||||
|
onClear={clearFilters}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-xs text-muted-foreground">
|
||||||
|
Nama Produk
|
||||||
|
</label>
|
||||||
|
<Combobox
|
||||||
|
items={filterOptions.productNames}
|
||||||
|
itemToStringLabel={(product) => product.product_name}
|
||||||
|
value={selectedProductName}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
applyFilter(
|
||||||
|
'product_name',
|
||||||
|
value ? value.product_name : '',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ComboboxInput
|
||||||
|
placeholder="Pilih nama produk..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ComboboxContent>
|
||||||
|
<ComboboxEmpty>
|
||||||
|
Tidak ada produk ditemukan.
|
||||||
|
</ComboboxEmpty>
|
||||||
|
<ComboboxList>
|
||||||
|
{(product) => (
|
||||||
|
<ComboboxItem value={product}>
|
||||||
|
{product.product_name}
|
||||||
|
</ComboboxItem>
|
||||||
|
)}
|
||||||
|
</ComboboxList>
|
||||||
|
</ComboboxContent>
|
||||||
|
</Combobox>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-xs text-muted-foreground">
|
||||||
|
Status
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
value={filters.status ?? ''}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
applyFilter('status', value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue placeholder="Semua status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{filterOptions.statusOptions.map((opt) => (
|
||||||
|
<SelectItem key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</FilterPopover>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head title="Cutting" />
|
<Head title="Cutting" />
|
||||||
@ -87,6 +197,7 @@ export default function CuttingIndex({ cuttings }: Props) {
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
|
|
||||||
|
toolbar={filterToolbar}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
|
|||||||
183
resources/js/pages/admin/manage/cutting/show.tsx
Normal file
183
resources/js/pages/admin/manage/cutting/show.tsx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import { Head, Link } from '@inertiajs/react';
|
||||||
|
import { ArrowLeft } from 'lucide-react';
|
||||||
|
import { ImagePreviewButton } from '@/components/dialogs';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import { formatNumber } from '@/lib/format';
|
||||||
|
import { index as cuttingIndex } from '@/routes/admin/manage/cuttings';
|
||||||
|
import type { Cutting } from './columns';
|
||||||
|
import { CuttingItemSubRow } from './cutting-sub-row';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
cutting: Cutting;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS_BADGE_CLASSES: Record<string, string> = {
|
||||||
|
completed: 'bg-green-100 text-green-800 hover:bg-green-100',
|
||||||
|
cancelled: 'bg-red-100 text-red-800 hover:bg-red-100',
|
||||||
|
in_progress: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-100',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CuttingShow({ cutting }: Props) {
|
||||||
|
const items = cutting.cutting_materials ?? [];
|
||||||
|
const result = cutting.cutting_results?.[0];
|
||||||
|
const totalUsage = items.reduce(
|
||||||
|
(sum, item) => sum + Number(item.material_usage),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const statusBadgeClass =
|
||||||
|
STATUS_BADGE_CLASSES[cutting.status] ?? STATUS_BADGE_CLASSES.in_progress;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head title={`Cutting #${cutting.id}`} />
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8">
|
||||||
|
<div className="mb-8 text-center">
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight">
|
||||||
|
Detail Cutting
|
||||||
|
</h1>
|
||||||
|
<p className="mt-1 text-muted-foreground">
|
||||||
|
Rincian data proses cutting
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="overflow-hidden rounded-lg border bg-card shadow-sm">
|
||||||
|
<div className="border-b bg-muted/30 p-6">
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<h2 className="text-xl font-semibold">
|
||||||
|
Cutting #{cutting.id}
|
||||||
|
</h2>
|
||||||
|
<Badge variant="secondary" className={statusBadgeClass}>
|
||||||
|
{cutting.status === 'completed'
|
||||||
|
? 'Selesai'
|
||||||
|
: cutting.status === 'cancelled'
|
||||||
|
? 'Dibatalkan'
|
||||||
|
: 'Dikerjakan'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 space-y-1 text-sm text-muted-foreground">
|
||||||
|
<p>{cutting.formatted_created_at}</p>
|
||||||
|
<p>
|
||||||
|
Oleh{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{cutting.created_by?.user_profile?.full_name ?? '-'}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{cutting.description && (
|
||||||
|
<p className="mt-3 text-sm">{cutting.description}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cutting.photo_url && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<ImagePreviewButton
|
||||||
|
srcs={[cutting.photo_conversion_url ?? cutting.photo_url]}
|
||||||
|
modalSrc={cutting.photo_url}
|
||||||
|
title={result?.product_name ?? `Cutting #${cutting.id}`}
|
||||||
|
description={cutting.description ?? ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{result && (
|
||||||
|
<div className="border-b p-6">
|
||||||
|
<h3 className="mb-3 text-sm font-semibold">Hasil Produk</h3>
|
||||||
|
<div className="overflow-x-auto rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Produk</TableHead>
|
||||||
|
<TableHead className="text-right">Hasil</TableHead>
|
||||||
|
<TableHead className="text-right">Sample</TableHead>
|
||||||
|
<TableHead className="text-right">Diluar Sample</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{result.product_name ?? '-'}</TableCell>
|
||||||
|
<TableCell className="text-right tabular-nums">
|
||||||
|
{result.cutting_result ?? '-'} pcs
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right tabular-nums">
|
||||||
|
{result.sample ?? '-'} pcs
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right tabular-nums">
|
||||||
|
{result.original_outside_sample ?? '-'} pcs
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{items.length > 0 && (
|
||||||
|
<div className="border-b p-6">
|
||||||
|
<h3 className="mb-3 text-sm font-semibold">Bahan Baku</h3>
|
||||||
|
|
||||||
|
<div className="pb-2">
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div className="rounded-lg border p-3">
|
||||||
|
<p className="text-xs text-muted-foreground">Total Pemakaian</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold text-primary">
|
||||||
|
{formatNumber(totalUsage)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{result?.cutting_result && (
|
||||||
|
<div className="rounded-lg border p-3">
|
||||||
|
<p className="text-xs text-muted-foreground">Total Hasil Cutting</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold text-primary">
|
||||||
|
{formatNumber(result.cutting_result)} pcs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{cutting.formatted_cost_per_unit && (
|
||||||
|
<div className="rounded-lg border p-3">
|
||||||
|
<p className="text-xs text-muted-foreground">Biaya Per Produk</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold text-primary">
|
||||||
|
{cutting.formatted_cost_per_unit}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{cutting.formatted_total_material_cost && (
|
||||||
|
<div className="rounded-lg border p-3">
|
||||||
|
<p className="text-xs text-muted-foreground">Total Biaya Bahan</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold text-primary">
|
||||||
|
{cutting.formatted_total_material_cost}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<i className="text-xs md:hidden">Geser kesamping untuk melihat lebih banyak</i>
|
||||||
|
<CuttingItemSubRow cutting={cutting} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 flex items-center justify-between">
|
||||||
|
<Button variant="outline" asChild>
|
||||||
|
<Link href={cuttingIndex.url()}>
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
Kembali
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Data ini dibagikan dari sistem DST Collection
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import { Plus } from 'lucide-react';
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { CardTable } from '@/components/data-display';
|
import { CardTable } from '@/components/data-display';
|
||||||
import { DeleteConfirmDialog } from '@/components/dialogs';
|
import { DeleteConfirmDialog } from '@/components/dialogs';
|
||||||
|
import type { ImagePreviewItem } from '@/components/dialogs';
|
||||||
import { FilterPopover } from '@/components/data-display';
|
import { FilterPopover } from '@/components/data-display';
|
||||||
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
||||||
import { PageHeader } from '@/components/layout';
|
import { PageHeader } from '@/components/layout';
|
||||||
@ -103,6 +104,22 @@ export default function ProductIndex({ products, categories, productNames, filte
|
|||||||
[productNames],
|
[productNames],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const allPreviewItems: ImagePreviewItem[] = useMemo(
|
||||||
|
() =>
|
||||||
|
products.data.flatMap((product) =>
|
||||||
|
(product.product_variants ?? [])
|
||||||
|
.filter((v) => v.photo_urls?.length > 0)
|
||||||
|
.map((v) => ({
|
||||||
|
id: `${product.id}-${v.id}`,
|
||||||
|
src: v.photo_urls[0],
|
||||||
|
sources: v.photo_urls,
|
||||||
|
title: v.name,
|
||||||
|
description: `Stok Bagus: ${v.formatted_stock} | Stok Reject: ${v.formatted_reject_stock} | Stok Ecer: ${v.formatted_retail_stock}`,
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
[products.data],
|
||||||
|
);
|
||||||
|
|
||||||
const selectedCategory = useMemo(
|
const selectedCategory = useMemo(
|
||||||
() => categories.find((c) => String(c.id) === filters.category) ?? null,
|
() => categories.find((c) => String(c.id) === filters.category) ?? null,
|
||||||
[categories, filters.category],
|
[categories, filters.category],
|
||||||
@ -311,6 +328,7 @@ export default function ProductIndex({ products, categories, productNames, filte
|
|||||||
renderSubContent={(product) => (
|
renderSubContent={(product) => (
|
||||||
<VariantSubRow
|
<VariantSubRow
|
||||||
product={product}
|
product={product}
|
||||||
|
allPreviewItems={allPreviewItems}
|
||||||
onEditVariant={(p, v) => {
|
onEditVariant={(p, v) => {
|
||||||
router.visit(
|
router.visit(
|
||||||
variantEdit.url({
|
variantEdit.url({
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { router } from '@inertiajs/react';
|
|||||||
import { ArrowRightLeft, Pencil, ScrollText, Trash2 } from 'lucide-react';
|
import { ArrowRightLeft, Pencil, ScrollText, Trash2 } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ImagePreviewButton } from '@/components/dialogs';
|
import { ImagePreviewButton } from '@/components/dialogs';
|
||||||
|
import type { ImagePreviewItem } from '@/components/dialogs';
|
||||||
import { RowActions } from '@/components/data-display';
|
import { RowActions } from '@/components/data-display';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -20,10 +21,12 @@ import { TransferStockDialog } from './transfer-stock-dialog';
|
|||||||
|
|
||||||
export function VariantSubRow({
|
export function VariantSubRow({
|
||||||
product,
|
product,
|
||||||
|
allPreviewItems,
|
||||||
onEditVariant,
|
onEditVariant,
|
||||||
onDeleteVariantClick,
|
onDeleteVariantClick,
|
||||||
}: {
|
}: {
|
||||||
product: Product;
|
product: Product;
|
||||||
|
allPreviewItems: ImagePreviewItem[];
|
||||||
onEditVariant: (product: Product, variant: ProductVariant) => void;
|
onEditVariant: (product: Product, variant: ProductVariant) => void;
|
||||||
onDeleteVariantClick: (product: Product, variant: ProductVariant) => void;
|
onDeleteVariantClick: (product: Product, variant: ProductVariant) => void;
|
||||||
}) {
|
}) {
|
||||||
@ -94,6 +97,8 @@ export function VariantSubRow({
|
|||||||
modalSrcs={variant.photo_urls}
|
modalSrcs={variant.photo_urls}
|
||||||
title={variant.name}
|
title={variant.name}
|
||||||
description={`Stok Bagus: ${variant.formatted_stock} | Stok Reject: ${variant.formatted_reject_stock} | Stok Ecer: ${variant.formatted_retail_stock}`}
|
description={`Stok Bagus: ${variant.formatted_stock} | Stok Reject: ${variant.formatted_reject_stock} | Stok Ecer: ${variant.formatted_retail_stock}`}
|
||||||
|
items={allPreviewItems}
|
||||||
|
startIndex={allPreviewItems.findIndex((item) => item.id === `${product.id}-${variant.id}`)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Plus } from 'lucide-react';
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { CardTable } from '@/components/data-display';
|
import { CardTable } from '@/components/data-display';
|
||||||
import { DeleteConfirmDialog } from '@/components/dialogs';
|
import { DeleteConfirmDialog } from '@/components/dialogs';
|
||||||
|
import type { ImagePreviewItem } from '@/components/dialogs';
|
||||||
import { FilterPopover } from '@/components/data-display';
|
import { FilterPopover } from '@/components/data-display';
|
||||||
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
||||||
import { PageHeader } from '@/components/layout';
|
import { PageHeader } from '@/components/layout';
|
||||||
@ -91,6 +92,21 @@ export default function RawMaterialIndex({ rawMaterials, rawMaterialNames, filte
|
|||||||
[rawMaterialNames],
|
[rawMaterialNames],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const allPreviewItems: ImagePreviewItem[] = useMemo(
|
||||||
|
() =>
|
||||||
|
rawMaterials.data.flatMap((rm) =>
|
||||||
|
(rm.raw_material_prices ?? [])
|
||||||
|
.filter((v) => v.photo_url)
|
||||||
|
.map((v) => ({
|
||||||
|
id: `${rm.id}-${v.id}`,
|
||||||
|
src: v.photo_url,
|
||||||
|
title: v.variant,
|
||||||
|
description: `Stok: ${v.formatted_stock}`,
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
[rawMaterials.data],
|
||||||
|
);
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
return;
|
return;
|
||||||
@ -246,7 +262,10 @@ export default function RawMaterialIndex({ rawMaterials, rawMaterialNames, filte
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderSubContent={(rawMaterial) => (
|
renderSubContent={(rawMaterial) => (
|
||||||
<RawMaterialVariantSubRow rawMaterial={rawMaterial} />
|
<RawMaterialVariantSubRow
|
||||||
|
rawMaterial={rawMaterial}
|
||||||
|
allPreviewItems={allPreviewItems}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Pencil, Trash2 } from 'lucide-react';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ConfirmDialog } from '@/components/dialogs';
|
import { ConfirmDialog } from '@/components/dialogs';
|
||||||
import { ImagePreviewButton } from '@/components/dialogs';
|
import { ImagePreviewButton } from '@/components/dialogs';
|
||||||
|
import type { ImagePreviewItem } from '@/components/dialogs';
|
||||||
import { RowActions } from '@/components/data-display';
|
import { RowActions } from '@/components/data-display';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -23,8 +24,10 @@ import type { RawMaterial, RawMaterialVariant } from '../columns';
|
|||||||
|
|
||||||
export function RawMaterialVariantSubRow({
|
export function RawMaterialVariantSubRow({
|
||||||
rawMaterial,
|
rawMaterial,
|
||||||
|
allPreviewItems,
|
||||||
}: {
|
}: {
|
||||||
rawMaterial: RawMaterial;
|
rawMaterial: RawMaterial;
|
||||||
|
allPreviewItems: ImagePreviewItem[];
|
||||||
}) {
|
}) {
|
||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
const variants = rawMaterial.raw_material_prices ?? [];
|
const variants = rawMaterial.raw_material_prices ?? [];
|
||||||
@ -91,6 +94,8 @@ export function RawMaterialVariantSubRow({
|
|||||||
modalSrc={variant.photo_url}
|
modalSrc={variant.photo_url}
|
||||||
title={variant.variant}
|
title={variant.variant}
|
||||||
description={`Stok: ${variant.formatted_stock}`}
|
description={`Stok: ${variant.formatted_stock}`}
|
||||||
|
items={allPreviewItems}
|
||||||
|
startIndex={allPreviewItems.findIndex((item) => item.id === `${rawMaterial.id}-${variant.id}`)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
use App\Console\Commands\GeneratePayrollCommand;
|
use App\Console\Commands\GeneratePayrollCommand;
|
||||||
use App\Jobs\CheckAttendancePenaltiesJob;
|
use App\Jobs\CheckAttendancePenaltiesJob;
|
||||||
|
use App\Jobs\CleanupOrphanedMediaJob;
|
||||||
use Illuminate\Support\Facades\Schedule;
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
Schedule::command(GeneratePayrollCommand::class)->monthlyOn(1, '00:00');
|
Schedule::command(GeneratePayrollCommand::class)->monthlyOn(1, '00:00');
|
||||||
Schedule::job(new CheckAttendancePenaltiesJob)->dailyAt('00:00');
|
Schedule::job(new CheckAttendancePenaltiesJob)->dailyAt('00:00');
|
||||||
|
Schedule::job(new CleanupOrphanedMediaJob)->monthlyOn(1, '01:00');
|
||||||
|
|||||||
@ -64,7 +64,7 @@
|
|||||||
Route::prefix('manage')->name('admin.manage.')->group(function () {
|
Route::prefix('manage')->name('admin.manage.')->group(function () {
|
||||||
Route::resource('purchases', PurchaseController::class)->except(['show'])->middleware('permission:purchases.view|purchases.create|purchases.update|purchases.delete');
|
Route::resource('purchases', PurchaseController::class)->except(['show'])->middleware('permission:purchases.view|purchases.create|purchases.update|purchases.delete');
|
||||||
|
|
||||||
Route::resource('cuttings', CuttingController::class)->except(['show'])->middleware('permission:cuttings.view|cuttings.create|cuttings.update|cuttings.delete');
|
Route::resource('cuttings', CuttingController::class)->middleware('permission:cuttings.view|cuttings.create|cuttings.update|cuttings.delete');
|
||||||
|
|
||||||
Route::resource('transactions', TransactionController::class)->except(['show'])->middleware('permission:orders.view|orders.create|orders.update|orders.delete');
|
Route::resource('transactions', TransactionController::class)->except(['show'])->middleware('permission:orders.view|orders.create|orders.update|orders.delete');
|
||||||
Route::patch('transactions/{transaction}/status', [TransactionController::class, 'updateStatus'])->name('transactions.updateStatus')->middleware('permission:orders.update');
|
Route::patch('transactions/{transaction}/status', [TransactionController::class, 'updateStatus'])->name('transactions.updateStatus')->middleware('permission:orders.update');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user