refactor: replace hardcoded URL strings with dynamic route helpers across services and frontend components.

This commit is contained in:
Yoga Pangestu 2026-06-23 10:49:27 +07:00
parent 2f8ece4b0a
commit dfd2c5560b
91 changed files with 358 additions and 230 deletions

View File

@ -2,7 +2,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Enums\EmployeeStatus;
use App\Models\Attendance; use App\Models\Attendance;
use App\Models\Employee; use App\Models\Employee;
use App\Services\System\PushNotificationService; use App\Services\System\PushNotificationService;
@ -31,8 +30,7 @@ public function handle(PushNotificationService $pushNotificationService): int
$scheduledCheckIn = Carbon::createFromFormat('Y-m-d H:i', $now->format('Y-m-d').' '.$scheduledTime); $scheduledCheckIn = Carbon::createFromFormat('Y-m-d H:i', $now->format('Y-m-d').' '.$scheduledTime);
$employees = Employee::query() $employees = Employee::query()
->where('status', EmployeeStatus::ACTIVE) ->whereHas('user', fn ($query) => $query->active())
->whereNotNull('user_id')
->get(); ->get();
$sentCount = 0; $sentCount = 0;
@ -51,7 +49,7 @@ public function handle(PushNotificationService $pushNotificationService): int
'⏰ Pengingat Presensi', '⏰ Pengingat Presensi',
"Jangan lupa melakukan presensi masuk sebelum pukul {$scheduledTime}.", "Jangan lupa melakukan presensi masuk sebelum pukul {$scheduledTime}.",
$employee->user_id, $employee->user_id,
'/admin/hr/attendances', route('admin.hr.attendances.index'),
); );
$sentCount++; $sentCount++;

View File

@ -97,7 +97,7 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user):
'💰 Setoran Kas', '💰 Setoran Kas',
"Setoran kas baru {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.", "Setoran kas baru {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.",
['owner', 'developer', 'admin-toko'], ['owner', 'developer', 'admin-toko'],
'/admin/finance/cash', route('admin.finance.cash.index'),
); );
return $transaction; return $transaction;
@ -141,7 +141,7 @@ public function withdraw(CashAccount $cashAccount, array $validated, User $user)
'🏦 Tarik Kas', '🏦 Tarik Kas',
"Tarik kas {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.", "Tarik kas {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.",
['owner', 'developer', 'admin-toko'], ['owner', 'developer', 'admin-toko'],
'/admin/finance/cash', route('admin.finance.cash.index'),
); );
return $transaction; return $transaction;
@ -250,7 +250,7 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v
'✏️ Transaksi Kas Diperbarui', '✏️ Transaksi Kas Diperbarui',
"Transaksi kas dengan keterangan {$transaction->description} diperbarui menjadi senilai {$transaction->amount_formatted}.", "Transaksi kas dengan keterangan {$transaction->description} diperbarui menjadi senilai {$transaction->amount_formatted}.",
['owner', 'developer', 'admin-toko'], ['owner', 'developer', 'admin-toko'],
'/admin/finance/cash', route('admin.finance.cash.index'),
); );
} }
@ -279,7 +279,7 @@ public function deleteTransaction(CashTransaction $transaction): void
'🗑️ Transaksi Kas Dihapus', '🗑️ Transaksi Kas Dihapus',
"Transaksi kas senilai {$transaction->amount_formatted} dengan keterangan {$transaction->description} telah dihapus.", "Transaksi kas senilai {$transaction->amount_formatted} dengan keterangan {$transaction->description} telah dihapus.",
['owner', 'developer', 'admin-toko'], ['owner', 'developer', 'admin-toko'],
'/admin/finance/cash', route('admin.finance.cash.index'),
); );
} }

View File

@ -90,7 +90,7 @@ public function create(array $validated, User $user): void
'💰 Pengajuan Kasbon Baru', '💰 Pengajuan Kasbon Baru',
"Karyawan {$user->profil?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.", "Karyawan {$user->profil?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/finance/employee-advances', route('admin.finance.employee-advances.index'),
); );
} }
@ -111,7 +111,7 @@ public function update(EmployeeAdvance $employeeAdvance, array $validated, User
'✏️ Kasbon Diperbarui', '✏️ Kasbon Diperbarui',
"Kasbon sebesar {$employeeAdvance->amount_formatted} telah diperbarui oleh {$user->profile?->full_name}.", "Kasbon sebesar {$employeeAdvance->amount_formatted} telah diperbarui oleh {$user->profile?->full_name}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/finance/employee-advances', route('admin.finance.employee-advances.index'),
); );
} }
@ -126,7 +126,7 @@ public function delete(EmployeeAdvance $employeeAdvance, User $user): void
'🗑️ Kasbon Dihapus', '🗑️ Kasbon Dihapus',
"Kasbon sebesar {$amount} dengan keterangan {$description} telah dihapus.", "Kasbon sebesar {$amount} dengan keterangan {$description} telah dihapus.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/finance/employee-advances', route('admin.finance.employee-advances.index'),
); );
} }
@ -161,7 +161,7 @@ public function approve(EmployeeAdvance $employeeAdvance, User $user): void
'💰 Kasbon Disetujui', '💰 Kasbon Disetujui',
"Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} telah disetujui.", "Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} telah disetujui.",
$employeeAdvance->employee->user_id, $employeeAdvance->employee->user_id,
'/admin/finance/employee-advances', route('admin.finance.employee-advances.index'),
); );
} }
} }
@ -187,7 +187,7 @@ public function reject(EmployeeAdvance $employeeAdvance, string $reason, User $u
'💰 Kasbon Ditolak', '💰 Kasbon Ditolak',
"Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} ditolak dengan alasan: {$reason}.", "Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} ditolak dengan alasan: {$reason}.",
$employeeAdvance->employee->user_id, $employeeAdvance->employee->user_id,
'/admin/finance/employee-advances', route('admin.finance.employee-advances.index'),
); );
} }
} }

View File

@ -85,7 +85,7 @@ public function create(array $validated, User $user): void
'💸 Pengeluaran Baru', '💸 Pengeluaran Baru',
"Pengeluaran baru sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dicatat oleh {$user->profile?->full_name}.", "Pengeluaran baru sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dicatat oleh {$user->profile?->full_name}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/finance/expenses', route('admin.finance.expenses.index'),
); );
} }
@ -118,7 +118,7 @@ public function update(Expense $expense, array $validated): void
'✏️ Pengeluaran Diperbarui', '✏️ Pengeluaran Diperbarui',
"Pengeluaran dengan keterangan {$expense->description} diperbarui menjadi sebesar {$expense->amount_formatted}.", "Pengeluaran dengan keterangan {$expense->description} diperbarui menjadi sebesar {$expense->amount_formatted}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/finance/expenses', route('admin.finance.expenses.index'),
); );
} }
@ -137,7 +137,7 @@ public function delete(Expense $expense): void
'🗑️ Pengeluaran Dihapus', '🗑️ Pengeluaran Dihapus',
"Pengeluaran sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dihapus.", "Pengeluaran sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dihapus.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/finance/expenses', route('admin.finance.expenses.index'),
); );
} }

View File

@ -224,7 +224,7 @@ public function addAdjustment(Payroll $payroll, array $validated, User $user): v
"📊 Penyesuaian Gaji: {$typeLabel}", "📊 Penyesuaian Gaji: {$typeLabel}",
"Gaji periode {$payroll->payrollPeriod->period_label} disesuaikan: {$typeLabel} sebesar {$formattedAmount} ({$validated['description']}).", "Gaji periode {$payroll->payrollPeriod->period_label} disesuaikan: {$typeLabel} sebesar {$formattedAmount} ({$validated['description']}).",
$payroll->employee->user_id, $payroll->employee->user_id,
'/admin/finance/payrolls', route('admin.finance.payroll.index'),
); );
} }
} }
@ -252,7 +252,7 @@ public function updateAdjustment(PayrollAdjustment $adjustment, array $validated
"📊 Penyesuaian Gaji Diperbarui: {$typeLabel}", "📊 Penyesuaian Gaji Diperbarui: {$typeLabel}",
"Penyesuaian gaji Anda untuk periode {$payroll->payrollPeriod->period_label} diperbarui: {$typeLabel} menjadi {$formattedAmount} ({$validated['description']}).", "Penyesuaian gaji Anda untuk periode {$payroll->payrollPeriod->period_label} diperbarui: {$typeLabel} menjadi {$formattedAmount} ({$validated['description']}).",
$payroll->employee->user_id, $payroll->employee->user_id,
'/admin/finance/payrolls', route('admin.finance.payroll.index'),
); );
} }
} }
@ -275,7 +275,7 @@ public function deleteAdjustment(PayrollAdjustment $adjustment): void
'📊 Penyesuaian Gaji Dihapus', '📊 Penyesuaian Gaji Dihapus',
"Penyesuaian gaji Anda untuk periode {$payroll->payrollPeriod->period_label} telah dihapus.", "Penyesuaian gaji Anda untuk periode {$payroll->payrollPeriod->period_label} telah dihapus.",
$payroll->employee->user_id, $payroll->employee->user_id,
'/admin/finance/payrolls', route('admin.finance.payroll.index'),
); );
} }
} }
@ -325,7 +325,7 @@ public function pay(Payroll $payroll, User $user): void
'💸 Gaji Dibayarkan', '💸 Gaji Dibayarkan',
"Gaji Anda untuk periode {$payroll->payrollPeriod->period_label} senilai {$payroll->total_amount_formatted} telah dibayarkan.", "Gaji Anda untuk periode {$payroll->payrollPeriod->period_label} senilai {$payroll->total_amount_formatted} telah dibayarkan.",
$payroll->employee->user_id, $payroll->employee->user_id,
'/admin/finance/payrolls', route('admin.finance.payroll.index'),
); );
} }
} }

View File

@ -105,7 +105,7 @@ public function checkIn(array $validated, User $user): void
'⏰ Presensi Masuk', '⏰ Presensi Masuk',
"Karyawan {$user->profile?->full_name} melakukan presensi masuk.", "Karyawan {$user->profile?->full_name} melakukan presensi masuk.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/hr/attendances', route('admin.hr.attendances.index'),
); );
} }
@ -154,7 +154,7 @@ public function checkOut(array $validated, User $user): void
'⏰ Presensi Pulang', '⏰ Presensi Pulang',
"Karyawan {$user->profile?->full_name} melakukan presensi pulang (Durasi kerja: ".round($workDurationMinutes / 60, 1).' jam).', "Karyawan {$user->profile?->full_name} melakukan presensi pulang (Durasi kerja: ".round($workDurationMinutes / 60, 1).' jam).',
['owner', 'developer'], ['owner', 'developer'],
'/admin/hr/attendances', route('admin.hr.attendances.index'),
); );
} }
@ -172,7 +172,7 @@ public function delete(Attendance $attendance): void
'🗑️ Presensi Dihapus', '🗑️ Presensi Dihapus',
"Data presensi {$employeeName} tanggal {$date} telah dihapus.", "Data presensi {$employeeName} tanggal {$date} telah dihapus.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/hr/attendances', route('admin.hr.attendances.index'),
); );
} }
} }

View File

@ -83,7 +83,7 @@ public function create(array $validated, User $user): void
'🏖️ Pengajuan Cuti Baru', '🏖️ Pengajuan Cuti Baru',
"Karyawan {$user->profile?->full_name} mengajukan cuti selama {$leaveRequest->total_days} hari mulai dari tanggal {$leaveRequest->start_date_formatted}.", "Karyawan {$user->profile?->full_name} mengajukan cuti selama {$leaveRequest->total_days} hari mulai dari tanggal {$leaveRequest->start_date_formatted}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/hr/leave-requests', route('admin.hr.leave_requests.index'),
); );
} }
@ -107,7 +107,7 @@ public function update(LeaveRequest $leaveRequest, array $validated, User $user)
'✏️ Pengajuan Cuti Diperbarui', '✏️ Pengajuan Cuti Diperbarui',
"Pengajuan cuti oleh {$user->profile?->full_name} telah diperbarui.", "Pengajuan cuti oleh {$user->profile?->full_name} telah diperbarui.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/hr/leave-requests', route('admin.hr.leave_requests.index'),
); );
} }
@ -122,7 +122,7 @@ public function delete(LeaveRequest $leaveRequest): void
'🗑️ Pengajuan Cuti Dihapus', '🗑️ Pengajuan Cuti Dihapus',
"Pengajuan cuti {$totalDays} hari oleh {$employeeName} telah dihapus.", "Pengajuan cuti {$totalDays} hari oleh {$employeeName} telah dihapus.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/hr/leave-requests', route('admin.hr.leave_requests.index'),
); );
} }
@ -142,7 +142,7 @@ public function approve(LeaveRequest $leaveRequest, User $user): void
'🏖️ Pengajuan Cuti Disetujui', '🏖️ Pengajuan Cuti Disetujui',
"Pengajuan cuti Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) telah disetujui.", "Pengajuan cuti Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) telah disetujui.",
$leaveRequest->employee->user_id, $leaveRequest->employee->user_id,
'/admin/hr/leave-requests', route('admin.hr.leave_requests.index'),
); );
} }
} }
@ -168,7 +168,7 @@ public function reject(LeaveRequest $leaveRequest, string $reason, User $user):
'🏖️ Pengajuan Cuti Ditolak', '🏖️ Pengajuan Cuti Ditolak',
"Pengajuan cuti Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) ditolak dengan alasan: '{$reason}'.", "Pengajuan cuti Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) ditolak dengan alasan: '{$reason}'.",
$leaveRequest->employee->user_id, $leaveRequest->employee->user_id,
'/admin/hr/leave-requests', route('admin.hr.leave_requests.index'),
); );
} }
} }

