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;
use App\Enums\EmployeeStatus;
use App\Models\Attendance;
use App\Models\Employee;
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);
$employees = Employee::query()
->where('status', EmployeeStatus::ACTIVE)
->whereNotNull('user_id')
->whereHas('user', fn ($query) => $query->active())
->get();
$sentCount = 0;
@ -51,7 +49,7 @@ public function handle(PushNotificationService $pushNotificationService): int
'⏰ Pengingat Presensi',
"Jangan lupa melakukan presensi masuk sebelum pukul {$scheduledTime}.",
$employee->user_id,
'/admin/hr/attendances',
route('admin.hr.attendances.index'),
);
$sentCount++;

View File

@ -97,7 +97,7 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user):
'💰 Setoran Kas',
"Setoran kas baru {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.",
['owner', 'developer', 'admin-toko'],
'/admin/finance/cash',
route('admin.finance.cash.index'),
);
return $transaction;
@ -141,7 +141,7 @@ public function withdraw(CashAccount $cashAccount, array $validated, User $user)
'🏦 Tarik Kas',
"Tarik kas {$transaction->amount_formatted} dengan keterangan: {$transaction->description} oleh {$user->profile?->full_name}.",
['owner', 'developer', 'admin-toko'],
'/admin/finance/cash',
route('admin.finance.cash.index'),
);
return $transaction;
@ -250,7 +250,7 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v
'✏️ Transaksi Kas Diperbarui',
"Transaksi kas dengan keterangan {$transaction->description} diperbarui menjadi senilai {$transaction->amount_formatted}.",
['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 senilai {$transaction->amount_formatted} dengan keterangan {$transaction->description} telah dihapus.",
['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',
"Karyawan {$user->profil?->full_name} mengajukan kasbon sebesar {$employeeAdvance->amount_formatted} dengan keterangan: {$employeeAdvance->description}.",
['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 sebesar {$employeeAdvance->amount_formatted} telah diperbarui oleh {$user->profile?->full_name}.",
['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 sebesar {$amount} dengan keterangan {$description} telah dihapus.",
['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',
"Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} telah disetujui.",
$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',
"Pengajuan kasbon Anda sebesar {$employeeAdvance->amount_formatted} ditolak dengan alasan: {$reason}.",
$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 sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dicatat oleh {$user->profile?->full_name}.",
['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 dengan keterangan {$expense->description} diperbarui menjadi sebesar {$expense->amount_formatted}.",
['owner', 'developer'],
'/admin/finance/expenses',
route('admin.finance.expenses.index'),
);
}
@ -137,7 +137,7 @@ public function delete(Expense $expense): void
'🗑️ Pengeluaran Dihapus',
"Pengeluaran sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dihapus.",
['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}",
"Gaji periode {$payroll->payrollPeriod->period_label} disesuaikan: {$typeLabel} sebesar {$formattedAmount} ({$validated['description']}).",
$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 Anda untuk periode {$payroll->payrollPeriod->period_label} diperbarui: {$typeLabel} menjadi {$formattedAmount} ({$validated['description']}).",
$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 Anda untuk periode {$payroll->payrollPeriod->period_label} telah dihapus.",
$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 Anda untuk periode {$payroll->payrollPeriod->period_label} senilai {$payroll->total_amount_formatted} telah dibayarkan.",
$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',
"Karyawan {$user->profile?->full_name} melakukan presensi masuk.",
['owner', 'developer'],
'/admin/hr/attendances',
route('admin.hr.attendances.index'),
);
}
@ -154,7 +154,7 @@ public function checkOut(array $validated, User $user): void
'⏰ Presensi Pulang',
"Karyawan {$user->profile?->full_name} melakukan presensi pulang (Durasi kerja: ".round($workDurationMinutes / 60, 1).' jam).',
['owner', 'developer'],
'/admin/hr/attendances',
route('admin.hr.attendances.index'),
);
}
@ -172,7 +172,7 @@ public function delete(Attendance $attendance): void
'🗑️ Presensi Dihapus',
"Data presensi {$employeeName} tanggal {$date} telah dihapus.",
['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',
"Karyawan {$user->profile?->full_name} mengajukan cuti selama {$leaveRequest->total_days} hari mulai dari tanggal {$leaveRequest->start_date_formatted}.",
['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 oleh {$user->profile?->full_name} telah diperbarui.",
['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 {$totalDays} hari oleh {$employeeName} telah dihapus.",
['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 Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) telah disetujui.",
$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 Anda selama {$leaveRequest->total_days} hari ({$leaveRequest->start_date_formatted} - {$leaveRequest->end_date_formatted}) ditolak dengan alasan: '{$reason}'.",
$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 dengan deskripsi ({$cutting->description}) telah dimulai oleh {$user->profile?->full_name}.",
['owner', 'developer'],
'/admin/manage/cuttings',
route('admin.manage.cuttings.index'),
);
return $cutting;
@ -536,7 +536,7 @@ public function update(Cutting $cutting, array $validated): void
'✏️ Proses Cutting Diperbarui',
"Proses cutting dengan deskripsi '{$description}' telah diperbarui.",
['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 dengan deskripsi {$description} telah dihapus.",
['owner', 'developer'],
'/admin/manage/cuttings',
route('admin.manage.cuttings.index'),
);
}
@ -655,7 +655,7 @@ public function transitionStatus(
$title,
$message,
$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 {$order->order_number} senilai {$order->total_amount_formatted} telah dibuat oleh {$user->profile?->full_name}.",
['owner', 'developer'],
'/admin/manage/orders',
route('admin.manage.orders.index'),
);
return $order;
@ -563,7 +563,7 @@ public function update(Order $order, array $validated): void
'✏️ Pesanan Diperbarui',
"Pesanan {$order->order_number} senilai {$order->total_amount_formatted} telah diperbarui.",
['owner', 'developer'],
'/admin/manage/orders',
route('admin.manage.orders.index'),
);
}
@ -593,7 +593,7 @@ public function delete(Order $order): void
'🗑️ Pesanan Dihapus',
"Pesanan {$orderNumber} senilai {$order->total_amount_formatted} telah dihapus.",
['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',
"Pesanan {$order->order_number} diubah statusnya menjadi {$status->label()}.",
['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',
"Pembelian dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} telah ditambahkan oleh {$user->profile?->full_name}.",
['owner', 'developer'],
'/admin/manage/purchases',
route('admin.manage.purchases.index'),
);
return $purchase;
@ -301,7 +301,7 @@ public function update(Purchase $purchase, array $validated): void
'✏️ Belanja Diperbarui',
"Pembelian dari supplier {$purchase->supplier->name} senilai {$purchase->total_formatted} telah diperbarui.",
['owner', 'developer'],
'/admin/manage/purchases',
route('admin.manage.purchases.index'),
);
}
@ -325,7 +325,7 @@ public function delete(Purchase $purchase): void
'🗑️ Belanja Dihapus',
"Pembelian dari supplier {$supplierName} senilai {$purchase->total_formatted} telah dihapus.",
['owner', 'developer'],
'/admin/manage/purchases',
route('admin.manage.purchases.index'),
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,6 +25,8 @@ import type {
ProfileUser,
} from '@/types/account';
import { update } from '@/routes/admin/account/profile';
const props = defineProps<{
user: ProfileUser;
genders: EnumOption[];
@ -41,7 +43,7 @@ const form = useForm<ProfileFormData>({
});
function submit() {
form.put('/admin/account/profile', {
form.put(update.url(), {
preserveScroll: true,
onError: () => {
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 CashTransactionFormModal from './form/CashTransactionFormModal.vue';
import { createColumns } from './table/columns';
import { index } from '@/routes/admin/finance/cash';
const props = defineProps<{
cashAccount: CashAccount;
@ -33,7 +34,7 @@ const formModalMode = ref<'deposit' | 'withdrawal'>(CashTransactionType.DEPOSIT)
const editingTransaction = ref<CashTransactionListItem | null>(null);
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/finance/cash',
url: index.url(),
initial: { ...props.filters },
filterKeys: ['reference_type'],
});

View File

@ -29,6 +29,8 @@ import { parseRupiah } from '@/lib/rupiah';
import type { CashTransactionListItem } from '@/types/cash';
import { appendRootPhotosToFormData, createMediaUploadState } 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 });
@ -111,12 +113,12 @@ function submit() {
};
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;
}
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);
}

View File

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

View File

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

View File

@ -26,6 +26,7 @@ import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form';
import { parseRupiah } from '@/lib/rupiah';
import type { EmployeeAdvanceFormData, EmployeeAdvanceListItem } from '@/types/employee-advance';
import { store, update } from '@/routes/admin/finance/employee_advances';
const open = defineModel<boolean>('open', { default: false });
@ -94,12 +95,12 @@ function submit() {
};
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;
}
form.transform(() => payload).post('/admin/finance/employee-advances', options);
form.transform(() => payload).post(store.url(), options);
}
</script>