View File

@ -476,7 +476,7 @@ public function create(array $validated, User $user): Cutting
'✂️ Proses Cutting Baru', '✂️ Proses Cutting Baru',
"Proses cutting dengan deskripsi ({$cutting->description}) telah dimulai oleh {$user->profile?->full_name}.", "Proses cutting dengan deskripsi ({$cutting->description}) telah dimulai oleh {$user->profile?->full_name}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/cuttings', route('admin.manage.cuttings.index'),
); );
return $cutting; return $cutting;
@ -536,7 +536,7 @@ public function update(Cutting $cutting, array $validated): void
'✏️ Proses Cutting Diperbarui', '✏️ Proses Cutting Diperbarui',
"Proses cutting dengan deskripsi '{$description}' telah diperbarui.", "Proses cutting dengan deskripsi '{$description}' telah diperbarui.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/cuttings', route('admin.manage.cuttings.index'),
); );
} }
@ -568,7 +568,7 @@ public function delete(Cutting $cutting): void
'🗑️ Proses Cutting Dihapus', '🗑️ Proses Cutting Dihapus',
"Proses cutting dengan deskripsi {$description} telah dihapus.", "Proses cutting dengan deskripsi {$description} telah dihapus.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/cuttings', route('admin.manage.cuttings.index'),
); );
} }
@ -655,7 +655,7 @@ public function transitionStatus(
$title, $title,
$message, $message,
$roles, $roles,
'/admin/manage/cuttings', route('admin.manage.cuttings.index'),
); );
} }

View File

@ -483,7 +483,7 @@ public function create(array $validated, User $user): Order
'📦 Pesanan Baru', '📦 Pesanan Baru',
"Pesanan baru {$order->order_number} senilai {$order->total_amount_formatted} telah dibuat oleh {$user->profile?->full_name}.", "Pesanan baru {$order->order_number} senilai {$order->total_amount_formatted} telah dibuat oleh {$user->profile?->full_name}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/orders', route('admin.manage.orders.index'),
); );
return $order; return $order;
@ -563,7 +563,7 @@ public function update(Order $order, array $validated): void
'✏️ Pesanan Diperbarui', '✏️ Pesanan Diperbarui',
"Pesanan {$order->order_number} senilai {$order->total_amount_formatted} telah diperbarui.", "Pesanan {$order->order_number} senilai {$order->total_amount_formatted} telah diperbarui.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/orders', route('admin.manage.orders.index'),
); );
} }
@ -593,7 +593,7 @@ public function delete(Order $order): void
'🗑️ Pesanan Dihapus', '🗑️ Pesanan Dihapus',
"Pesanan {$orderNumber} senilai {$order->total_amount_formatted} telah dihapus.", "Pesanan {$orderNumber} senilai {$order->total_amount_formatted} telah dihapus.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/orders', route('admin.manage.orders.index'),
); );
} }
@ -627,7 +627,7 @@ public function transitionStatus(Order $order, OrderStatus $status): void
'📦 Status Pesanan Diubah', '📦 Status Pesanan Diubah',
"Pesanan {$order->order_number} diubah statusnya menjadi {$status->label()}.", "Pesanan {$order->order_number} diubah statusnya menjadi {$status->label()}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/orders', route('admin.manage.orders.index'),
); );
} }

View File

@ -253,7 +253,7 @@ public function create(array $validated, User $user): Purchase
'🛒 Belanja Baru', '🛒 Belanja Baru',
"Pembelian dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} telah ditambahkan oleh {$user->profile?->full_name}.", "Pembelian dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} telah ditambahkan oleh {$user->profile?->full_name}.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/purchases', route('admin.manage.purchases.index'),
); );
return $purchase; return $purchase;
@ -301,7 +301,7 @@ public function update(Purchase $purchase, array $validated): void
'✏️ Belanja Diperbarui', '✏️ Belanja Diperbarui',
"Pembelian dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} telah diperbarui.", "Pembelian dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} telah diperbarui.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/purchases', route('admin.manage.purchases.index'),
); );
} }
@ -325,7 +325,7 @@ public function delete(Purchase $purchase): void
'🗑️ Belanja Dihapus', '🗑️ Belanja Dihapus',
"Pembelian dari supplier {$supplierName} senilai {$purchase->total_formatted} telah dihapus.", "Pembelian dari supplier {$supplierName} senilai {$purchase->total_formatted} telah dihapus.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/purchases', route('admin.manage.purchases.index'),
); );
} }

View File

@ -120,7 +120,7 @@ public function submitVerification(
'📦 Verifikasi Stok Menunggu Persetujuan', '📦 Verifikasi Stok Menunggu Persetujuan',
"Cutting dengan deskripsi '{$description}' telah diajukan verifikasi dan menunggu persetujuan owner.", "Cutting dengan deskripsi '{$description}' telah diajukan verifikasi dan menunggu persetujuan owner.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/owner-verifications', route('admin.manage.owner_verifications.index'),
); );
} }
@ -161,7 +161,7 @@ public function approveVerification(
'📦 Stok Cutting Diverifikasi', '📦 Stok Cutting Diverifikasi',
"Cutting dengan deskripsi '{$description}' telah disetujui owner dan stok produk telah ditambahkan ke toko.", "Cutting dengan deskripsi '{$description}' telah disetujui owner dan stok produk telah ditambahkan ke toko.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/owner-verifications', route('admin.manage.owner_verifications.index'),
); );
} }
@ -194,7 +194,7 @@ public function rejectVerification(
'📦 Verifikasi Cutting Ditolak Owner', '📦 Verifikasi Cutting Ditolak Owner',
"Cutting dengan deskripsi '{$description}' ditolak oleh owner dengan alasan: '{$reason}'.", "Cutting dengan deskripsi '{$description}' ditolak oleh owner dengan alasan: '{$reason}'.",
['owner', 'developer'], ['owner', 'developer'],
'/admin/manage/owner-verifications', route('admin.manage.owner_verifications.index'),
); );
if ($cutting->submitted_by_id) { if ($cutting->submitted_by_id) {
@ -202,7 +202,7 @@ public function rejectVerification(
'📦 Verifikasi Cutting Ditolak Owner', '📦 Verifikasi Cutting Ditolak Owner',
"Cutting dengan deskripsi '{$description}' yang Anda verifikasi ditolak oleh owner dengan alasan: '{$reason}'.", "Cutting dengan deskripsi '{$description}' yang Anda verifikasi ditolak oleh owner dengan alasan: '{$reason}'.",
$cutting->submitted_by_id, $cutting->submitted_by_id,
'/admin/manage/owner-verifications', route('admin.manage.owner_verifications.index'),
); );
} }
} }

View File

@ -15,6 +15,7 @@ import {
SidebarRail, SidebarRail,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import admin from '@/routes/admin';
const page = usePage(); const page = usePage();
const { can } = useCan(); const { can } = useCan();
@ -36,52 +37,52 @@ const menuGroups: MenuGroup[] = [
{ {
label: 'Menu', label: 'Menu',
items: [ items: [
{ title: 'Dasbor', href: '/admin/dashboard', icon: LayoutDashboard }, { title: 'Dasbor', href: admin.dashboard.url(), icon: LayoutDashboard },
], ],
}, },
{ {
label: 'Master', label: 'Master',
items: [ items: [
{ title: 'Kategori', href: '/admin/master/categories', icon: FolderTree, permission: 'categories.view' }, { title: 'Kategori', href: admin.master.categories.index.url(), icon: FolderTree, permission: 'categories.view' },
{ title: 'Produk', href: '/admin/master/products', icon: Package, permission: 'products.view' }, { title: 'Produk', href: admin.master.products.index.url(), icon: Package, permission: 'products.view' },
{ title: 'Bahan Baku', href: '/admin/master/raw-materials', icon: Layers, permission: 'raw_materials.view' }, { title: 'Bahan Baku', href: admin.master.raw_materials.index.url(), icon: Layers, permission: 'raw_materials.view' },
{ title: 'Supplier', href: '/admin/master/suppliers', icon: User, permission: 'suppliers.view' }, { title: 'Supplier', href: admin.master.suppliers.index.url(), icon: User, permission: 'suppliers.view' },
{ title: 'Customer', href: '/admin/master/customers', icon: UserCheck, permission: 'customers.view' }, { title: 'Customer', href: admin.master.customers.index.url(), icon: UserCheck, permission: 'customers.view' },
], ],
}, },
{ {
label: 'Kelola', label: 'Kelola',
items: [ items: [
{ title: 'Belanja', href: '/admin/manage/purchases', icon: ShoppingBag, permission: 'purchases.view' }, { title: 'Belanja', href: admin.manage.purchases.index.url(), icon: ShoppingBag, permission: 'purchases.view' },
{ title: 'Cutting', href: '/admin/manage/cuttings', icon: Scissors, permission: 'cuttings.view' }, { title: 'Cutting', href: admin.manage.cuttings.index.url(), icon: Scissors, permission: 'cuttings.view' },
{ title: 'Stok', href: '/admin/manage/stocks', icon: Warehouse, permission: 'stocks.view', badgeKey: 'pendingCuttings' }, { title: 'Stok', href: admin.manage.stocks.index.url(), icon: Warehouse, permission: 'stocks.view', badgeKey: 'pendingCuttings' },
{ title: 'Verifikasi Owner', href: '/admin/manage/owner-verifications', icon: CheckCircle, permission: 'owner_verifications.view', badgeKey: 'pendingOwnerVerifications' }, { title: 'Verifikasi Owner', href: admin.manage.owner_verifications.index.url(), icon: CheckCircle, permission: 'owner_verifications.view', badgeKey: 'pendingOwnerVerifications' },
{ title: 'Pesanan', href: '/admin/manage/orders', icon: ShoppingCart, permission: 'orders.view' }, { title: 'Pesanan', href: admin.manage.orders.index.url(), icon: ShoppingCart, permission: 'orders.view' },
], ],
}, },
{ {
label: 'Keuangan', label: 'Keuangan',
items: [ items: [
{ title: 'Kas Toko', href: '/admin/finance/cash', icon: Wallet, permission: 'cash.view' }, { title: 'Kas Toko', href: admin.finance.cash.index.url(), icon: Wallet, permission: 'cash.view' },
{ title: 'Pengeluaran', href: '/admin/finance/expenses', icon: Receipt, permission: 'expenses.view' }, { title: 'Pengeluaran', href: admin.finance.expenses.index.url(), icon: Receipt, permission: 'expenses.view' },
{ title: 'Kasbon', href: '/admin/finance/employee-advances', icon: Banknote, permission: 'employee_advances.view', badgeKey: 'pendingEmployeeAdvances' }, { title: 'Kasbon', href: admin.finance.employee_advances.index.url(), icon: Banknote, permission: 'employee_advances.view', badgeKey: 'pendingEmployeeAdvances' },
{ title: 'Gaji', href: '/admin/finance/payroll', icon: WalletCards, permission: 'payroll.view' }, { title: 'Gaji', href: admin.finance.payroll.index.url(), icon: WalletCards, permission: 'payroll.view' },
], ],
}, },
{ {
label: 'HR', label: 'HR',
items: [ items: [
{ title: 'Pegawai', href: '/admin/hr/employees', icon: Users, permission: 'employees.view' }, { title: 'Pegawai', href: admin.hr.employees.index.url(), icon: Users, permission: 'employees.view' },
{ title: 'Presensi', href: '/admin/hr/attendances', icon: Clock, permission: 'attendances.view' }, { title: 'Presensi', href: admin.hr.attendances.index.url(), icon: Clock, permission: 'attendances.view' },
{ title: 'Cuti', href: '/admin/hr/leave-requests', icon: CalendarDays, permission: 'leave_requests.view', badgeKey: 'pendingLeaveRequests' }, { title: 'Cuti', href: admin.hr.leave_requests.index.url(), icon: CalendarDays, permission: 'leave_requests.view', badgeKey: 'pendingLeaveRequests' },
], ],
}, },
{ {
label: 'Sistem', label: 'Sistem',
items: [ items: [
{ title: 'Role & Permission', href: '/admin/system/roles', icon: Shield, permission: 'roles.view' }, { title: 'Role & Permission', href: admin.system.roles.index.url(), icon: Shield, permission: 'roles.view' },
{ title: 'Pengaturan', href: '/admin/system/settings', icon: Settings2, permission: 'settings.view' }, { title: 'Pengaturan', href: admin.system.settings.index.url(), icon: Settings2, permission: 'settings.view' },
{ title: 'Log Aktivitas', href: '/admin/system/activity-logs', icon: History, permission: 'activity_logs.view' }, { title: 'Log Aktivitas', href: admin.system.activity_logs.index.url(), icon: History, permission: 'activity_logs.view' },
], ],
}, },
]; ];
@ -93,7 +94,7 @@ const menuGroups: MenuGroup[] = [
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton size="lg" as-child> <SidebarMenuButton size="lg" as-child>
<Link href="/admin/dashboard"> <Link :href="admin.dashboard.url()">
<img <img
v-if="page.props.logo_url" v-if="page.props.logo_url"
:src="page.props.logo_url" :src="page.props.logo_url"

View File

@ -13,6 +13,8 @@ import {
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { usePushNotification } from '@/composables/usePushNotification'; import { usePushNotification } from '@/composables/usePushNotification';
import Separator from './ui/separator/Separator.vue'; import Separator from './ui/separator/Separator.vue';
import { logout as logoutRoute } from '@/routes';
import { profile } from '@/routes/admin/account';
const page = usePage(); const page = usePage();
@ -27,7 +29,7 @@ const initials = computed(() => {
const { isSupported, isSubscribed, isLoading, toggle } = usePushNotification(); const { isSupported, isSubscribed, isLoading, toggle } = usePushNotification();
function logout() { function logout() {
router.post('/auth/logout'); router.post(logoutRoute.url());
} }
</script> </script>
@ -69,7 +71,7 @@ function logout() {
</div> </div>
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem @click="router.visit('/admin/account/profile')"> <DropdownMenuItem @click="router.visit(profile.url())">
<User /> <User />
Profil Profil
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -5,23 +5,25 @@ import { computed } from 'vue';
import AdminLayout from '@/layouts/AdminLayout.vue'; import AdminLayout from '@/layouts/AdminLayout.vue';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { profile, password, appearance } from '@/routes/admin/account';
const page = usePage(); const page = usePage();
const currentPath = computed(() => page.url.split('?')[0]); const currentPath = computed(() => page.url.split('?')[0]);
const navItems = [ const navItems = [
{ {
href: '/admin/account/profile', href: profile.url(),
label: 'Profil', label: 'Profil',
icon: User, icon: User,
}, },
{ {
href: '/admin/account/password', href: password.url(),
label: 'Kata Sandi', label: 'Kata Sandi',
icon: KeyRound, icon: KeyRound,
}, },
{ {
href: '/admin/account/appearance', href: appearance.url(),
label: 'Tampilan', label: 'Tampilan',
icon: Palette, icon: Palette,
}, },

View File

@ -19,6 +19,8 @@ import { formErrors } from '@/lib/form';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { AppearanceFormData, AppearanceMode } from '@/types/account'; import type { AppearanceFormData, AppearanceMode } from '@/types/account';
import { update } from '@/routes/admin/account/appearance';
const props = defineProps<{ const props = defineProps<{
appearance: AppearanceMode; appearance: AppearanceMode;
}>(); }>();
@ -63,7 +65,7 @@ function onAppearanceChange(value: AcceptableValue) {
} }
function submit() { function submit() {
form.put('/admin/account/appearance', { form.put(update.url(), {
onError: () => { onError: () => {
toast.error('Gagal menyimpan tampilan.'); toast.error('Gagal menyimpan tampilan.');
}, },

View File

@ -17,6 +17,8 @@ import AccountLayout from '@/layouts/AccountLayout.vue';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { PasswordFormData } from '@/types/account'; import type { PasswordFormData } from '@/types/account';
import { update } from '@/routes/admin/account/password';
const form = useForm<PasswordFormData>({ const form = useForm<PasswordFormData>({
current_password: '', current_password: '',
password: '', password: '',
@ -28,7 +30,7 @@ const showPassword = ref(false);
const showPasswordConfirmation = ref(false); const showPasswordConfirmation = ref(false);
function submit() { function submit() {
form.put('/admin/account/password', { form.put(update.url(), {
onSuccess: () => { onSuccess: () => {
form.reset(); form.reset();
}, },

View File

@ -25,6 +25,8 @@ import type {
ProfileUser, ProfileUser,
} from '@/types/account'; } from '@/types/account';
import { update } from '@/routes/admin/account/profile';
const props = defineProps<{ const props = defineProps<{
user: ProfileUser; user: ProfileUser;
genders: EnumOption[]; genders: EnumOption[];
@ -41,7 +43,7 @@ const form = useForm<ProfileFormData>({
}); });
function submit() { function submit() {
form.put('/admin/account/profile', { form.put(update.url(), {
preserveScroll: true, preserveScroll: true,
onError: () => { onError: () => {
toast.error('Gagal menyimpan profil. Periksa kembali formulir.'); toast.error('Gagal menyimpan profil. Periksa kembali formulir.');

View File

@ -14,6 +14,7 @@ import type { CashAccount, CashTransactionListItem, PaginatedCashTransactions }
import type { DataTableFilterDef, DataTableSort } from '@/types/data-table'; import type { DataTableFilterDef, DataTableSort } from '@/types/data-table';
import CashTransactionFormModal from './form/CashTransactionFormModal.vue'; import CashTransactionFormModal from './form/CashTransactionFormModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/finance/cash';
const props = defineProps<{ const props = defineProps<{
cashAccount: CashAccount; cashAccount: CashAccount;
@ -33,7 +34,7 @@ const formModalMode = ref<'deposit' | 'withdrawal'>(CashTransactionType.DEPOSIT)
const editingTransaction = ref<CashTransactionListItem | null>(null); const editingTransaction = ref<CashTransactionListItem | null>(null);
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({ const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/finance/cash', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['reference_type'], filterKeys: ['reference_type'],
}); });

View File

@ -29,6 +29,8 @@ import { parseRupiah } from '@/lib/rupiah';
import type { CashTransactionListItem } from '@/types/cash'; import type { CashTransactionListItem } from '@/types/cash';
import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media'; import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media';
import type { MediaUploadState } from '@/types/media'; import type { MediaUploadState } from '@/types/media';
import { withdraw, deposit } from '@/routes/admin/finance/cash';
import { update } from '@/routes/admin/finance/cash/transactions';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -111,12 +113,12 @@ function submit() {
}; };
if (isEditing.value && props.transaction) { if (isEditing.value && props.transaction) {
form.transform(() => payload).put(`/admin/finance/cash/transactions/${props.transaction.id}`, options); form.transform(() => payload).put(update.url(props.transaction.id), options);
return; return;
} }
const endpoint = isWithdrawal.value ? '/admin/finance/cash/withdraw' : '/admin/finance/cash/deposit'; const endpoint = isWithdrawal.value ? withdraw.url() : deposit.url();
form.transform(() => payload).post(endpoint, options); form.transform(() => payload).post(endpoint, options);
} }

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { CashTransactionListItem } from '@/types/cash'; import type { CashTransactionListItem } from '@/types/cash';
import { destroy } from '@/routes/admin/finance/cash/transactions';
const props = defineProps<{ const props = defineProps<{
transaction: CashTransactionListItem; transaction: CashTransactionListItem;
@ -27,7 +28,7 @@ const deleteProcessing = ref(false);
function destroyTransaction() { function destroyTransaction() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/finance/cash/transactions/${props.transaction.id}`, { router.delete(destroy.url(props.transaction.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;

View File

@ -21,6 +21,8 @@ import EmployeeAdvanceFormModal from './form/EmployeeAdvanceFormModal.vue';
import RejectEmployeeAdvanceModal from './form/RejectEmployeeAdvanceModal.vue'; import RejectEmployeeAdvanceModal from './form/RejectEmployeeAdvanceModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/finance/employee_advances';
const props = defineProps<EmployeeAdvancePageProps>(); const props = defineProps<EmployeeAdvancePageProps>();
const { can } = useCan(); const { can } = useCan();
@ -32,7 +34,7 @@ const rejectingEmployeeAdvance = ref<EmployeeAdvanceListItem | null>(null);
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/finance/employee-advances', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['status'], filterKeys: ['status'],
}); });

View File

@ -26,6 +26,7 @@ import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import { parseRupiah } from '@/lib/rupiah'; import { parseRupiah } from '@/lib/rupiah';
import type { EmployeeAdvanceFormData, EmployeeAdvanceListItem } from '@/types/employee-advance'; import type { EmployeeAdvanceFormData, EmployeeAdvanceListItem } from '@/types/employee-advance';
import { store, update } from '@/routes/admin/finance/employee_advances';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -94,12 +95,12 @@ function submit() {
}; };
if (isEditing.value && props.employeeAdvance) { if (isEditing.value && props.employeeAdvance) {
form.transform(() => payload).put(`/admin/finance/employee-advances/${props.employeeAdvance.id}`, options); form.transform(() => payload).put(update.url(props.employeeAdvance.id), options);
return; return;
} }
form.transform(() => payload).post('/admin/finance/employee-advances', options); form.transform(() => payload).post(store.url(), options);
} }
</script> </script>

View File

@ -22,6 +22,7 @@ import { Textarea } from '@/components/ui/textarea';
import { FIELD_LIMITS } from '@/lib/field-limits'; import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { EmployeeAdvanceListItem } from '@/types/employee-advance'; import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
import { reject } from '@/routes/admin/finance/employee_advances';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -49,7 +50,7 @@ function submit() {
return; return;
} }
form.post(`/admin/finance/employee-advances/${props.employeeAdvance.id}/reject`, { form.post(reject.url(props.employeeAdvance.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
open.value = false; open.value = false;

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { EmployeeAdvanceListItem } from '@/types/employee-advance'; import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
import { destroy, approve, pay } from '@/routes/admin/finance/employee_advances';
const props = defineProps<{ const props = defineProps<{
employeeAdvance: EmployeeAdvanceListItem; employeeAdvance: EmployeeAdvanceListItem;
@ -50,7 +51,7 @@ const payProcessing = ref(false);
function destroyEmployeeAdvance() { function destroyEmployeeAdvance() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/finance/employee-advances/${props.employeeAdvance.id}`, { router.delete(destroy.url(props.employeeAdvance.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
@ -67,7 +68,7 @@ function destroyEmployeeAdvance() {
function approveEmployeeAdvance() { function approveEmployeeAdvance() {
approveProcessing.value = true; approveProcessing.value = true;
router.post(`/admin/finance/employee-advances/${props.employeeAdvance.id}/approve`, {}, { router.post(approve.url(props.employeeAdvance.id), {}, {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
approveConfirmOpen.value = false; approveConfirmOpen.value = false;
@ -84,7 +85,7 @@ function approveEmployeeAdvance() {
function payEmployeeAdvance() { function payEmployeeAdvance() {
payProcessing.value = true; payProcessing.value = true;
router.post(`/admin/finance/employee-advances/${props.employeeAdvance.id}/pay`, {}, { router.post(pay.url(props.employeeAdvance.id), {}, {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
payConfirmOpen.value = false; payConfirmOpen.value = false;

View File

@ -15,6 +15,8 @@ import { computed, ref, watch } from 'vue';
import ExpenseFormModal from './form/ExpenseFormModal.vue'; import ExpenseFormModal from './form/ExpenseFormModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/finance/expenses';
const props = defineProps<{ const props = defineProps<{
expenses: PaginatedExpenses; expenses: PaginatedExpenses;
filters: { filters: {
@ -31,7 +33,7 @@ const editingExpense = ref<ExpenseListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/finance/expenses', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });

View File

@ -29,6 +29,8 @@ import type { ExpenseListItem } from '@/types/expense';
import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media'; import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media';
import type { MediaUploadState } from '@/types/media'; import type { MediaUploadState } from '@/types/media';
import { store, update } from '@/routes/admin/finance/expenses';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
const props = defineProps<{ const props = defineProps<{
@ -99,12 +101,12 @@ function submit() {
}; };
if (isEditing.value && props.expense) { if (isEditing.value && props.expense) {
form.transform(() => payload).put(`/admin/finance/expenses/${props.expense.id}`, options); form.transform(() => payload).put(update.url(props.expense.id), options);
return; return;
} }
form.transform(() => payload).post('/admin/finance/expenses', options); form.transform(() => payload).post(store.url(), options);
} }
</script> </script>

View File

@ -9,6 +9,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { ExpenseListItem } from '@/types/expense'; import type { ExpenseListItem } from '@/types/expense';
import { destroy } from '@/routes/admin/finance/expenses';
const props = defineProps<{ const props = defineProps<{
expense: ExpenseListItem; expense: ExpenseListItem;
}>(); }>();
@ -25,7 +27,7 @@ const deleteProcessing = ref(false);
function destroyExpense() { function destroyExpense() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/finance/expenses/${props.expense.id}`, { router.delete(destroy.url(props.expense.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;

View File

@ -27,6 +27,8 @@ import type { PayrollListItem, PayrollPageProps } from '@/types/payroll';
import PayrollAdjustmentModal from './form/PayrollAdjustmentModal.vue'; import PayrollAdjustmentModal from './form/PayrollAdjustmentModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/finance/payroll';
const props = defineProps<PayrollPageProps>(); const props = defineProps<PayrollPageProps>();
const { can } = useCan(); const { can } = useCan();
@ -48,7 +50,7 @@ const currentAdjustingPayroll = computed(() => {
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/finance/payroll', url: index.url(),
initial: { initial: {
...props.filters, ...props.filters,
period_id: props.filters.period_id period_id: props.filters.period_id

View File

@ -31,6 +31,7 @@ import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import { parseRupiah } from '@/lib/rupiah'; import { parseRupiah } from '@/lib/rupiah';
import type { PayrollAdjustmentFormData, PayrollAdjustmentItem, PayrollListItem, SelectOption } from '@/types/payroll'; import type { PayrollAdjustmentFormData, PayrollAdjustmentItem, PayrollListItem, SelectOption } from '@/types/payroll';
import { store, update, destroy } from '@/routes/admin/finance/payroll/adjustments';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -76,7 +77,7 @@ function cancelEdit() {
} }
function deleteAdj(adjustment: PayrollAdjustmentItem) { function deleteAdj(adjustment: PayrollAdjustmentItem) {
router.delete(`/admin/finance/payroll/adjustments/${adjustment.id}`, { router.delete(destroy.url(adjustment.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
toast.success('Penyesuaian berhasil dihapus.'); toast.success('Penyesuaian berhasil dihapus.');
@ -98,7 +99,7 @@ function submit() {
})); }));
if (editingAdjustment.value) { if (editingAdjustment.value) {
payload.put(`/admin/finance/payroll/adjustments/${editingAdjustment.value.id}`, { payload.put(update.url(editingAdjustment.value.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
cancelEdit(); cancelEdit();
@ -108,7 +109,7 @@ function submit() {
}, },
}); });
} else { } else {
payload.post(`/admin/finance/payroll/${props.payroll.id}/adjustments`, { payload.post(store.url(props.payroll.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
resetForm(); resetForm();

View File

@ -14,6 +14,7 @@ import { useForm } from '@inertiajs/vue3';
import { Camera, Loader2 } from '@lucide/vue'; import { Camera, Loader2 } from '@lucide/vue';
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'; import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { check_in, check_out } from '@/routes/admin/hr/attendances';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -44,8 +45,8 @@ const submitLabel = computed(() =>
); );
const submitUrl = computed(() => const submitUrl = computed(() =>
props.mode === 'check-in' props.mode === 'check-in'
? '/admin/hr/attendances/check-in' ? check_in.url()
: '/admin/hr/attendances/check-out', : check_out.url(),
); );
async function initializeCamera() { async function initializeCamera() {

View File

@ -13,6 +13,7 @@ import { Button } from '@/components/ui/button';
import { DatePicker } from '@/components/ui/date-picker'; import { DatePicker } from '@/components/ui/date-picker';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { AttendanceListItem, CalendarRange } from '@/types/attendance'; import type { AttendanceListItem, CalendarRange } from '@/types/attendance';
import { index } from '@/routes/admin/hr/attendances';
const props = defineProps<{ const props = defineProps<{
attendances: AttendanceListItem[]; attendances: AttendanceListItem[];
@ -180,7 +181,7 @@ function handleDatesSet(arg: DatesSetArg) {
isNavigating.value = true; isNavigating.value = true;
router.get('/admin/hr/attendances', { start, end }, { router.get(index.url(), { start, end }, {
preserveState: true, preserveState: true,
preserveScroll: true, preserveScroll: true,
only: ['attendances', 'calendarRange'], only: ['attendances', 'calendarRange'],

View File

@ -16,6 +16,7 @@ import {
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { AttendanceListItem } from '@/types/attendance'; import type { AttendanceListItem } from '@/types/attendance';
import { destroy } from '@/routes/admin/hr/attendances';
const open = defineModel<boolean>('open', { required: true }); const open = defineModel<boolean>('open', { required: true });
@ -35,7 +36,7 @@ function destroyAttendance() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/hr/attendances/${props.attendance.id}`, { router.delete(destroy.url(props.attendance.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { AttendanceListItem } from '@/types/attendance'; import type { AttendanceListItem } from '@/types/attendance';
import { destroy } from '@/routes/admin/hr/attendances';
const props = defineProps<{ const props = defineProps<{
attendance: AttendanceListItem; attendance: AttendanceListItem;
@ -21,7 +22,7 @@ const deleteProcessing = ref(false);
function destroyAttendance() { function destroyAttendance() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/hr/attendances/${props.attendance.id}`, { router.delete(destroy.url(props.attendance.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;

View File

@ -4,6 +4,7 @@ import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EnumOption } from '@/types/employee'; import type { EnumOption } from '@/types/employee';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import EmployeeForm from './form/EmployeeForm.vue'; import EmployeeForm from './form/EmployeeForm.vue';
import { index, store } from '@/routes/admin/hr/employees';
defineProps<{ defineProps<{
genders: EnumOption[]; genders: EnumOption[];
@ -25,11 +26,11 @@ defineProps<{
</h2> </h2>
</div> </div>
<BackButton href="/admin/hr/employees" /> <BackButton :href="index.url()" />
</div> </div>
<EmployeeForm <EmployeeForm
submit-url="/admin/hr/employees" :submit-url="store.url()"
:genders="genders" :genders="genders"
:employment-statuses="employmentStatuses" :employment-statuses="employmentStatuses"
:roles="roles" :roles="roles"

View File

@ -5,6 +5,7 @@ import type { EmployeeListItem, EnumOption } from '@/types/employee';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed } from 'vue'; import { computed } from 'vue';
import EmployeeForm from './form/EmployeeForm.vue'; import EmployeeForm from './form/EmployeeForm.vue';
import { index, update } from '@/routes/admin/hr/employees';
const props = defineProps<{ const props = defineProps<{
employee: EmployeeListItem; employee: EmployeeListItem;
@ -42,11 +43,11 @@ const initialData = computed(() => ({
<h2 class="text-2xl font-bold tracking-tight">Ubah Pegawai</h2> <h2 class="text-2xl font-bold tracking-tight">Ubah Pegawai</h2>
</div> </div>
<BackButton href="/admin/hr/employees" /> <BackButton :href="index.url()" />
</div> </div>
<EmployeeForm <EmployeeForm
:submit-url="`/admin/hr/employees/${employee.id}`" :submit-url="update.url(employee.id)"
method="put" method="put"
submit-label="Perbarui" submit-label="Perbarui"
:initial-data="initialData" :initial-data="initialData"

View File

@ -14,6 +14,7 @@ import type { EnumOption, PaginatedEmployees } from '@/types/employee';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { columns } from './table/columns'; import { columns } from './table/columns';
import { index, create } from '@/routes/admin/hr/employees';
const props = defineProps<{ const props = defineProps<{
employees: PaginatedEmployees; employees: PaginatedEmployees;
@ -36,7 +37,7 @@ const search = ref(props.filters.search ?? '');
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/hr/employees', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['role', 'gender', 'employment_status', 'is_active'], filterKeys: ['role', 'gender', 'employment_status', 'is_active'],
}); });
@ -123,7 +124,7 @@ watch(
<CreateButton <CreateButton
v-if="can('employees.create')" v-if="can('employees.create')"
href="/admin/hr/employees/create" :href="create.url()"
/> />
</div> </div>

View File

@ -14,6 +14,7 @@ import {
AlertDialogTrigger, AlertDialogTrigger,
} from '@/components/ui/alert-dialog'; } from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { reset_password } from '@/routes/admin/hr/employees';
const props = defineProps<{ const props = defineProps<{
employeeId: number; employeeId: number;
@ -29,7 +30,7 @@ const processing = ref(false);
function resetPassword() { function resetPassword() {
processing.value = true; processing.value = true;
router.post(`/admin/hr/employees/${props.employeeId}/reset-password`, {}, { router.post(reset_password.url(props.employeeId), {}, {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
open.value = false; open.value = false;

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { EmployeeListItem } from '@/types/employee'; import type { EmployeeListItem } from '@/types/employee';
import { edit, destroy, reset_password } from '@/routes/admin/hr/employees';
const props = defineProps<{ const props = defineProps<{
employee: EmployeeListItem; employee: EmployeeListItem;
@ -25,7 +26,7 @@ const resetPasswordProcessing = ref(false);
function destroyEmployee() { function destroyEmployee() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/hr/employees/${props.employee.id}`, { router.delete(destroy.url(props.employee.id), {
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
}, },
@ -41,7 +42,7 @@ function destroyEmployee() {
function resetPassword() { function resetPassword() {
resetPasswordProcessing.value = true; resetPasswordProcessing.value = true;
router.post(`/admin/hr/employees/${props.employee.id}/reset-password`, {}, { router.post(reset_password.url(props.employee.id), {}, {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
resetPasswordConfirmOpen.value = false; resetPasswordConfirmOpen.value = false;
@ -61,7 +62,7 @@ function resetPassword() {
<Tooltip v-if="can('employees.update')"> <Tooltip v-if="can('employees.update')">
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/hr/employees/${employee.id}/edit`"> <Link :href="edit.url(employee.id)">
<Pencil class="size-4" /> <Pencil class="size-4" />
<span class="sr-only">Ubah</span> <span class="sr-only">Ubah</span>
</Link> </Link>

View File

@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { EmployeeListItem } from '@/types/employee'; import type { EmployeeListItem } from '@/types/employee';
import { toggle_status } from '@/routes/admin/hr/employees';
const props = defineProps<{ const props = defineProps<{
employee: EmployeeListItem; employee: EmployeeListItem;
@ -32,7 +33,7 @@ function toggleStatus(checked: boolean) {
isActive.value = checked; isActive.value = checked;
processing.value = true; processing.value = true;
router.patch(`/admin/hr/employees/${props.employee.id}/toggle-status`, { router.patch(toggle_status.url(props.employee.id), {
is_active: checked, is_active: checked,
}, { }, {
preserveScroll: true, preserveScroll: true,

View File

@ -18,6 +18,7 @@ import { computed, ref, watch } from 'vue';
import LeaveRequestFormModal from './form/LeaveRequestFormModal.vue'; import LeaveRequestFormModal from './form/LeaveRequestFormModal.vue';
import RejectLeaveRequestModal from './form/RejectLeaveRequestModal.vue'; import RejectLeaveRequestModal from './form/RejectLeaveRequestModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/hr/leave_requests';
const props = defineProps<LeaveRequestPageProps>(); const props = defineProps<LeaveRequestPageProps>();
@ -30,7 +31,7 @@ const rejectingLeaveRequest = ref<LeaveRequestListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/hr/leave-requests', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });

View File

@ -22,6 +22,7 @@ import {
import { useFormDialog } from '@/composables/useFormDialog'; import { useFormDialog } from '@/composables/useFormDialog';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { LeaveRequestFormData, LeaveRequestListItem } from '@/types/leave-request'; import type { LeaveRequestFormData, LeaveRequestListItem } from '@/types/leave-request';
import { store, update } from '@/routes/admin/hr/leave_requests';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -71,12 +72,12 @@ function submit() {
}; };
if (isEditing.value && props.leaveRequest) { if (isEditing.value && props.leaveRequest) {
form.put(`/admin/hr/leave-requests/${props.leaveRequest.id}`, options); form.put(update.url(props.leaveRequest.id), options);
return; return;
} }
form.post('/admin/hr/leave-requests', options); form.post(store.url(), options);
} }
</script> </script>

View File

@ -22,6 +22,7 @@ import { Textarea } from '@/components/ui/textarea';
import { FIELD_LIMITS } from '@/lib/field-limits'; import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { LeaveRequestListItem } from '@/types/leave-request'; import type { LeaveRequestListItem } from '@/types/leave-request';
import { reject } from '@/routes/admin/hr/leave_requests';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -49,7 +50,7 @@ function submit() {
return; return;
} }
form.post(`/admin/hr/leave-requests/${props.leaveRequest.id}/reject`, { form.post(reject.url(props.leaveRequest.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
open.value = false; open.value = false;

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { LeaveRequestListItem } from '@/types/leave-request'; import type { LeaveRequestListItem } from '@/types/leave-request';
import { destroy, approve } from '@/routes/admin/hr/leave_requests';
const props = defineProps<{ const props = defineProps<{
leaveRequest: LeaveRequestListItem; leaveRequest: LeaveRequestListItem;
@ -48,7 +49,7 @@ const approveProcessing = ref(false);
function destroyLeaveRequest() { function destroyLeaveRequest() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/hr/leave-requests/${props.leaveRequest.id}`, { router.delete(destroy.url(props.leaveRequest.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
@ -65,7 +66,7 @@ function destroyLeaveRequest() {
function approveLeaveRequest() { function approveLeaveRequest() {
approveProcessing.value = true; approveProcessing.value = true;
router.post(`/admin/hr/leave-requests/${props.leaveRequest.id}/approve`, {}, { router.post(approve.url(props.leaveRequest.id), {}, {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
approveConfirmOpen.value = false; approveConfirmOpen.value = false;

View File

@ -9,6 +9,7 @@ import type {
} from '@/types/cutting'; } from '@/types/cutting';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import CuttingPosForm from './form/CuttingPosForm.vue'; import CuttingPosForm from './form/CuttingPosForm.vue';
import { index, store } from '@/routes/admin/manage/cuttings';
defineProps<{ defineProps<{
rawMaterialCatalog: CuttingRawMaterialCatalogItem[]; rawMaterialCatalog: CuttingRawMaterialCatalogItem[];
@ -31,7 +32,7 @@ defineProps<{
</h2> </h2>
</div> </div>
<BackButton href="/admin/manage/cuttings" /> <BackButton :href="index.url()" />
</div> </div>
<CuttingPosForm <CuttingPosForm
@ -39,7 +40,7 @@ defineProps<{
:product-catalog="productCatalog" :product-catalog="productCatalog"
:draft-materials="draftMaterials" :draft-materials="draftMaterials"
:draft-results="draftResults" :draft-results="draftResults"
submit-url="/admin/manage/cuttings" :submit-url="store.url()"
method="post" method="post"
submit-label="Simpan" submit-label="Simpan"
/> />

View File

@ -9,6 +9,7 @@ import type {
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed } from 'vue'; import { computed } from 'vue';
import CuttingPosForm from './form/CuttingPosForm.vue'; import CuttingPosForm from './form/CuttingPosForm.vue';
import { index, update } from '@/routes/admin/manage/cuttings';
const props = defineProps<{ const props = defineProps<{
cutting: CuttingEditItem; cutting: CuttingEditItem;
@ -56,14 +57,14 @@ const initialData = computed(() => ({
<h2 class="text-2xl font-bold tracking-tight">Ubah Cutting</h2> <h2 class="text-2xl font-bold tracking-tight">Ubah Cutting</h2>
</div> </div>
<BackButton href="/admin/manage/cuttings" /> <BackButton :href="index.url()" />
</div> </div>
<CuttingPosForm <CuttingPosForm
:raw-material-catalog="rawMaterialCatalog" :raw-material-catalog="rawMaterialCatalog"
:product-catalog="productCatalog" :product-catalog="productCatalog"
:initial-data="initialData" :initial-data="initialData"
:submit-url="`/admin/manage/cuttings/${cutting.id}`" :submit-url="update.url(props.cutting.id)"
method="put" method="put"
submit-label="Perbarui" submit-label="Perbarui"
/> />

View File

@ -12,6 +12,7 @@ import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import CuttingGroupedTable from './table/CuttingGroupedTable.vue'; import CuttingGroupedTable from './table/CuttingGroupedTable.vue';
import CuttingInProgressSection from './table/CuttingInProgressSection.vue'; import CuttingInProgressSection from './table/CuttingInProgressSection.vue';
import { index, create } from '@/routes/admin/manage/cuttings';
const props = defineProps<{ const props = defineProps<{
cuttings: PaginatedCuttings; cuttings: PaginatedCuttings;
@ -27,7 +28,7 @@ const { can, hasAnyRole } = useCan();
const search = ref(props.filters.search ?? ''); const search = ref(props.filters.search ?? '');
const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({ const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/manage/cuttings', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });
@ -71,7 +72,7 @@ watch(
<CreateButton <CreateButton
v-if="can('cuttings.create')" v-if="can('cuttings.create')"
href="/admin/manage/cuttings/create" :href="create.url()"
/> />
</div> </div>

View File

@ -44,6 +44,8 @@ import type {
} from '@/types/cutting'; } from '@/types/cutting';
import PosCatalogCard from '../../shared/PosCatalogCard.vue'; import PosCatalogCard from '../../shared/PosCatalogCard.vue';
import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue'; import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue';
import draft_materials from '@/routes/admin/manage/cuttings/draft_materials';
import draft_results from '@/routes/admin/manage/cuttings/draft_results';
const props = defineProps<{ const props = defineProps<{
rawMaterialCatalog: CuttingRawMaterialCatalogItem[]; rawMaterialCatalog: CuttingRawMaterialCatalogItem[];
@ -245,7 +247,7 @@ function upsertResultCartItem(item: CuttingResultCartItem) {
} }
async function syncDraftMaterial(rawMaterial: CuttingRawMaterialCatalogItem, price: CatalogPrice, materialUsage: string, remainingMaterial: string) { async function syncDraftMaterial(rawMaterial: CuttingRawMaterialCatalogItem, price: CatalogPrice, materialUsage: string, remainingMaterial: string) {
const { item } = await apiFetch<{ item: CuttingMaterialCartItem }>('/admin/manage/cuttings/draft-materials', { const { item } = await apiFetch<{ item: CuttingMaterialCartItem }>(draft_materials.store.url(), {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
raw_material_price_id: price.id, raw_material_price_id: price.id,
@ -258,7 +260,7 @@ async function syncDraftMaterial(rawMaterial: CuttingRawMaterialCatalogItem, pri
} }
async function syncDraftResult(product: CuttingProductCatalogItem, variant: CuttingProductCatalogItem['variants'][number], cuttingResult: string, warehouseStock: string, cuttingReject: string) { async function syncDraftResult(product: CuttingProductCatalogItem, variant: CuttingProductCatalogItem['variants'][number], cuttingResult: string, warehouseStock: string, cuttingReject: string) {
const { item } = await apiFetch<{ item: CuttingResultCartItem }>('/admin/manage/cuttings/draft-results', { const { item } = await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
product_variant_id: variant.id, product_variant_id: variant.id,
@ -371,7 +373,7 @@ async function removeMaterial(index: number) {
if (isCreateMode.value) { if (isCreateMode.value) {
try { try {
await apiFetch( await apiFetch(
`/admin/manage/cuttings/draft-materials/${item.raw_material_price_id}`, draft_materials.destroy.url(item.raw_material_price_id),
{ method: 'DELETE' }, { method: 'DELETE' },
); );
} catch (error) { } catch (error) {
@ -487,7 +489,7 @@ async function removeResult(index: number) {
if (isCreateMode.value) { if (isCreateMode.value) {
try { try {
await apiFetch( await apiFetch(
`/admin/manage/cuttings/draft-results/${item.product_variant_id}`, draft_results.destroy.url(item.product_variant_id),
{ method: 'DELETE' }, { method: 'DELETE' },
); );
} catch (error) { } catch (error) {
@ -516,7 +518,7 @@ async function syncMaterialField(index: number) {
const item = materialCart.value[index]; const item = materialCart.value[index];
try { try {
await apiFetch<{ item: CuttingMaterialCartItem }>('/admin/manage/cuttings/draft-materials', { await apiFetch<{ item: CuttingMaterialCartItem }>(draft_materials.store.url(), {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
raw_material_price_id: item.raw_material_price_id, raw_material_price_id: item.raw_material_price_id,
@ -537,7 +539,7 @@ async function syncResultField(index: number) {
const item = resultCart.value[index]; const item = resultCart.value[index];
try { try {
await apiFetch<{ item: CuttingResultCartItem }>('/admin/manage/cuttings/draft-results', { await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
product_variant_id: item.product_variant_id, product_variant_id: item.product_variant_id,

View File

@ -38,6 +38,8 @@ import {
PRICE_TYPE_LABELS, PRICE_TYPE_LABELS,
} from '@/types/product'; } from '@/types/product';
import type { CuttingListItem, CuttingStatusAction } from '@/types/cutting'; import type { CuttingListItem, CuttingStatusAction } from '@/types/cutting';
import { edit, destroy, transition_status } from '@/routes/admin/manage/cuttings';
import { FIELD_LIMITS } from '@/lib/field-limits';
const props = defineProps<{ const props = defineProps<{
cutting: CuttingListItem; cutting: CuttingListItem;
@ -152,7 +154,7 @@ const canEdit = computed(
() => props.cutting.is_editable && can('cuttings.update'), () => props.cutting.is_editable && can('cuttings.update'),
); );
const canDelete = computed( const canDelete = computed(
() => can('cuttings.delete') && [CuttingStatus.IN_PROGRESS, CuttingStatus.REJECTED].includes(props.cutting.status), () => can('cuttings.delete') && (props.cutting.status === CuttingStatus.IN_PROGRESS || props.cutting.status === CuttingStatus.REJECTED),
); );
function canPerformAction(action: CuttingStatusAction): boolean { function canPerformAction(action: CuttingStatusAction): boolean {
@ -206,7 +208,7 @@ function submitVerify() {
})), })),
result_prices: buildResultPricesPayload(), result_prices: buildResultPricesPayload(),
})) }))
.post(`/admin/manage/cuttings/${props.cutting.id}/status`, { .post(transition_status.url(props.cutting.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
verifyDialogOpen.value = false; verifyDialogOpen.value = false;
@ -231,7 +233,7 @@ function transitionStatus() {
statusProcessing.value = true; statusProcessing.value = true;
router.post( router.post(
`/admin/manage/cuttings/${props.cutting.id}/status`, transition_status.url(props.cutting.id),
{ {
status: pendingAction.value.status, status: pendingAction.value.status,
}, },
@ -258,7 +260,7 @@ function transitionStatus() {
} }
function submitReject() { function submitReject() {
rejectForm.post(`/admin/manage/cuttings/${props.cutting.id}/status`, { rejectForm.post(transition_status.url(props.cutting.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
rejectDialogOpen.value = false; rejectDialogOpen.value = false;
@ -285,7 +287,7 @@ watch(rejectDialogOpen, (isOpen) => {
function destroyCutting() { function destroyCutting() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/manage/cuttings/${props.cutting.id}`, { router.delete(destroy.url(props.cutting.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
@ -348,7 +350,7 @@ function actionIcon(status: string) {
<Tooltip v-if="canEdit"> <Tooltip v-if="canEdit">
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/manage/cuttings/${cutting.id}/edit`"> <Link :href="edit.url(cutting.id)">
<Pencil class="size-4" /> <Pencil class="size-4" />
<span class="sr-only">Ubah</span> <span class="sr-only">Ubah</span>
</Link> </Link>

View File

@ -10,6 +10,7 @@ import type {
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { ref } from 'vue'; import { ref } from 'vue';
import OrderPosForm from './form/OrderPosForm.vue'; import OrderPosForm from './form/OrderPosForm.vue';
import { index, store } from '@/routes/admin/manage/orders';
const props = defineProps<{ const props = defineProps<{
customers: SelectOption[]; customers: SelectOption[];
@ -41,7 +42,7 @@ function onCustomerCreated(customer: { id: number; name: string }) {
</h2> </h2>
</div> </div>
<BackButton href="/admin/manage/orders" /> <BackButton :href="index.url()" />
</div> </div>
<OrderPosForm <OrderPosForm
@ -52,7 +53,7 @@ function onCustomerCreated(customer: { id: number; name: string }) {
:store-price-types="storePriceTypes" :store-price-types="storePriceTypes"
:payment-types="paymentTypes" :payment-types="paymentTypes"
:draft-items="draftItems" :draft-items="draftItems"
submit-url="/admin/manage/orders" :submit-url="store.url()"
method="post" method="post"
submit-label="Simpan" submit-label="Simpan"
@customer-created="onCustomerCreated" @customer-created="onCustomerCreated"

View File

@ -10,6 +10,7 @@ import type {
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import OrderPosForm from './form/OrderPosForm.vue'; import OrderPosForm from './form/OrderPosForm.vue';
import { index, update } from '@/routes/admin/manage/orders';
const props = defineProps<{ const props = defineProps<{
order: OrderEditItem; order: OrderEditItem;
@ -69,7 +70,7 @@ function onCustomerCreated(customer: { id: number; name: string }) {
</p> </p>
</div> </div>
<BackButton href="/admin/manage/orders" /> <BackButton :href="index.url()" />
</div> </div>
<OrderPosForm <OrderPosForm
@ -80,7 +81,7 @@ function onCustomerCreated(customer: { id: number; name: string }) {
:store-price-types="storePriceTypes" :store-price-types="storePriceTypes"
:payment-types="paymentTypes" :payment-types="paymentTypes"
:initial-data="initialData" :initial-data="initialData"
:submit-url="`/admin/manage/orders/${order.id}`" :submit-url="update.url(props.order.id)"
method="put" method="put"
submit-label="Perbarui" submit-label="Perbarui"
@customer-created="onCustomerCreated" @customer-created="onCustomerCreated"

View File

@ -16,6 +16,7 @@ import type { PaginatedOrders } from '@/types/order';
import { Head, usePage } from '@inertiajs/vue3'; import { Head, usePage } from '@inertiajs/vue3';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { index, create } from '@/routes/admin/manage/orders';
import ThermalPrinterConnectButton from './form/ThermalPrinterConnectButton.vue'; import ThermalPrinterConnectButton from './form/ThermalPrinterConnectButton.vue';
import OrderGroupedTable from './table/OrderGroupedTable.vue'; import OrderGroupedTable from './table/OrderGroupedTable.vue';
@ -34,7 +35,7 @@ const { isConnected, printOrderReceipt, tryReconnect } = useThermalPrinter();
const search = ref(props.filters.search ?? ''); const search = ref(props.filters.search ?? '');
const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({ const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/manage/orders', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });
@ -125,7 +126,7 @@ onMounted(async () => {
> >
<ThermalPrinterConnectButton /> <ThermalPrinterConnectButton />
<CreateButton href="/admin/manage/orders/create" /> <CreateButton :href="create.url()" />
</div> </div>
</div> </div>

View File

@ -36,6 +36,7 @@ import { OrderStatus } from '@/constants/order-status';
import { StockQuality } from '@/constants/stock-quality'; import { StockQuality } from '@/constants/stock-quality';
import AdminLayout from '@/layouts/AdminLayout.vue'; import AdminLayout from '@/layouts/AdminLayout.vue';
import type { OrderDetail, OrderStatusAction } from '@/types/order'; import type { OrderDetail, OrderStatusAction } from '@/types/order';
import { index, edit, destroy, transition_status } from '@/routes/admin/manage/orders';
const props = defineProps<{ const props = defineProps<{
order: OrderDetail; order: OrderDetail;
@ -97,7 +98,7 @@ function transitionStatus() {
statusProcessing.value = true; statusProcessing.value = true;
router.post(`/admin/manage/orders/${props.order.id}/status`, { router.post(transition_status.url(props.order.id), {
status: pendingAction.value.status, status: pendingAction.value.status,
}, { }, {
onSuccess: () => { onSuccess: () => {
@ -117,7 +118,7 @@ function transitionStatus() {
function destroyOrder() { function destroyOrder() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/manage/orders/${props.order.id}`, { router.delete(destroy.url(props.order.id), {
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
}, },
@ -239,7 +240,7 @@ const feeEntries = computed(() => {
</template> </template>
<Button v-if="canEdit" variant="outline" size="sm" as-child> <Button v-if="canEdit" variant="outline" size="sm" as-child>
<Link :href="`/admin/manage/orders/${order.id}/edit`"> <Link :href="edit.url(props.order.id)">
<Pencil class="size-3.5" /> <Pencil class="size-3.5" />
Ubah Ubah
</Link> </Link>
@ -256,7 +257,7 @@ const feeEntries = computed(() => {
Hapus Hapus
</Button> </Button>
<BackButton href="/admin/manage/orders" /> <BackButton :href="index.url()" />
</div> </div>
</div> </div>

View File

@ -1,7 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { Save } from '@lucide/vue';
import { reactive, ref } from 'vue';
import { toast } from 'vue-sonner';
import { PhoneNumberInput } from '@/components/form/phone-number-input'; import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
@ -22,6 +19,10 @@ import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { apiFetch } from '@/lib/api'; import { apiFetch } from '@/lib/api';
import { FIELD_LIMITS } from '@/lib/field-limits'; import { FIELD_LIMITS } from '@/lib/field-limits';
import { Save } from '@lucide/vue';
import { reactive, ref } from 'vue';
import { toast } from 'vue-sonner';
import { api } from '@/routes/admin/master/customers/store';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -50,21 +51,25 @@ async function submit() {
processing.value = true; processing.value = true;
try { try {
const customer = await apiFetch<{ id: number; name: string }>('/admin/master/customers/api', { const customer = await apiFetch<{ id: number; name: string }>(
method: 'POST', api.url(),
body: JSON.stringify({ {
name: form.name, method: 'POST',
phone_number: form.phone_number || null, body: JSON.stringify({
address: form.address || null, name: form.name,
}), phone_number: form.phone_number || null,
}); address: form.address || null,
}),
},
);
emit('created', customer); emit('created', customer);
open.value = false; open.value = false;
resetForm(); resetForm();
toast.success('Pelanggan berhasil ditambahkan.'); toast.success('Pelanggan berhasil ditambahkan.');
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : 'Gagal menyimpan data.'; const message =
error instanceof Error ? error.message : 'Gagal menyimpan data.';
if (message.includes('nama')) { if (message.includes('nama')) {
errors.name = message; errors.name = message;
@ -88,26 +93,53 @@ async function submit() {
<FieldGroup> <FieldGroup>
<FieldSet class="grid gap-4"> <FieldSet class="grid gap-4">
<Field> <Field>
<FieldLabel for="quick-customer-name" required>Nama</FieldLabel> <FieldLabel for="quick-customer-name" required
<Input id="quick-customer-name" v-model="form.name" type="text" >Nama</FieldLabel
placeholder="Contoh: Budi Santoso" autofocus :maxlength="FIELD_LIMITS.name" /> >
<FieldError v-if="errors.name" :errors="[errors.name]" /> <Input
id="quick-customer-name"
v-model="form.name"
type="text"
placeholder="Contoh: Budi Santoso"
autofocus
:maxlength="FIELD_LIMITS.name"
/>
<FieldError
v-if="errors.name"
:errors="[errors.name]"
/>
</Field> </Field>
<Field> <Field>
<FieldLabel for="quick-customer-phone">Nomor Telepon</FieldLabel> <FieldLabel for="quick-customer-phone"
<PhoneNumberInput id="quick-customer-phone" v-model="form.phone_number" >Nomor Telepon</FieldLabel
placeholder="Contoh: 08123456789" /> >
<PhoneNumberInput
id="quick-customer-phone"
v-model="form.phone_number"
placeholder="Contoh: 08123456789"
/>
</Field> </Field>
<Field> <Field>
<FieldLabel for="quick-customer-address">Alamat</FieldLabel> <FieldLabel for="quick-customer-address"
<Textarea id="quick-customer-address" v-model="form.address" rows="3" >Alamat</FieldLabel
placeholder="Contoh: Jl. Mawar No. 12, Surabaya" /> >
<Textarea
id="quick-customer-address"
v-model="form.address"
rows="3"
placeholder="Contoh: Jl. Mawar No. 12, Surabaya"
/>
</Field> </Field>
</FieldSet> </FieldSet>
</FieldGroup> </FieldGroup>
<DialogFooter class="mt-6"> <DialogFooter class="mt-6">
<Button type="button" variant="outline" :disabled="processing" @click="open = false"> <Button
type="button"
variant="outline"
:disabled="processing"
@click="open = false"
>
Batal Batal
</Button> </Button>
<Button type="submit" :disabled="processing"> <Button type="submit" :disabled="processing">

View File

@ -55,6 +55,7 @@ import type { ProductPriceItem, ProductVariantItem } from '@/types/product';
import PosCatalogCard from '../../shared/PosCatalogCard.vue'; import PosCatalogCard from '../../shared/PosCatalogCard.vue';
import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue'; import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue';
import CustomerQuickCreateModal from './CustomerQuickCreateModal.vue'; import CustomerQuickCreateModal from './CustomerQuickCreateModal.vue';
import { store as syncDraftRoute, resync_prices as resyncPricesRoute, destroy as destroyDraftRoute } from '@/routes/admin/manage/orders/draft_items';
const props = defineProps<{ const props = defineProps<{
customers: SelectOption[]; customers: SelectOption[];
@ -200,7 +201,7 @@ watch(
try { try {
const { items } = await apiFetch<{ items: OrderCartItem[] }>( const { items } = await apiFetch<{ items: OrderCartItem[] }>(
'/admin/manage/orders/draft-items/resync-prices', resyncPricesRoute.url(),
{ {
method: 'PUT', method: 'PUT',
body: JSON.stringify({ price_type: priceType }), body: JSON.stringify({ price_type: priceType }),
@ -286,7 +287,7 @@ function lineSubtotal(item: OrderCartItem): number {
} }
async function syncDraftItem(variantId: number, quantity: number, stockQuality = selectedStockQuality.value) { async function syncDraftItem(variantId: number, quantity: number, stockQuality = selectedStockQuality.value) {
const { item } = await apiFetch<{ item: OrderCartItem }>('/admin/manage/orders/draft-items', { const { item } = await apiFetch<{ item: OrderCartItem }>(syncDraftRoute.url(), {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
product_variant_id: variantId, product_variant_id: variantId,
@ -364,7 +365,9 @@ async function removeFromCart(index: number) {
if (isCreateMode.value) { if (isCreateMode.value) {
try { try {
await apiFetch( await apiFetch(
`/admin/manage/orders/draft-items/${item.product_variant_id}?stock_quality=${item.stock_quality}`, destroyDraftRoute.url(item.product_variant_id, {
query: { stock_quality: item.stock_quality }
}),
{ {
method: 'DELETE', method: 'DELETE',
}, },

View File

@ -10,6 +10,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import { OrderStatus } from '@/constants/order-status'; import { OrderStatus } from '@/constants/order-status';
import type { OrderListItem, OrderStatusAction } from '@/types/order'; import type { OrderListItem, OrderStatusAction } from '@/types/order';
import { show, edit, destroy, transition_status } from '@/routes/admin/manage/orders';
const props = defineProps<{ const props = defineProps<{
order: OrderListItem; order: OrderListItem;
@ -56,7 +57,7 @@ function transitionStatus() {
statusProcessing.value = true; statusProcessing.value = true;
router.post(`/admin/manage/orders/${props.order.id}/status`, { router.post(transition_status.url(props.order.id), {
status: pendingAction.value.status, status: pendingAction.value.status,
}, { }, {
preserveScroll: true, preserveScroll: true,
@ -78,7 +79,7 @@ function transitionStatus() {
function destroyOrder() { function destroyOrder() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/manage/orders/${props.order.id}`, { router.delete(destroy.url(props.order.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
@ -141,7 +142,7 @@ function actionIcon(status: string) {
<Tooltip> <Tooltip>
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/manage/orders/${order.id}`"> <Link :href="show.url(props.order.id)">
<Eye class="size-4" /> <Eye class="size-4" />
<span class="sr-only">Detail</span> <span class="sr-only">Detail</span>
</Link> </Link>
@ -153,7 +154,7 @@ function actionIcon(status: string) {
<Tooltip v-if="canEdit"> <Tooltip v-if="canEdit">
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/manage/orders/${order.id}/edit`"> <Link :href="edit.url(props.order.id)">
<Pencil class="size-4" /> <Pencil class="size-4" />
<span class="sr-only">Ubah</span> <span class="sr-only">Ubah</span>
</Link> </Link>

View File

@ -24,6 +24,7 @@ import {
} from '@/components/ui/tooltip'; } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { CuttingListItem } from '@/types/cutting'; import type { CuttingListItem } from '@/types/cutting';
import { approve, reject } from '@/routes/admin/manage/owner_verifications';
const props = defineProps<{ const props = defineProps<{
cutting: CuttingListItem; cutting: CuttingListItem;
@ -41,7 +42,7 @@ const rejectForm = useForm({
function submitApprove() { function submitApprove() {
approveForm approveForm
.post(`/admin/manage/owner-verifications/${props.cutting.id}/approve`, { .post(approve.url(props.cutting.id), {
preserveScroll: true, preserveScroll: true,
onError: (errors) => { onError: (errors) => {
const message = Object.values(errors)[0]; const message = Object.values(errors)[0];
@ -52,7 +53,7 @@ function submitApprove() {
function submitReject() { function submitReject() {
rejectForm rejectForm
.post(`/admin/manage/owner-verifications/${props.cutting.id}/reject`, { .post(reject.url(props.cutting.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
rejectDialogOpen.value = false; rejectDialogOpen.value = false;

View File

@ -8,6 +8,7 @@ import type {
} from '@/types/purchase'; } from '@/types/purchase';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import PurchasePosForm from './form/PurchasePosForm.vue'; import PurchasePosForm from './form/PurchasePosForm.vue';
import { index, store } from '@/routes/admin/manage/purchases';
defineProps<{ defineProps<{
suppliers: SelectOption[]; suppliers: SelectOption[];
@ -29,14 +30,14 @@ defineProps<{
</h2> </h2>
</div> </div>
<BackButton href="/admin/manage/purchases" /> <BackButton :href="index.url()" />
</div> </div>
<PurchasePosForm <PurchasePosForm
:suppliers="suppliers" :suppliers="suppliers"
:catalog="catalog" :catalog="catalog"
:draft-items="draftItems" :draft-items="draftItems"
submit-url="/admin/manage/purchases" :submit-url="store.url()"
method="post" method="post"
submit-label="Simpan" submit-label="Simpan"
/> />

View File

@ -9,6 +9,7 @@ import type {
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed } from 'vue'; import { computed } from 'vue';
import PurchasePosForm from './form/PurchasePosForm.vue'; import PurchasePosForm from './form/PurchasePosForm.vue';
import { index, update } from '@/routes/admin/manage/purchases';
const props = defineProps<{ const props = defineProps<{
purchase: PurchaseEditItem; purchase: PurchaseEditItem;
@ -45,14 +46,14 @@ const initialData = computed(() => ({
<h2 class="text-2xl font-bold tracking-tight">Ubah Belanja</h2> <h2 class="text-2xl font-bold tracking-tight">Ubah Belanja</h2>
</div> </div>
<BackButton href="/admin/manage/purchases" /> <BackButton :href="index.url()" />
</div> </div>
<PurchasePosForm <PurchasePosForm
:suppliers="suppliers" :suppliers="suppliers"
:catalog="catalog" :catalog="catalog"
:initial-data="initialData" :initial-data="initialData"
:submit-url="`/admin/manage/purchases/${purchase.id}`" :submit-url="update.url(props.purchase.id)"
method="put" method="put"
submit-label="Perbarui" submit-label="Perbarui"
/> />

View File

@ -10,6 +10,7 @@ import AdminLayout from '@/layouts/AdminLayout.vue';
import type { PaginatedPurchases } from '@/types/purchase'; import type { PaginatedPurchases } from '@/types/purchase';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { index, create } from '@/routes/admin/manage/purchases';
import PurchaseGroupedTable from './table/PurchaseGroupedTable.vue'; import PurchaseGroupedTable from './table/PurchaseGroupedTable.vue';
const props = defineProps<{ const props = defineProps<{
@ -25,7 +26,7 @@ const { can } = useCan();
const search = ref(props.filters.search ?? ''); const search = ref(props.filters.search ?? '');
const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({ const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/manage/purchases', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });
@ -69,7 +70,7 @@ watch(
<CreateButton <CreateButton
v-if="can('purchases.create')" v-if="can('purchases.create')"
href="/admin/manage/purchases/create" :href="create.url()"
/> />
</div> </div>

View File

@ -49,6 +49,7 @@ import type { MediaItem, MediaUploadState } from '@/types/media';
import type { PurchaseCartItem, PurchaseCatalogItem, SelectOption } from '@/types/purchase'; import type { PurchaseCartItem, PurchaseCatalogItem, SelectOption } from '@/types/purchase';
import PosCatalogCard from '../../shared/PosCatalogCard.vue'; import PosCatalogCard from '../../shared/PosCatalogCard.vue';
import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue'; import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue';
import { store as syncDraftRoute, destroy as destroyDraftRoute } from '@/routes/admin/manage/purchases/draft_items';
const props = defineProps<{ const props = defineProps<{
suppliers: SelectOption[]; suppliers: SelectOption[];
catalog: PurchaseCatalogItem[]; catalog: PurchaseCatalogItem[];
@ -175,7 +176,7 @@ function lineSubtotal(item: PurchaseCartItem): number {
} }
async function syncDraftItem(priceId: number, quantity: number) { async function syncDraftItem(priceId: number, quantity: number) {
const { item } = await apiFetch<{ item: PurchaseCartItem }>('/admin/manage/purchases/draft-items', { const { item } = await apiFetch<{ item: PurchaseCartItem }>(syncDraftRoute.url(), {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
raw_material_price_id: priceId, raw_material_price_id: priceId,
@ -224,7 +225,7 @@ async function removeFromCart(index: number) {
if (isCreateMode.value) { if (isCreateMode.value) {
try { try {
await apiFetch(`/admin/manage/purchases/draft-items/${item.raw_material_price_id}`, { await apiFetch(destroyDraftRoute.url(item.raw_material_price_id), {
method: 'DELETE', method: 'DELETE',
}); });
} catch (error) { } catch (error) {

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { PurchaseListItem } from '@/types/purchase'; import type { PurchaseListItem } from '@/types/purchase';
import { edit, destroy } from '@/routes/admin/manage/purchases';
const props = defineProps<{ const props = defineProps<{
purchase: PurchaseListItem; purchase: PurchaseListItem;
@ -21,7 +22,7 @@ const deleteProcessing = ref(false);
function destroyPurchase() { function destroyPurchase() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/manage/purchases/${props.purchase.id}`, { router.delete(destroy.url(props.purchase.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
@ -41,7 +42,7 @@ function destroyPurchase() {
<Tooltip v-if="can('purchases.update')"> <Tooltip v-if="can('purchases.update')">
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/manage/purchases/${purchase.id}/edit`"> <Link :href="edit.url(props.purchase.id)">
<Pencil class="size-4" /> <Pencil class="size-4" />
<span class="sr-only">Ubah</span> <span class="sr-only">Ubah</span>
</Link> </Link>

View File

@ -34,6 +34,7 @@ import {
PRICE_TYPE_LABELS, PRICE_TYPE_LABELS,
} from '@/types/product'; } from '@/types/product';
import type { CuttingListItem } from '@/types/cutting'; import type { CuttingListItem } from '@/types/cutting';
import { verify } from '@/routes/admin/manage/stocks';
const props = defineProps<{ const props = defineProps<{
cutting: CuttingListItem; cutting: CuttingListItem;
@ -145,7 +146,7 @@ function submitVerify() {
})), })),
result_prices: buildResultPricesPayload(), result_prices: buildResultPricesPayload(),
})) }))
.post(`/admin/manage/stocks/${props.cutting.id}/verify`, { .post(verify.url(props.cutting.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
verifyDialogOpen.value = false; verifyDialogOpen.value = false;

View File

@ -14,6 +14,7 @@ import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import CategoryFormModal from './form/CategoryFormModal.vue'; import CategoryFormModal from './form/CategoryFormModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/master/categories';
const props = defineProps<{ const props = defineProps<{
categories: PaginatedCategories; categories: PaginatedCategories;
@ -31,7 +32,7 @@ const editingCategory = ref<CategoryListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/master/categories', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });

View File

@ -23,6 +23,7 @@ import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits'; import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { CategoryFormData, CategoryListItem } from '@/types/category'; import type { CategoryFormData, CategoryListItem } from '@/types/category';
import { store, update } from '@/routes/admin/master/categories';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -70,12 +71,12 @@ function submit() {
}; };
if (isEditing.value && props.category) { if (isEditing.value && props.category) {
form.put(`/admin/master/categories/${props.category.id}`, options); form.put(update.url(props.category.id), options);
return; return;
} }
form.post('/admin/master/categories', options); form.post(store.url(), options);
} }
</script> </script>

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { CategoryListItem } from '@/types/category'; import type { CategoryListItem } from '@/types/category';
import { destroy } from '@/routes/admin/master/categories';
const props = defineProps<{ const props = defineProps<{
category: CategoryListItem; category: CategoryListItem;
@ -25,7 +26,7 @@ const deleteProcessing = ref(false);
function destroyCategory() { function destroyCategory() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/master/categories/${props.category.id}`, { router.delete(destroy.url(props.category.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;

View File

@ -14,6 +14,7 @@ import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import CustomerFormModal from './form/CustomerFormModal.vue'; import CustomerFormModal from './form/CustomerFormModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/master/customers';
const props = defineProps<{ const props = defineProps<{
customers: PaginatedCustomers; customers: PaginatedCustomers;
@ -31,7 +32,7 @@ const editingCustomer = ref<CustomerListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/master/customers', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });

View File

@ -25,6 +25,7 @@ import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits'; import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { CustomerFormData, CustomerListItem } from '@/types/customer'; import type { CustomerFormData, CustomerListItem } from '@/types/customer';
import { store, update } from '@/routes/admin/master/customers';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -76,12 +77,12 @@ function submit() {
}; };
if (isEditing.value && props.customer) { if (isEditing.value && props.customer) {
form.put(`/admin/master/customers/${props.customer.id}`, options); form.put(update.url(props.customer.id), options);
return; return;
} }
form.post('/admin/master/customers', options); form.post(store.url(), options);
} }
</script> </script>

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { CustomerListItem } from '@/types/customer'; import type { CustomerListItem } from '@/types/customer';
import { destroy } from '@/routes/admin/master/customers';
const props = defineProps<{ const props = defineProps<{
customer: CustomerListItem; customer: CustomerListItem;
@ -25,7 +26,7 @@ const deleteProcessing = ref(false);
function destroyCustomer() { function destroyCustomer() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/master/customers/${props.customer.id}`, { router.delete(destroy.url(props.customer.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;

View File

@ -4,6 +4,7 @@ import AdminLayout from '@/layouts/AdminLayout.vue';
import type { CategoryOption } from '@/types/product'; import type { CategoryOption } from '@/types/product';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import ProductForm from './form/ProductForm.vue'; import ProductForm from './form/ProductForm.vue';
import { index, store } from '@/routes/admin/master/products';
defineProps<{ defineProps<{
categories: CategoryOption[]; categories: CategoryOption[];
@ -21,11 +22,11 @@ defineProps<{
<h2 class="text-2xl font-bold tracking-tight">Tambah Produk</h2> <h2 class="text-2xl font-bold tracking-tight">Tambah Produk</h2>
</div> </div>
<BackButton href="/admin/master/products" /> <BackButton :href="index.url()" />
</div> </div>
<ProductForm <ProductForm
submit-url="/admin/master/products" :submit-url="store.url()"
method="post" method="post"
submit-label="Simpan" submit-label="Simpan"
:categories="categories" :categories="categories"

View File

@ -5,6 +5,7 @@ import type { CategoryOption, ProductListItem } from '@/types/product';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed } from 'vue'; import { computed } from 'vue';
import ProductForm from './form/ProductForm.vue'; import ProductForm from './form/ProductForm.vue';
import { index, update } from '@/routes/admin/master/products';
const props = defineProps<{ const props = defineProps<{
product: ProductListItem & { description?: string | null }; product: ProductListItem & { description?: string | null };
@ -36,11 +37,11 @@ const initialData = computed(() => ({
<h2 class="text-2xl font-bold tracking-tight">Ubah Produk</h2> <h2 class="text-2xl font-bold tracking-tight">Ubah Produk</h2>
</div> </div>
<BackButton href="/admin/master/products" /> <BackButton :href="index.url()" />
</div> </div>
<ProductForm <ProductForm
:submit-url="`/admin/master/products/${product.id}`" :submit-url="update.url(props.product.id)"
method="put" method="put"
submit-label="Perbarui" submit-label="Perbarui"
:initial-data="initialData" :initial-data="initialData"

View File

@ -13,6 +13,7 @@ import type { DataTableFilterDef } from '@/types/data-table';
import type { CategoryOption, PaginatedProducts } from '@/types/product'; import type { CategoryOption, PaginatedProducts } from '@/types/product';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { index, create } from '@/routes/admin/master/products';
import ProductGroupedTable from './table/ProductGroupedTable.vue'; import ProductGroupedTable from './table/ProductGroupedTable.vue';
const props = defineProps<{ const props = defineProps<{
@ -33,7 +34,7 @@ const search = ref(props.filters.search ?? '');
const { query, setSearch, setFilter, resetFilters, syncFromServer } = const { query, setSearch, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/master/products', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['is_active', 'category_id', 'stock_status'], filterKeys: ['is_active', 'category_id', 'stock_status'],
}); });
@ -114,7 +115,7 @@ watch(
<CreateButton <CreateButton
v-if="can('products.create')" v-if="can('products.create')"
href="/admin/master/products/create" :href="create.url()"
/> />
</div> </div>

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { ProductListItem } from '@/types/product'; import type { ProductListItem } from '@/types/product';
import { edit, destroy } from '@/routes/admin/master/products';
const props = defineProps<{ const props = defineProps<{
product: ProductListItem; product: ProductListItem;
@ -21,7 +22,7 @@ const deleteProcessing = ref(false);
function destroyProduct() { function destroyProduct() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/master/products/${props.product.id}`, { router.delete(destroy.url(props.product.id), {
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
}, },
@ -40,7 +41,7 @@ function destroyProduct() {
<Tooltip v-if="can('products.update')"> <Tooltip v-if="can('products.update')">
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/master/products/${product.id}/edit`"> <Link :href="edit.url(props.product.id)">
<Pencil class="size-4" /> <Pencil class="size-4" />
<span class="sr-only">Ubah</span> <span class="sr-only">Ubah</span>
</Link> </Link>

View File

@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { ProductListItem } from '@/types/product'; import type { ProductListItem } from '@/types/product';
import { toggle_status } from '@/routes/admin/master/products';
const props = defineProps<{ const props = defineProps<{
product: ProductListItem; product: ProductListItem;
@ -32,7 +33,7 @@ function toggleStatus(checked: boolean) {
isActive.value = checked; isActive.value = checked;
processing.value = true; processing.value = true;
router.patch(`/admin/master/products/${props.product.id}/toggle-status`, { router.patch(toggle_status.url(props.product.id), {
is_active: checked, is_active: checked,
}, { }, {
preserveScroll: true, preserveScroll: true,

View File

@ -4,6 +4,7 @@ import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EnumOption } from '@/types/raw-material'; import type { EnumOption } from '@/types/raw-material';
import RawMaterialForm from './form/RawMaterialForm.vue'; import RawMaterialForm from './form/RawMaterialForm.vue';
import BackButton from '@/components/button/BackButton.vue'; import BackButton from '@/components/button/BackButton.vue';
import { index, store } from '@/routes/admin/master/raw_materials';
defineProps<{ defineProps<{
units: EnumOption[]; units: EnumOption[];
@ -22,9 +23,9 @@ defineProps<{
</h2> </h2>
</div> </div>
<BackButton href="/admin/master/raw-materials" /> <BackButton :href="index.url()" />
</div> </div>
<RawMaterialForm submit-url="/admin/master/raw-materials" method="post" submit-label="Simpan" :units="units" /> <RawMaterialForm :submit-url="store.url()" method="post" submit-label="Simpan" :units="units" />
</AdminLayout> </AdminLayout>
</template> </template>

View File

@ -5,6 +5,7 @@ import type { EnumOption, RawMaterialListItem } from '@/types/raw-material';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed } from 'vue'; import { computed } from 'vue';
import RawMaterialForm from './form/RawMaterialForm.vue'; import RawMaterialForm from './form/RawMaterialForm.vue';
import { index, update } from '@/routes/admin/master/raw_materials';
const props = defineProps<{ const props = defineProps<{
rawMaterial: RawMaterialListItem; rawMaterial: RawMaterialListItem;
@ -37,11 +38,11 @@ const initialData = computed(() => ({
</h2> </h2>
</div> </div>
<BackButton href="/admin/master/raw-materials" /> <BackButton :href="index.url()" />
</div> </div>
<RawMaterialForm <RawMaterialForm
:submit-url="`/admin/master/raw-materials/${rawMaterial.id}`" :submit-url="update.url(props.rawMaterial.id)"
method="put" method="put"
submit-label="Perbarui" submit-label="Perbarui"
:initial-data="initialData" :initial-data="initialData"

View File

@ -13,6 +13,7 @@ import type { DataTableFilterDef } from '@/types/data-table';
import type { PaginatedRawMaterials } from '@/types/raw-material'; import type { PaginatedRawMaterials } from '@/types/raw-material';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { index, create } from '@/routes/admin/master/raw_materials';
import RawMaterialGroupedTable from './table/RawMaterialGroupedTable.vue'; import RawMaterialGroupedTable from './table/RawMaterialGroupedTable.vue';
const props = defineProps<{ const props = defineProps<{
@ -31,7 +32,7 @@ const search = ref(props.filters.search ?? '');
const { query, setSearch, setFilter, resetFilters, syncFromServer } = const { query, setSearch, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/master/raw-materials', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['is_active', 'stock_status'], filterKeys: ['is_active', 'stock_status'],
}); });
@ -103,7 +104,7 @@ watch(
<CreateButton <CreateButton
v-if="can('raw_materials.create')" v-if="can('raw_materials.create')"
href="/admin/master/raw-materials/create" :href="create.url()"
/> />
</div> </div>

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { RawMaterialListItem } from '@/types/raw-material'; import type { RawMaterialListItem } from '@/types/raw-material';
import { edit, destroy } from '@/routes/admin/master/raw_materials';
const props = defineProps<{ const props = defineProps<{
material: RawMaterialListItem; material: RawMaterialListItem;
@ -21,7 +22,7 @@ const deleteProcessing = ref(false);
function destroyRawMaterial() { function destroyRawMaterial() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/master/raw-materials/${props.material.id}`, { router.delete(destroy.url(props.material.id), {
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
}, },
@ -40,7 +41,7 @@ function destroyRawMaterial() {
<Tooltip v-if="can('raw_materials.update')"> <Tooltip v-if="can('raw_materials.update')">
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/master/raw-materials/${material.id}/edit`"> <Link :href="edit.url(props.material.id)">
<Pencil class="size-4" /> <Pencil class="size-4" />
<span class="sr-only">Ubah</span> <span class="sr-only">Ubah</span>
</Link> </Link>

View File

@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { RawMaterialListItem } from '@/types/raw-material'; import type { RawMaterialListItem } from '@/types/raw-material';
import { toggle_status } from '@/routes/admin/master/raw_materials';
const props = defineProps<{ const props = defineProps<{
material: RawMaterialListItem; material: RawMaterialListItem;
@ -32,7 +33,7 @@ function toggleStatus(checked: boolean) {
isActive.value = checked; isActive.value = checked;
processing.value = true; processing.value = true;
router.patch(`/admin/master/raw-materials/${props.material.id}/toggle-status`, { router.patch(toggle_status.url(props.material.id), {
is_active: checked, is_active: checked,
}, { }, {
preserveScroll: true, preserveScroll: true,

View File

@ -14,6 +14,7 @@ import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import SupplierFormModal from './form/SupplierFormModal.vue'; import SupplierFormModal from './form/SupplierFormModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/master/suppliers';
const props = defineProps<{ const props = defineProps<{
suppliers: PaginatedSuppliers; suppliers: PaginatedSuppliers;
@ -31,7 +32,7 @@ const editingSupplier = ref<SupplierListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/master/suppliers', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });

View File

@ -25,6 +25,7 @@ import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits'; import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { SupplierFormData, SupplierListItem } from '@/types/supplier'; import type { SupplierFormData, SupplierListItem } from '@/types/supplier';
import { store, update } from '@/routes/admin/master/suppliers';
const open = defineModel<boolean>('open', { default: false }); const open = defineModel<boolean>('open', { default: false });
@ -76,12 +77,12 @@ function submit() {
}; };
if (isEditing.value && props.supplier) { if (isEditing.value && props.supplier) {
form.put(`/admin/master/suppliers/${props.supplier.id}`, options); form.put(update.url(props.supplier.id), options);
return; return;
} }
form.post('/admin/master/suppliers', options); form.post(store.url(), options);
} }
</script> </script>

View File

@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { SupplierListItem } from '@/types/supplier'; import type { SupplierListItem } from '@/types/supplier';
import { destroy } from '@/routes/admin/master/suppliers';
const props = defineProps<{ const props = defineProps<{
supplier: SupplierListItem; supplier: SupplierListItem;
@ -25,7 +26,7 @@ const deleteProcessing = ref(false);
function destroySupplier() { function destroySupplier() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/master/suppliers/${props.supplier.id}`, { router.delete(destroy.url(props.supplier.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;

View File

@ -9,6 +9,7 @@ import type { ActivityLogListItem, PaginatedActivityLogs, SelectOption } from '@
import type { DataTableSort } from '@/types/data-table'; import type { DataTableSort } from '@/types/data-table';
import ActivityLogDetailModal from './table/ActivityLogDetailModal.vue'; import ActivityLogDetailModal from './table/ActivityLogDetailModal.vue';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
import { index } from '@/routes/admin/system/activity_logs';
const props = defineProps<{ const props = defineProps<{
activityLogs: PaginatedActivityLogs; activityLogs: PaginatedActivityLogs;
@ -28,7 +29,7 @@ const detailModalOpen = ref(false);
const selectedActivityLog = ref<ActivityLogListItem | null>(null); const selectedActivityLog = ref<ActivityLogListItem | null>(null);
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({ const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/system/activity-logs', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['event', 'subject_type'], filterKeys: ['event', 'subject_type'],
}); });

View File

@ -3,6 +3,7 @@ import { Head, Link } from '@inertiajs/vue3';
import AdminLayout from '@/layouts/AdminLayout.vue'; import AdminLayout from '@/layouts/AdminLayout.vue';
import RoleForm from './form/RoleForm.vue'; import RoleForm from './form/RoleForm.vue';
import BackButton from '@/components/button/BackButton.vue'; import BackButton from '@/components/button/BackButton.vue';
import { index, store } from '@/routes/admin/system/roles';
interface PermissionOption { interface PermissionOption {
value: string; value: string;
@ -27,9 +28,9 @@ defineProps<{
</h2> </h2>
</div> </div>
<BackButton href="/admin/system/roles" /> <BackButton :href="index.url()" />
</div> </div>
<RoleForm submit-url="/admin/system/roles" method="post" submit-label="Simpan" :permissions="permissions" /> <RoleForm :submit-url="store.url()" method="post" submit-label="Simpan" :permissions="permissions" />
</AdminLayout> </AdminLayout>
</template> </template>

View File

@ -3,6 +3,7 @@ import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue'; import AdminLayout from '@/layouts/AdminLayout.vue';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import RoleForm from './form/RoleForm.vue'; import RoleForm from './form/RoleForm.vue';
import { index, update } from '@/routes/admin/system/roles';
interface PermissionOption { interface PermissionOption {
value: string; value: string;
@ -33,11 +34,11 @@ defineProps<{
<h2 class="text-2xl font-bold tracking-tight">Ubah Role</h2> <h2 class="text-2xl font-bold tracking-tight">Ubah Role</h2>
</div> </div>
<BackButton href="/admin/system/roles" /> <BackButton :href="index.url()" />
</div> </div>
<RoleForm <RoleForm
:submit-url="`/admin/system/roles/${role.id}`" :submit-url="update.url(role.id)"
method="put" method="put"
submit-label="Perbarui" submit-label="Perbarui"
:initial-data="role" :initial-data="role"

View File

@ -11,6 +11,7 @@ import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableSort } from '@/types/data-table'; import type { DataTableSort } from '@/types/data-table';
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { index, create } from '@/routes/admin/system/roles';
import type { RoleListItem } from './table/columns'; import type { RoleListItem } from './table/columns';
import { createColumns } from './table/columns'; import { createColumns } from './table/columns';
@ -37,7 +38,7 @@ const search = ref(props.filters.search ?? '');
const { query, setSearch, setSort, resetFilters, syncFromServer } = const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({ useDataTableQuery({
url: '/admin/system/roles', url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
}); });
@ -90,7 +91,7 @@ watch(
<CreateButton <CreateButton
v-if="can('roles.create')" v-if="can('roles.create')"
href="/admin/system/roles/create" :href="create.url()"
/> />
</div> </div>

View File

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { Link, router } from '@inertiajs/vue3'; import { Link, router } from '@inertiajs/vue3';
import { Pencil, Trash2 } from '@lucide/vue'; import { Pencil, Trash2 } from '@lucide/vue';
import { ref } from 'vue'; import { computed, ref } from 'vue';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import ConfirmDialog from '@/components/ConfirmDialog.vue'; import ConfirmDialog from '@/components/ConfirmDialog.vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan'; import { useCan } from '@/composables/useCan';
import type { RoleListItem } from './columns'; import type { RoleListItem } from './columns';
import { edit, destroy } from '@/routes/admin/system/roles';
const props = defineProps<{ const props = defineProps<{
role: RoleListItem; role: RoleListItem;
@ -18,10 +19,14 @@ const { can } = useCan();
const deleteConfirmOpen = ref(false); const deleteConfirmOpen = ref(false);
const deleteProcessing = ref(false); const deleteProcessing = ref(false);
const isSystemRole = computed(() => {
return ['developer', 'owner', 'admin-toko', 'admin-bahan-baku', 'direktur', 'marketing', 'cashier', 'non-operator'].includes(props.role.name);
});
function destroyRole() { function destroyRole() {
deleteProcessing.value = true; deleteProcessing.value = true;
router.delete(`/admin/system/roles/${props.role.id}`, { router.delete(destroy.url(props.role.id), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
deleteConfirmOpen.value = false; deleteConfirmOpen.value = false;
@ -41,7 +46,7 @@ function destroyRole() {
<Tooltip v-if="can('roles.update')"> <Tooltip v-if="can('roles.update')">
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" as-child> <Button variant="ghost" size="icon" class="size-8" as-child>
<Link :href="`/admin/system/roles/${role.id}/edit`"> <Link :href="edit.url(props.role.id)">
<Pencil class="size-4" /> <Pencil class="size-4" />
<span class="sr-only">Ubah</span> <span class="sr-only">Ubah</span>
</Link> </Link>

View File

@ -9,6 +9,7 @@ import { Card, CardContent } from '@/components/ui/card';
import { createMediaUploadState } from '@/types/media'; import { createMediaUploadState } from '@/types/media';
import type { MediaUploadState } from '@/types/media'; import type { MediaUploadState } from '@/types/media';
import type { HomepageSettingsData } from '@/types/setting'; import type { HomepageSettingsData } from '@/types/setting';
import { update } from '@/routes/admin/system/settings/homepage';
const props = defineProps<{ const props = defineProps<{
data: HomepageSettingsData; data: HomepageSettingsData;
@ -42,7 +43,7 @@ function submit() {
formData.append('gallery_images_remove[]', String(id)); formData.append('gallery_images_remove[]', String(id));
}); });
router.put('/admin/system/settings/homepage', formData, { router.put(update.url(), formData, {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
heroImage.value = createMediaUploadState(); heroImage.value = createMediaUploadState();

View File

@ -9,6 +9,7 @@ import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from '@/component
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { HrSettingsData } from '@/types/setting'; import type { HrSettingsData } from '@/types/setting';
import { update } from '@/routes/admin/system/settings/hr';
const props = defineProps<{ const props = defineProps<{
data: HrSettingsData; data: HrSettingsData;
@ -22,7 +23,7 @@ const form = useForm({
}); });
function submit() { function submit() {
form.put('/admin/system/settings/hr', { form.put(update.url(), {
preserveScroll: true, preserveScroll: true,
onError: () => { onError: () => {
toast.error('Gagal menyimpan pengaturan HR.'); toast.error('Gagal menyimpan pengaturan HR.');

View File

@ -20,6 +20,7 @@ import type {
MarketplaceSettingsData, MarketplaceSettingsData,
} from '@/types/setting'; } from '@/types/setting';
import MarketplaceFeeField from './MarketplaceFeeField.vue'; import MarketplaceFeeField from './MarketplaceFeeField.vue';
import { update } from '@/routes/admin/system/settings/marketplace';
const props = defineProps<{ const props = defineProps<{
data: MarketplaceSettingsData; data: MarketplaceSettingsData;
@ -64,7 +65,7 @@ function feeErrors(key: string): string[] {
} }
function submit() { function submit() {
form.put('/admin/system/settings/marketplace', { form.put(update.url(), {
preserveScroll: true, preserveScroll: true,
onError: () => { onError: () => {
toast.error('Gagal menyimpan pengaturan marketplace.'); toast.error('Gagal menyimpan pengaturan marketplace.');

View File

@ -8,6 +8,7 @@ import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from '@/component
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { formErrors } from '@/lib/form'; import { formErrors } from '@/lib/form';
import type { SocialMediaSettingsData } from '@/types/setting'; import type { SocialMediaSettingsData } from '@/types/setting';
import { update } from '@/routes/admin/system/settings/social_media';
const props = defineProps<{ const props = defineProps<{
data: SocialMediaSettingsData; data: SocialMediaSettingsData;
@ -20,7 +21,7 @@ const form = useForm({
}); });
function submit() { function submit() {
form.put('/admin/system/settings/social-media', { form.put(update.url(), {
preserveScroll: true, preserveScroll: true,
onError: () => { onError: () => {
toast.error('Gagal menyimpan pengaturan media sosial.'); toast.error('Gagal menyimpan pengaturan media sosial.');

View File

@ -14,6 +14,7 @@ import { formErrors } from '@/lib/form';
import { createMediaUploadState } from '@/types/media'; import { createMediaUploadState } from '@/types/media';
import type { MediaUploadState } from '@/types/media'; import type { MediaUploadState } from '@/types/media';
import type { SystemSettingsData } from '@/types/setting'; import type { SystemSettingsData } from '@/types/setting';
import { update } from '@/routes/admin/system/settings/system';
const props = defineProps<{ const props = defineProps<{
data: SystemSettingsData; data: SystemSettingsData;
@ -58,7 +59,7 @@ function buildFormData(): FormData {
function submit() { function submit() {
const payload = buildFormData(); const payload = buildFormData();
form.transform(() => payload).put('/admin/system/settings/system', { form.transform(() => payload).put(update.url(), {
preserveScroll: true, preserveScroll: true,
onError: () => { onError: () => {
toast.error('Gagal menyimpan pengaturan sistem.'); toast.error('Gagal menyimpan pengaturan sistem.');