View File

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

View File

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

View File

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

View File

@ -29,6 +29,8 @@ import type { ExpenseListItem } from '@/types/expense';
import { appendRootPhotosToFormData, createMediaUploadState } 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 props = defineProps<{
@ -99,12 +101,12 @@ function submit() {
};
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;
}
form.transform(() => payload).post('/admin/finance/expenses', options);
form.transform(() => payload).post(store.url(), options);
}
</script>

View File

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

View File

@ -27,6 +27,8 @@ import type { PayrollListItem, PayrollPageProps } from '@/types/payroll';
import PayrollAdjustmentModal from './form/PayrollAdjustmentModal.vue';
import { createColumns } from './table/columns';
import { index } from '@/routes/admin/finance/payroll';
const props = defineProps<PayrollPageProps>();
const { can } = useCan();
@ -48,7 +50,7 @@ const currentAdjustingPayroll = computed(() => {
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/finance/payroll',
url: index.url(),
initial: {
...props.filters,
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 { parseRupiah } from '@/lib/rupiah';
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 });
@ -76,7 +77,7 @@ function cancelEdit() {
}
function deleteAdj(adjustment: PayrollAdjustmentItem) {
router.delete(`/admin/finance/payroll/adjustments/${adjustment.id}`, {
router.delete(destroy.url(adjustment.id), {
preserveScroll: true,
onSuccess: () => {
toast.success('Penyesuaian berhasil dihapus.');
@ -98,7 +99,7 @@ function submit() {
}));
if (editingAdjustment.value) {
payload.put(`/admin/finance/payroll/adjustments/${editingAdjustment.value.id}`, {
payload.put(update.url(editingAdjustment.value.id), {
preserveScroll: true,
onSuccess: () => {
cancelEdit();
@ -108,7 +109,7 @@ function submit() {
},
});
} else {
payload.post(`/admin/finance/payroll/${props.payroll.id}/adjustments`, {
payload.post(store.url(props.payroll.id), {
preserveScroll: true,
onSuccess: () => {
resetForm();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,6 +44,8 @@ import type {
} from '@/types/cutting';
import PosCatalogCard from '../../shared/PosCatalogCard.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<{
rawMaterialCatalog: CuttingRawMaterialCatalogItem[];
@ -245,7 +247,7 @@ function upsertResultCartItem(item: CuttingResultCartItem) {
}
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',
body: JSON.stringify({
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) {
const { item } = await apiFetch<{ item: CuttingResultCartItem }>('/admin/manage/cuttings/draft-results', {
const { item } = await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
method: 'POST',
body: JSON.stringify({
product_variant_id: variant.id,
@ -371,7 +373,7 @@ async function removeMaterial(index: number) {
if (isCreateMode.value) {
try {
await apiFetch(
`/admin/manage/cuttings/draft-materials/${item.raw_material_price_id}`,
draft_materials.destroy.url(item.raw_material_price_id),
{ method: 'DELETE' },
);
} catch (error) {
@ -487,7 +489,7 @@ async function removeResult(index: number) {
if (isCreateMode.value) {
try {
await apiFetch(
`/admin/manage/cuttings/draft-results/${item.product_variant_id}`,
draft_results.destroy.url(item.product_variant_id),
{ method: 'DELETE' },
);
} catch (error) {
@ -516,7 +518,7 @@ async function syncMaterialField(index: number) {
const item = materialCart.value[index];
try {
await apiFetch<{ item: CuttingMaterialCartItem }>('/admin/manage/cuttings/draft-materials', {
await apiFetch<{ item: CuttingMaterialCartItem }>(draft_materials.store.url(), {
method: 'POST',
body: JSON.stringify({
raw_material_price_id: item.raw_material_price_id,
@ -537,7 +539,7 @@ async function syncResultField(index: number) {
const item = resultCart.value[index];
try {
await apiFetch<{ item: CuttingResultCartItem }>('/admin/manage/cuttings/draft-results', {
await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
method: 'POST',
body: JSON.stringify({
product_variant_id: item.product_variant_id,

View File

@ -38,6 +38,8 @@ import {
PRICE_TYPE_LABELS,
} from '@/types/product';
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<{
cutting: CuttingListItem;
@ -152,7 +154,7 @@ const canEdit = computed(
() => props.cutting.is_editable && can('cuttings.update'),
);
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 {
@ -206,7 +208,7 @@ function submitVerify() {
})),
result_prices: buildResultPricesPayload(),
}))
.post(`/admin/manage/cuttings/${props.cutting.id}/status`, {
.post(transition_status.url(props.cutting.id), {
preserveScroll: true,
onSuccess: () => {
verifyDialogOpen.value = false;
@ -231,7 +233,7 @@ function transitionStatus() {
statusProcessing.value = true;
router.post(
`/admin/manage/cuttings/${props.cutting.id}/status`,
transition_status.url(props.cutting.id),
{
status: pendingAction.value.status,
},
@ -258,7 +260,7 @@ function transitionStatus() {
}
function submitReject() {
rejectForm.post(`/admin/manage/cuttings/${props.cutting.id}/status`, {
rejectForm.post(transition_status.url(props.cutting.id), {
preserveScroll: true,
onSuccess: () => {
rejectDialogOpen.value = false;
@ -285,7 +287,7 @@ watch(rejectDialogOpen, (isOpen) => {
function destroyCutting() {
deleteProcessing.value = true;
router.delete(`/admin/manage/cuttings/${props.cutting.id}`, {
router.delete(destroy.url(props.cutting.id), {
preserveScroll: true,
onSuccess: () => {
deleteConfirmOpen.value = false;
@ -348,7 +350,7 @@ function actionIcon(status: string) {
<Tooltip v-if="canEdit">
<TooltipTrigger 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" />
<span class="sr-only">Ubah</span>
</Link>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,4 @@
<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 { Button } from '@/components/ui/button';
import {
@ -22,6 +19,10 @@ import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { apiFetch } from '@/lib/api';
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 });
@ -50,21 +51,25 @@ async function submit() {
processing.value = true;
try {
const customer = await apiFetch<{ id: number; name: string }>('/admin/master/customers/api', {
method: 'POST',
body: JSON.stringify({
name: form.name,
phone_number: form.phone_number || null,
address: form.address || null,
}),
});
const customer = await apiFetch<{ id: number; name: string }>(
api.url(),
{
method: 'POST',
body: JSON.stringify({
name: form.name,
phone_number: form.phone_number || null,
address: form.address || null,
}),
},
);
emit('created', customer);
open.value = false;
resetForm();
toast.success('Pelanggan berhasil ditambahkan.');
} 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')) {
errors.name = message;
@ -88,26 +93,53 @@ async function submit() {
<FieldGroup>
<FieldSet class="grid gap-4">
<Field>
<FieldLabel for="quick-customer-name" required>Nama</FieldLabel>
<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]" />
<FieldLabel for="quick-customer-name" required
>Nama</FieldLabel
>
<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>
<FieldLabel for="quick-customer-phone">Nomor Telepon</FieldLabel>
<PhoneNumberInput id="quick-customer-phone" v-model="form.phone_number"
placeholder="Contoh: 08123456789" />
<FieldLabel for="quick-customer-phone"
>Nomor Telepon</FieldLabel
>
<PhoneNumberInput
id="quick-customer-phone"
v-model="form.phone_number"
placeholder="Contoh: 08123456789"
/>
</Field>
<Field>
<FieldLabel for="quick-customer-address">Alamat</FieldLabel>
<Textarea id="quick-customer-address" v-model="form.address" rows="3"
placeholder="Contoh: Jl. Mawar No. 12, Surabaya" />
<FieldLabel for="quick-customer-address"
>Alamat</FieldLabel
>
<Textarea
id="quick-customer-address"
v-model="form.address"
rows="3"
placeholder="Contoh: Jl. Mawar No. 12, Surabaya"
/>
</Field>
</FieldSet>
</FieldGroup>
<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
</Button>
<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 PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.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<{
customers: SelectOption[];
@ -200,7 +201,7 @@ watch(
try {
const { items } = await apiFetch<{ items: OrderCartItem[] }>(
'/admin/manage/orders/draft-items/resync-prices',
resyncPricesRoute.url(),
{
method: 'PUT',
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) {
const { item } = await apiFetch<{ item: OrderCartItem }>('/admin/manage/orders/draft-items', {
const { item } = await apiFetch<{ item: OrderCartItem }>(syncDraftRoute.url(), {
method: 'POST',
body: JSON.stringify({
product_variant_id: variantId,
@ -364,7 +365,9 @@ async function removeFromCart(index: number) {
if (isCreateMode.value) {
try {
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',
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { useCan } from '@/composables/useCan';
import type { RawMaterialListItem } from '@/types/raw-material';
import { toggle_status } from '@/routes/admin/master/raw_materials';
const props = defineProps<{
material: RawMaterialListItem;
@ -32,7 +33,7 @@ function toggleStatus(checked: boolean) {
isActive.value = checked;
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,
}, {
preserveScroll: true,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,14 @@
<script setup lang="ts">
import { Link, router } from '@inertiajs/vue3';
import { Pencil, Trash2 } from '@lucide/vue';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { toast } from 'vue-sonner';
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useCan } from '@/composables/useCan';
import type { RoleListItem } from './columns';
import { edit, destroy } from '@/routes/admin/system/roles';
const props = defineProps<{
role: RoleListItem;
@ -18,10 +19,14 @@ const { can } = useCan();
const deleteConfirmOpen = 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() {
deleteProcessing.value = true;
router.delete(`/admin/system/roles/${props.role.id}`, {
router.delete(destroy.url(props.role.id), {
preserveScroll: true,
onSuccess: () => {
deleteConfirmOpen.value = false;
@ -41,7 +46,7 @@ function destroyRole() {
<Tooltip v-if="can('roles.update')">
<TooltipTrigger 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" />
<span class="sr-only">Ubah</span>
</Link>

View File

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

View File

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

View File

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

View File

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