refactor: standardize table row actions by implementing reusable RowEditAction and RowDeleteAction components

This commit is contained in:
Yoga Pangestu 2026-06-23 09:11:18 +07:00
parent 06e313d168
commit 2f8ece4b0a
37 changed files with 831 additions and 524 deletions

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { Button } from '@/components/ui/button';
withDefaults(
defineProps<{
href?: string;
label?: string;
}>(),
{
label: 'Kembali',
}
);
</script>
<template>
<Button variant="outline" :as-child="!!href" class="shrink-0 self-start sm:self-center">
<Link v-if="href" :href="href">
<ArrowLeft class="size-4" />
<slot>{{ label }}</slot>
</Link>
<template v-else>
<ArrowLeft class="size-4" />
<slot>{{ label }}</slot>
</template>
</Button>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { Button } from '@/components/ui/button';
withDefaults(
defineProps<{
href?: string;
label?: string;
}>(),
{
label: 'Tambah',
}
);
</script>
<template>
<Button :as-child="!!href" class="shrink-0 self-start sm:self-center">
<Link v-if="href" :href="href">
<Plus class="size-4" />
<slot>{{ label }}</slot>
</Link>
<template v-else>
<Plus class="size-4" />
<slot>{{ label }}</slot>
</template>
</Button>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { Check } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
defineProps<{
tooltip?: string;
disabled?: boolean;
}>();
</script>
<template>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8 text-green-600 hover:text-green-700" :disabled="disabled">
<Check class="size-4" />
<span class="sr-only">{{ tooltip || 'Setujui' }}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{{ tooltip || 'Setujui' }}</TooltipContent>
</Tooltip>
</template>

View File

@ -0,0 +1,61 @@
<script setup lang="ts">
import { router } from '@inertiajs/vue3';
import { Trash2 } from '@lucide/vue';
import { 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';
const props = defineProps<{
title?: string;
description?: string;
actionUrl?: string; // If provided, handles the deletion request automatically.
errorMessage?: string;
}>();
const emit = defineEmits<{
confirm: [];
}>();
const deleteConfirmOpen = ref(false);
const deleteProcessing = ref(false);
function handleConfirm() {
if (props.actionUrl) {
deleteProcessing.value = true;
router.delete(props.actionUrl, {
preserveScroll: true,
onSuccess: () => {
deleteConfirmOpen.value = false;
},
onError: (errors) => {
const firstError = Object.values(errors)[0];
toast.error(firstError || props.errorMessage || 'Gagal menghapus data.');
},
onFinish: () => {
deleteProcessing.value = false;
},
});
} else {
emit('confirm');
}
}
</script>
<template>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="text-destructive hover:text-destructive size-8"
@click="deleteConfirmOpen = true">
<Trash2 class="size-4" />
<span class="sr-only">Hapus</span>
</Button>
</TooltipTrigger>
<TooltipContent>Hapus</TooltipContent>
</Tooltip>
<ConfirmDialog v-model:open="deleteConfirmOpen" :title="title || 'Hapus data?'"
:description="description || 'Data akan dihapus secara permanen. Tindakan ini tidak dapat dibatalkan.'"
confirm-label="Hapus" cancel-label="Batal" destructive :loading="deleteProcessing" @confirm="handleConfirm" />
</template>

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3';
import { Eye } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
defineProps<{
href?: string;
tooltip?: string;
}>();
</script>
<template>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" :as-child="!!href">
<Link v-if="href" :href="href">
<Eye class="size-4" />
<span class="sr-only">{{ tooltip || 'Detail' }}</span>
</Link>
<template v-else>
<Eye class="size-4" />
<span class="sr-only">{{ tooltip || 'Detail' }}</span>
</template>
</Button>
</TooltipTrigger>
<TooltipContent>{{ tooltip || 'Detail' }}</TooltipContent>
</Tooltip>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import { Link } from '@inertiajs/vue3';
import { Pencil } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
defineProps<{
href?: string;
}>();
</script>
<template>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" :as-child="!!href">
<Link v-if="href" :href="href">
<Pencil class="size-4" />
<span class="sr-only">Ubah</span>
</Link>
<template v-else>
<Pencil class="size-4" />
<span class="sr-only">Ubah</span>
</template>
</Button>
</TooltipTrigger>
<TooltipContent>Ubah</TooltipContent>
</Tooltip>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { X } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
defineProps<{
tooltip?: string;
disabled?: boolean;
}>();
</script>
<template>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8 text-destructive hover:text-destructive" :disabled="disabled">
<X class="size-4" />
<span class="sr-only">{{ tooltip || 'Tolak' }}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{{ tooltip || 'Tolak' }}</TooltipContent>
</Tooltip>
</template>

View File

@ -0,0 +1,7 @@
export { default as RowEditAction } from './RowEditAction.vue';
export { default as RowDeleteAction } from './RowDeleteAction.vue';
export { default as RowApproveAction } from './RowApproveAction.vue';
export { default as RowRejectAction } from './RowRejectAction.vue';
export { default as RowDetailAction } from './RowDetailAction.vue';
export { default as CreateButton } from './CreateButton.vue';
export { default as BackButton } from './BackButton.vue';

View File

@ -1,19 +1,25 @@
<script setup lang="ts">
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import { EmployeeAdvanceStatus } from '@/constants/employee-advance-status';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableFilterDef, DataTableSort } from '@/types/data-table';
import type {
EmployeeAdvanceListItem,
EmployeeAdvancePageProps,
} from '@/types/employee-advance';
import { Head } from '@inertiajs/vue3';
import { HandCoins, Plus } from '@lucide/vue';
import { HandCoins } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import EmployeeAdvanceFormModal from './form/EmployeeAdvanceFormModal.vue';
import RejectEmployeeAdvanceModal from './form/RejectEmployeeAdvanceModal.vue';
import { createColumns } from './table/columns';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { EmployeeAdvanceStatus } from '@/constants/employee-advance-status';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableFilterDef, DataTableSort } from '@/types/data-table';
import type { EmployeeAdvanceListItem, EmployeeAdvancePageProps } from '@/types/employee-advance';
const props = defineProps<EmployeeAdvancePageProps>();
@ -24,19 +30,18 @@ const rejectModalOpen = ref(false);
const editingEmployeeAdvance = ref<EmployeeAdvanceListItem | null>(null);
const rejectingEmployeeAdvance = ref<EmployeeAdvanceListItem | null>(null);
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/finance/employee-advances',
initial: { ...props.filters },
filterKeys: ['status'],
});
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/finance/employee-advances',
initial: { ...props.filters },
filterKeys: ['status'],
});
useDataTableQuerySync(() => props.filters, syncFromServer);
const columns = computed(() => createColumns(
props.authEmployeeId,
openEditModal,
openRejectModal,
));
const columns = computed(() =>
createColumns(props.authEmployeeId, openEditModal, openRejectModal),
);
const currentSort = computed<DataTableSort | null>(() => {
if (!query.value.sort || !query.value.direction) {
@ -102,25 +107,27 @@ watch(
</script>
<template>
<Head title="Kasbon" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Kasbon
</h2>
<h2 class="text-2xl font-bold tracking-tight">Kasbon</h2>
</div>
<Button v-if="canSubmit" class="shrink-0 self-start sm:self-center" @click="openCreateModal">
<Plus class="size-4" />
Ajukan
</Button>
<CreateButton
v-if="canSubmit"
@click="openCreateModal"
label="Ajukan"
/>
</div>
<Card v-if="can('employee_advances.verify')">
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<CardHeader
class="flex flex-row items-center justify-between space-y-0 pb-2"
>
<CardTitle class="text-sm font-medium text-muted-foreground">
Total Kasbon
</CardTitle>
@ -138,17 +145,32 @@ watch(
<Card class="min-w-0">
<CardContent class="min-w-0 pt-6">
<DataTable v-model:search="search" :columns="columns" :data="employeeAdvances.data"
:pagination="pagination" :pagination-links="employeeAdvances.links" :sort="currentSort"
:filter-defs="filterDefs" :filter-values="filterValues" @sort-change="setSort"
@filter-change="setFilter" @filters-reset="resetFilters" />
<DataTable
v-model:search="search"
:columns="columns"
:data="employeeAdvances.data"
:pagination="pagination"
:pagination-links="employeeAdvances.links"
:sort="currentSort"
:filter-defs="filterDefs"
:filter-values="filterValues"
@sort-change="setSort"
@filter-change="setFilter"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
<EmployeeAdvanceFormModal v-if="canSubmit" v-model:open="formModalOpen"
:employee-advance="editingEmployeeAdvance" />
<EmployeeAdvanceFormModal
v-if="canSubmit"
v-model:open="formModalOpen"
:employee-advance="editingEmployeeAdvance"
/>
<RejectEmployeeAdvanceModal v-if="can('employee_advances.verify')" v-model:open="rejectModalOpen"
:employee-advance="rejectingEmployeeAdvance" />
<RejectEmployeeAdvanceModal
v-if="can('employee_advances.verify')"
v-model:open="rejectModalOpen"
:employee-advance="rejectingEmployeeAdvance"
/>
</AdminLayout>
</template>

View File

@ -1,17 +1,19 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import ExpenseFormModal from './form/ExpenseFormModal.vue';
import { createColumns } from './table/columns';
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableSort } from '@/types/data-table';
import type { ExpenseListItem, PaginatedExpenses } from '@/types/expense';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import ExpenseFormModal from './form/ExpenseFormModal.vue';
import { createColumns } from './table/columns';
const props = defineProps<{
expenses: PaginatedExpenses;
@ -27,10 +29,11 @@ const search = ref(props.filters.search ?? '');
const formModalOpen = ref(false);
const editingExpense = ref<ExpenseListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/finance/expenses',
initial: { ...props.filters },
});
const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/finance/expenses',
initial: { ...props.filters },
});
useDataTableQuerySync(() => props.filters, syncFromServer);
@ -77,32 +80,41 @@ watch(
</script>
<template>
<Head title="Pengeluaran" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Pengeluaran
</h2>
<h2 class="text-2xl font-bold tracking-tight">Pengeluaran</h2>
</div>
<Button v-if="can('expenses.create')" class="shrink-0 self-start sm:self-center" @click="openCreateModal">
<Plus class="size-4" />
Tambah
</Button>
<CreateButton
v-if="can('expenses.create')"
@click="openCreateModal"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<DataTable v-model:search="search" :columns="columns" :data="expenses.data" :pagination="pagination"
:pagination-links="expenses.links" :sort="currentSort" @sort-change="setSort"
@filters-reset="resetFilters" />
<DataTable
v-model:search="search"
:columns="columns"
:data="expenses.data"
:pagination="pagination"
:pagination-links="expenses.links"
:sort="currentSort"
@sort-change="setSort"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
<ExpenseFormModal v-if="can('expenses.create') || can('expenses.update')" v-model:open="formModalOpen"
:expense="editingExpense" />
<ExpenseFormModal
v-if="can('expenses.create') || can('expenses.update')"
v-model:open="formModalOpen"
:expense="editingExpense"
/>
</AdminLayout>
</template>

View File

@ -1,10 +1,9 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import EmployeeForm from './form/EmployeeForm.vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EnumOption } from '@/types/employee';
import { Head } from '@inertiajs/vue3';
import EmployeeForm from './form/EmployeeForm.vue';
defineProps<{
genders: EnumOption[];
@ -17,19 +16,23 @@ defineProps<{
<Head title="Tambah Pegawai" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">Tambah Pegawai</h2>
<h2 class="text-2xl font-bold tracking-tight">
Tambah Pegawai
</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/hr/employees">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/hr/employees" />
</div>
<EmployeeForm submit-url="/admin/hr/employees" :genders="genders" :employment-statuses="employmentStatuses" :roles="roles" />
<EmployeeForm
submit-url="/admin/hr/employees"
:genders="genders"
:employment-statuses="employmentStatuses"
:roles="roles"
/>
</AdminLayout>
</template>

View File

@ -1,11 +1,10 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { computed } from 'vue';
import EmployeeForm from './form/EmployeeForm.vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EmployeeListItem, EnumOption } from '@/types/employee';
import { Head } from '@inertiajs/vue3';
import { computed } from 'vue';
import EmployeeForm from './form/EmployeeForm.vue';
const props = defineProps<{
employee: EmployeeListItem;
@ -24,33 +23,38 @@ const initialData = computed(() => ({
address: props.employee.profile?.address ?? '',
join_date: props.employee.employee?.join_date_input ?? '',
employment_status: props.employee.employee?.employment_status ?? '',
base_salary: props.employee.employee?.base_salary != null ? String(props.employee.employee.base_salary) : '',
base_salary:
props.employee.employee?.base_salary != null
? String(props.employee.employee.base_salary)
: '',
role: props.employee.roles?.[0]?.name ?? '',
}));
</script>
<template>
<Head title="Ubah Pegawai" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Ubah Pegawai
</h2>
<h2 class="text-2xl font-bold tracking-tight">Ubah Pegawai</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/hr/employees">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/hr/employees" />
</div>
<EmployeeForm :submit-url="`/admin/hr/employees/${employee.id}`" method="put" submit-label="Perbarui"
:initial-data="initialData" :employee-id="employee.id" :employee-name="employee.profile?.full_name"
:genders="genders" :employment-statuses="employmentStatuses" :roles="roles" />
<EmployeeForm
:submit-url="`/admin/hr/employees/${employee.id}`"
method="put"
submit-label="Perbarui"
:initial-data="initialData"
:employee-id="employee.id"
:employee-name="employee.profile?.full_name"
:genders="genders"
:employment-statuses="employmentStatuses"
:roles="roles"
/>
</AdminLayout>
</template>

View File

@ -1,20 +1,19 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { columns } from './table/columns';
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { ActiveStatus } from '@/constants/active-status';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import { ActiveStatus } from '@/constants/active-status';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableFilterDef, DataTableSort } from '@/types/data-table';
import type { EnumOption, PaginatedEmployees } from '@/types/employee';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import { columns } from './table/columns';
const props = defineProps<{
employees: PaginatedEmployees;
@ -35,11 +34,12 @@ const props = defineProps<{
const { can } = useCan();
const search = ref(props.filters.search ?? '');
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/hr/employees',
initial: { ...props.filters },
filterKeys: ['role', 'gender', 'employment_status', 'is_active'],
});
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/hr/employees',
initial: { ...props.filters },
filterKeys: ['role', 'gender', 'employment_status', 'is_active'],
});
useDataTableQuerySync(() => props.filters, syncFromServer);
@ -114,17 +114,17 @@ watch(
<Head title="Pegawai" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">Pegawai</h2>
</div>
<Button v-if="can('employees.create')" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/hr/employees/create">
<Plus class="size-4" />
Tambah
</Link>
</Button>
<CreateButton
v-if="can('employees.create')"
href="/admin/hr/employees/create"
/>
</div>
<Card class="min-w-0">

View File

@ -1,12 +1,6 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import LeaveRequestFormModal from './form/LeaveRequestFormModal.vue';
import RejectLeaveRequestModal from './form/RejectLeaveRequestModal.vue';
import { createColumns } from './table/columns';
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import {
@ -15,7 +9,15 @@ import {
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableSort } from '@/types/data-table';
import type { LeaveRequestListItem, LeaveRequestPageProps } from '@/types/leave-request';
import type {
LeaveRequestListItem,
LeaveRequestPageProps,
} from '@/types/leave-request';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import LeaveRequestFormModal from './form/LeaveRequestFormModal.vue';
import RejectLeaveRequestModal from './form/RejectLeaveRequestModal.vue';
import { createColumns } from './table/columns';
const props = defineProps<LeaveRequestPageProps>();
@ -26,18 +28,17 @@ const rejectModalOpen = ref(false);
const editingLeaveRequest = ref<LeaveRequestListItem | null>(null);
const rejectingLeaveRequest = ref<LeaveRequestListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/hr/leave-requests',
initial: { ...props.filters },
});
const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/hr/leave-requests',
initial: { ...props.filters },
});
useDataTableQuerySync(() => props.filters, syncFromServer);
const columns = computed(() => createColumns(
props.authEmployeeId,
openEditModal,
openRejectModal,
));
const columns = computed(() =>
createColumns(props.authEmployeeId, openEditModal, openRejectModal),
);
const currentSort = computed<DataTableSort | null>(() => {
if (!query.value.sort || !query.value.direction) {
@ -85,35 +86,48 @@ watch(
</script>
<template>
<Head title="Cuti" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Cuti
</h2>
<h2 class="text-2xl font-bold tracking-tight">Cuti</h2>
</div>
<Button v-if="canSubmit && !hasPending" class="shrink-0 self-start sm:self-center" @click="openCreateModal">
<Plus class="size-4" />
Ajukan
</Button>
<CreateButton
v-if="canSubmit && !hasPending"
@click="openCreateModal"
label="Ajukan"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0 pt-6">
<DataTable v-model:search="search" :columns="columns" :data="leaveRequests.data"
:pagination="pagination" :pagination-links="leaveRequests.links" :sort="currentSort"
@sort-change="setSort" @filters-reset="resetFilters" />
<DataTable
v-model:search="search"
:columns="columns"
:data="leaveRequests.data"
:pagination="pagination"
:pagination-links="leaveRequests.links"
:sort="currentSort"
@sort-change="setSort"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
<LeaveRequestFormModal v-if="canSubmit" v-model:open="formModalOpen"
:leave-request="editingLeaveRequest" />
<LeaveRequestFormModal
v-if="canSubmit"
v-model:open="formModalOpen"
:leave-request="editingLeaveRequest"
/>
<RejectLeaveRequestModal v-if="can('leave_requests.verify')" v-model:open="rejectModalOpen"
:leave-request="rejectingLeaveRequest" />
<RejectLeaveRequestModal
v-if="can('leave_requests.verify')"
v-model:open="rejectModalOpen"
:leave-request="rejectingLeaveRequest"
/>
</AdminLayout>
</template>

View File

@ -1,10 +1,14 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import CuttingPosForm from './form/CuttingPosForm.vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { CuttingMaterialCartItem, CuttingProductCatalogItem, CuttingRawMaterialCatalogItem, CuttingResultCartItem } from '@/types/cutting';
import type {
CuttingMaterialCartItem,
CuttingProductCatalogItem,
CuttingRawMaterialCatalogItem,
CuttingResultCartItem,
} from '@/types/cutting';
import { Head } from '@inertiajs/vue3';
import CuttingPosForm from './form/CuttingPosForm.vue';
defineProps<{
rawMaterialCatalog: CuttingRawMaterialCatalogItem[];
@ -18,19 +22,16 @@ defineProps<{
<Head title="Tambah Cutting" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Tambah Cutting
</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/cuttings">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/manage/cuttings" />
</div>
<CuttingPosForm

View File

@ -1,15 +1,14 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { computed } from 'vue';
import CuttingPosForm from './form/CuttingPosForm.vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type {
CuttingEditItem,
CuttingProductCatalogItem,
CuttingRawMaterialCatalogItem,
} from '@/types/cutting';
import { Head } from '@inertiajs/vue3';
import { computed } from 'vue';
import CuttingPosForm from './form/CuttingPosForm.vue';
const props = defineProps<{
cutting: CuttingEditItem;
@ -26,7 +25,8 @@ const initialData = computed(() => ({
raw_material_name: item.raw_material_price?.raw_material?.name ?? '',
variant: item.raw_material_price?.variant ?? '',
unit: item.raw_material_price?.raw_material?.unit ?? 'kilogram',
unit_abbreviation: item.raw_material_price?.raw_material?.unit_abbreviation ?? '',
unit_abbreviation:
item.raw_material_price?.raw_material?.unit_abbreviation ?? '',
stock_input: item.raw_material_price?.stock_input ?? '',
material_usage: item.material_usage_input,
remaining_material: item.remaining_material_input,
@ -49,19 +49,14 @@ const initialData = computed(() => ({
<Head title="Ubah Cutting" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Ubah Cutting
</h2>
<h2 class="text-2xl font-bold tracking-tight">Ubah Cutting</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/cuttings">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/manage/cuttings" />
</div>
<CuttingPosForm

View File

@ -1,15 +1,17 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import CreateButton from '@/components/button/CreateButton.vue';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { CuttingListItem, PaginatedCuttings } from '@/types/cutting';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import CuttingGroupedTable from './table/CuttingGroupedTable.vue';
import CuttingInProgressSection from './table/CuttingInProgressSection.vue';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { PaginatedCuttings, CuttingListItem } from '@/types/cutting';
const props = defineProps<{
cuttings: PaginatedCuttings;
@ -38,11 +40,11 @@ const tablePagination = computed(() => ({
total: props.cuttings.total,
}));
const firstItem = computed(() => (
const firstItem = computed(() =>
props.cuttings.data.length > 0
? (props.cuttings.current_page - 1) * props.cuttings.per_page + 1
: 0
));
: 0,
);
watch(search, (value) => {
setSearch(value);
@ -60,19 +62,17 @@ watch(
<Head title="Cutting" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Cutting
</h2>
<h2 class="text-2xl font-bold tracking-tight">Cutting</h2>
</div>
<Button v-if="can('cuttings.create')" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/cuttings/create">
<Plus class="size-4" />
Tambah
</Link>
</Button>
<CreateButton
v-if="can('cuttings.create')"
href="/admin/manage/cuttings/create"
/>
</div>
<CuttingInProgressSection

View File

@ -1,11 +1,15 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type {
EnumOption,
OrderCartItem,
OrderCatalogItem,
SelectOption,
} from '@/types/order';
import { Head } from '@inertiajs/vue3';
import { ref } from 'vue';
import OrderPosForm from './form/OrderPosForm.vue';
import { Button } from '@/components/ui/button';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EnumOption, OrderCartItem, OrderCatalogItem, SelectOption } from '@/types/order';
const props = defineProps<{
customers: SelectOption[];
@ -28,19 +32,16 @@ function onCustomerCreated(customer: { id: number; name: string }) {
<Head title="Tambah Pesanan" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Tambah Pesanan
</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/orders">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/manage/orders" />
</div>
<OrderPosForm

View File

@ -1,11 +1,15 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type {
EnumOption,
OrderCatalogItem,
OrderEditItem,
SelectOption,
} from '@/types/order';
import { Head } from '@inertiajs/vue3';
import { computed, ref } from 'vue';
import OrderPosForm from './form/OrderPosForm.vue';
import { Button } from '@/components/ui/button';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EnumOption, OrderCatalogItem, OrderEditItem, SelectOption } from '@/types/order';
const props = defineProps<{
order: OrderEditItem;
@ -21,7 +25,9 @@ const customers = ref([...props.customers]);
const initialData = computed(() => ({
customer_id: props.order.customer_id ? String(props.order.customer_id) : '',
marketing_id: props.order.marketing_id ? String(props.order.marketing_id) : '',
marketing_id: props.order.marketing_id
? String(props.order.marketing_id)
: '',
channel: props.order.channel,
price_type: props.order.price_type,
payment_type: props.order.payment_type,
@ -36,7 +42,8 @@ const initialData = computed(() => ({
product_name: item.product_name,
variant_name: item.variant_name,
stock_quality: item.stock_quality ?? 'good',
stock_quality_label: item.stock_quality === 'reject' ? 'Reject' : 'Bagus',
stock_quality_label:
item.stock_quality === 'reject' ? 'Reject' : 'Bagus',
quantity: item.quantity_input,
unit_price: item.unit_price,
images: item.product_variant?.images ?? [],
@ -52,22 +59,17 @@ function onCustomerCreated(customer: { id: number; name: string }) {
<Head title="Ubah Pesanan" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Ubah Pesanan
</h2>
<p class="text-muted-foreground text-sm">
<h2 class="text-2xl font-bold tracking-tight">Ubah Pesanan</h2>
<p class="text-sm text-muted-foreground">
{{ order.order_number }}
</p>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/orders">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/manage/orders" />
</div>
<OrderPosForm

View File

@ -1,21 +1,23 @@
<script setup lang="ts">
import { Head, Link, usePage } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, onMounted, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
import OrderGroupedTable from './table/OrderGroupedTable.vue';
import ThermalPrinterConnectButton from './form/ThermalPrinterConnectButton.vue';
import { Button } from '@/components/ui/button';
import CreateButton from '@/components/button/CreateButton.vue';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import { useThermalPrinter, getPaperSizeLabel } from '@/composables/useThermalPrinter';
import {
getPaperSizeLabel,
useThermalPrinter,
} from '@/composables/useThermalPrinter';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { PaperSize } from '@/lib/thermal-printer/types';
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 ThermalPrinterConnectButton from './form/ThermalPrinterConnectButton.vue';
import OrderGroupedTable from './table/OrderGroupedTable.vue';
const props = defineProps<{
orders: PaginatedOrders;
@ -92,9 +94,14 @@ onMounted(async () => {
storeAddress: (page.props.address as string) ?? '',
});
toast.success(`Struk ${order.order_number} dikirim ke printer ${getPaperSizeLabel(paperSize)}.`);
toast.success(
`Struk ${order.order_number} dikirim ke printer ${getPaperSizeLabel(paperSize)}.`,
);
} catch (error) {
const message = error instanceof Error ? error.message : 'Gagal mencetak struk pesanan.';
const message =
error instanceof Error
? error.message
: 'Gagal mencetak struk pesanan.';
toast.error(message);
}
@ -102,31 +109,36 @@ onMounted(async () => {
</script>
<template>
<Head title="Pesanan" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">Pesanan</h2>
</div>
<div class="flex shrink-0 items-center gap-2 self-start sm:self-center" v-if="can('orders.create')">
<div
class="flex shrink-0 items-center gap-2 self-start sm:self-center"
v-if="can('orders.create')"
>
<ThermalPrinterConnectButton />
<Button as-child>
<Link href="/admin/manage/orders/create">
<Plus class="size-4" />
Tambah
</Link>
</Button>
<CreateButton href="/admin/manage/orders/create" />
</div>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<OrderGroupedTable v-model:search="search" :orders="orders.data" :first-item="firstItem"
:pagination="tablePagination" :pagination-links="orders.links" @filters-reset="resetFilters" />
<OrderGroupedTable
v-model:search="search"
:orders="orders.data"
:first-item="firstItem"
:pagination="tablePagination"
:pagination-links="orders.links"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
</AdminLayout>

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { Head, Link, router } from '@inertiajs/vue3';
import {
ArrowLeft,
Check,
CreditCard,
MapPin,
@ -20,7 +19,6 @@ import { toast } from 'vue-sonner';
import OrderPrintButton from './form/OrderPrintButton.vue';
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import {
@ -258,12 +256,7 @@ const feeEntries = computed(() => {
Hapus
</Button>
<Button variant="outline" size="sm" as-child>
<Link href="/admin/manage/orders">
<ArrowLeft class="size-3.5" />
Kembali
</Link>
</Button>
<BackButton href="/admin/manage/orders" />
</div>
</div>

View File

@ -1,10 +1,13 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import PurchasePosForm from './form/PurchasePosForm.vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { PurchaseCartItem, PurchaseCatalogItem, SelectOption } from '@/types/purchase';
import type {
PurchaseCartItem,
PurchaseCatalogItem,
SelectOption,
} from '@/types/purchase';
import { Head } from '@inertiajs/vue3';
import PurchasePosForm from './form/PurchasePosForm.vue';
defineProps<{
suppliers: SelectOption[];
@ -17,19 +20,16 @@ defineProps<{
<Head title="Tambah Belanja" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Tambah Belanja
</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/purchases">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/manage/purchases" />
</div>
<PurchasePosForm

View File

@ -1,11 +1,14 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type {
PurchaseCatalogItem,
PurchaseEditItem,
SelectOption,
} from '@/types/purchase';
import { Head } from '@inertiajs/vue3';
import { computed } from 'vue';
import PurchasePosForm from './form/PurchasePosForm.vue';
import { Button } from '@/components/ui/button';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { PurchaseCatalogItem, PurchaseEditItem, SelectOption } from '@/types/purchase';
const props = defineProps<{
purchase: PurchaseEditItem;
@ -35,19 +38,14 @@ const initialData = computed(() => ({
<Head title="Ubah Belanja" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Ubah Belanja
</h2>
<h2 class="text-2xl font-bold tracking-tight">Ubah Belanja</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/purchases">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/manage/purchases" />
</div>
<PurchasePosForm

View File

@ -1,14 +1,16 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import PurchaseGroupedTable from './table/PurchaseGroupedTable.vue';
import { Button } from '@/components/ui/button';
import CreateButton from '@/components/button/CreateButton.vue';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { PaginatedPurchases } from '@/types/purchase';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import PurchaseGroupedTable from './table/PurchaseGroupedTable.vue';
const props = defineProps<{
purchases: PaginatedPurchases;
@ -36,11 +38,11 @@ const tablePagination = computed(() => ({
total: props.purchases.total,
}));
const firstItem = computed(() => (
const firstItem = computed(() =>
props.purchases.data.length > 0
? (props.purchases.current_page - 1) * props.purchases.per_page + 1
: 0
));
: 0,
);
watch(search, (value) => {
setSearch(value);
@ -58,19 +60,17 @@ watch(
<Head title="Belanja" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Belanja
</h2>
<h2 class="text-2xl font-bold tracking-tight">Belanja</h2>
</div>
<Button v-if="can('purchases.create')" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/manage/purchases/create">
<Plus class="size-4" />
Tambah
</Link>
</Button>
<CreateButton
v-if="can('purchases.create')"
href="/admin/manage/purchases/create"
/>
</div>
<Card class="min-w-0">

View File

@ -1,15 +1,17 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { CategoryListItem, PaginatedCategories } from '@/types/category';
import type { DataTableSort } from '@/types/data-table';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import CategoryFormModal from './form/CategoryFormModal.vue';
import { createColumns } from './table/columns';
@ -27,10 +29,11 @@ const search = ref(props.filters.search ?? '');
const formModalOpen = ref(false);
const editingCategory = ref<CategoryListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/master/categories',
initial: { ...props.filters },
});
const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/master/categories',
initial: { ...props.filters },
});
useDataTableQuerySync(() => props.filters, syncFromServer);
@ -77,32 +80,41 @@ watch(
</script>
<template>
<Head title="Kategori" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Kategori
</h2>
<h2 class="text-2xl font-bold tracking-tight">Kategori</h2>
</div>
<Button v-if="can('categories.create')" class="shrink-0 self-start sm:self-center" @click="openCreateModal">
<Plus class="size-4" />
Tambah
</Button>
<CreateButton
v-if="can('categories.create')"
@click="openCreateModal"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<DataTable v-model:search="search" :columns="columns" :data="categories.data" :pagination="pagination"
:pagination-links="categories.links" :sort="currentSort" @sort-change="setSort"
@filters-reset="resetFilters" />
<DataTable
v-model:search="search"
:columns="columns"
:data="categories.data"
:pagination="pagination"
:pagination-links="categories.links"
:sort="currentSort"
@sort-change="setSort"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
<CategoryFormModal v-if="can('categories.create') || can('categories.update')" v-model:open="formModalOpen"
:category="editingCategory" />
<CategoryFormModal
v-if="can('categories.create') || can('categories.update')"
v-model:open="formModalOpen"
:category="editingCategory"
/>
</AdminLayout>
</template>

View File

@ -1,15 +1,17 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { CustomerListItem, PaginatedCustomers } from '@/types/customer';
import type { DataTableSort } from '@/types/data-table';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import CustomerFormModal from './form/CustomerFormModal.vue';
import { createColumns } from './table/columns';
@ -27,10 +29,11 @@ const search = ref(props.filters.search ?? '');
const formModalOpen = ref(false);
const editingCustomer = ref<CustomerListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/master/customers',
initial: { ...props.filters },
});
const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/master/customers',
initial: { ...props.filters },
});
useDataTableQuerySync(() => props.filters, syncFromServer);
@ -77,32 +80,41 @@ watch(
</script>
<template>
<Head title="Customer" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Customer
</h2>
<h2 class="text-2xl font-bold tracking-tight">Customer</h2>
</div>
<Button v-if="can('customers.create')" class="shrink-0 self-start sm:self-center" @click="openCreateModal">
<Plus class="size-4" />
Tambah
</Button>
<CreateButton
v-if="can('customers.create')"
@click="openCreateModal"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<DataTable v-model:search="search" :columns="columns" :data="customers.data" :pagination="pagination"
:pagination-links="customers.links" :sort="currentSort" @sort-change="setSort"
@filters-reset="resetFilters" />
<DataTable
v-model:search="search"
:columns="columns"
:data="customers.data"
:pagination="pagination"
:pagination-links="customers.links"
:sort="currentSort"
@sort-change="setSort"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
<CustomerFormModal v-if="can('customers.create') || can('customers.update')" v-model:open="formModalOpen"
:customer="editingCustomer" />
<CustomerFormModal
v-if="can('customers.create') || can('customers.update')"
v-model:open="formModalOpen"
:customer="editingCustomer"
/>
</AdminLayout>
</template>

View File

@ -1,9 +1,8 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { CategoryOption } from '@/types/product';
import { Head } from '@inertiajs/vue3';
import ProductForm from './form/ProductForm.vue';
defineProps<{
@ -12,25 +11,24 @@ defineProps<{
</script>
<template>
<Head title="Tambah Produk" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Tambah Produk
</h2>
<h2 class="text-2xl font-bold tracking-tight">Tambah Produk</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/master/products">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/master/products" />
</div>
<ProductForm submit-url="/admin/master/products" method="post" submit-label="Simpan" :categories="categories" />
<ProductForm
submit-url="/admin/master/products"
method="post"
submit-label="Simpan"
:categories="categories"
/>
</AdminLayout>
</template>

View File

@ -1,10 +1,9 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { computed } from 'vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { CategoryOption, ProductListItem } from '@/types/product';
import { Head } from '@inertiajs/vue3';
import { computed } from 'vue';
import ProductForm from './form/ProductForm.vue';
const props = defineProps<{
@ -15,7 +14,8 @@ const props = defineProps<{
const initialData = computed(() => ({
name: props.product.name ?? '',
description: props.product.description ?? '',
category_ids: props.product.categories?.map((category) => category.id) ?? [],
category_ids:
props.product.categories?.map((category) => category.id) ?? [],
variants: (props.product.variants ?? []).map((variant) => ({
id: variant.id,
name: variant.name,
@ -26,26 +26,25 @@ const initialData = computed(() => ({
</script>
<template>
<Head title="Ubah Produk" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Ubah Produk
</h2>
<h2 class="text-2xl font-bold tracking-tight">Ubah Produk</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/master/products">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/master/products" />
</div>
<ProductForm :submit-url="`/admin/master/products/${product.id}`" method="put" submit-label="Perbarui"
:initial-data="initialData" :categories="categories" />
<ProductForm
:submit-url="`/admin/master/products/${product.id}`"
method="put"
submit-label="Perbarui"
:initial-data="initialData"
:categories="categories"
/>
</AdminLayout>
</template>

View File

@ -1,22 +1,18 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import CreateButton from '@/components/button/CreateButton.vue';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { ActiveStatus } from '@/constants/active-status';
import { StockStatus } from '@/constants/stock-status';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import { ActiveStatus } from '@/constants/active-status';
import { StockStatus } from '@/constants/stock-status';
import AdminLayout from '@/layouts/AdminLayout.vue';
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 { computed, ref, watch } from 'vue';
import ProductGroupedTable from './table/ProductGroupedTable.vue';
const props = defineProps<{
@ -49,7 +45,10 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
key: 'category_id',
label: 'Kategori',
type: 'select',
options: props.categories.map((c) => ({ value: String(c.value), label: c.label })),
options: props.categories.map((c) => ({
value: String(c.value),
label: c.label,
})),
},
{
key: 'is_active',
@ -103,28 +102,35 @@ watch(
</script>
<template>
<Head title="Produk" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">Produk</h2>
</div>
<Button v-if="can('products.create')" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/master/products/create">
<Plus class="size-4" />
Tambah
</Link>
</Button>
<CreateButton
v-if="can('products.create')"
href="/admin/master/products/create"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<ProductGroupedTable v-model:search="search" :products="products.data" :first-item="firstItem"
:pagination="pagination" :pagination-links="products.links" :filter-defs="filterDefs"
:filter-values="filterValues" @filter-change="setFilter" @filters-reset="resetFilters" />
<ProductGroupedTable
v-model:search="search"
:products="products.data"
:first-item="firstItem"
:pagination="pagination"
:pagination-links="products.links"
:filter-defs="filterDefs"
:filter-values="filterValues"
@filter-change="setFilter"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
</AdminLayout>

View File

@ -1,10 +1,9 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { Button } from '@/components/ui/button';
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';
defineProps<{
units: EnumOption[];
@ -23,12 +22,7 @@ defineProps<{
</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/master/raw-materials">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/master/raw-materials" />
</div>
<RawMaterialForm submit-url="/admin/master/raw-materials" method="post" submit-label="Simpan" :units="units" />

View File

@ -1,10 +1,9 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { computed } from 'vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EnumOption, RawMaterialListItem } from '@/types/raw-material';
import { Head } from '@inertiajs/vue3';
import { computed } from 'vue';
import RawMaterialForm from './form/RawMaterialForm.vue';
const props = defineProps<{
@ -26,26 +25,27 @@ const initialData = computed(() => ({
</script>
<template>
<Head title="Ubah Bahan Baku" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Ubah Bahan Baku
</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/master/raw-materials">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/master/raw-materials" />
</div>
<RawMaterialForm :submit-url="`/admin/master/raw-materials/${rawMaterial.id}`" method="put"
submit-label="Perbarui" :initial-data="initialData" :units="units" />
<RawMaterialForm
:submit-url="`/admin/master/raw-materials/${rawMaterial.id}`"
method="put"
submit-label="Perbarui"
:initial-data="initialData"
:units="units"
/>
</AdminLayout>
</template>

View File

@ -1,19 +1,18 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import CreateButton from '@/components/button/CreateButton.vue';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { ActiveStatus } from '@/constants/active-status';
import { StockStatus } from '@/constants/stock-status';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import { ActiveStatus } from '@/constants/active-status';
import { StockStatus } from '@/constants/stock-status';
import AdminLayout from '@/layouts/AdminLayout.vue';
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 RawMaterialGroupedTable from './table/RawMaterialGroupedTable.vue';
const props = defineProps<{
@ -75,7 +74,7 @@ const pagination = computed(() => ({
const firstItem = computed(() =>
props.rawMaterials.data.length > 0
? (props.rawMaterials.current_page - 1) * props.rawMaterials.per_page +
1
1
: 0,
);
@ -92,28 +91,35 @@ watch(
</script>
<template>
<Head title="Bahan Baku" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">Bahan Baku</h2>
</div>
<Button v-if="can('raw_materials.create')" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/master/raw-materials/create">
<Plus class="size-4" />
Tambah
</Link>
</Button>
<CreateButton
v-if="can('raw_materials.create')"
href="/admin/master/raw-materials/create"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<RawMaterialGroupedTable v-model:search="search" :materials="rawMaterials.data" :first-item="firstItem"
:pagination="pagination" :pagination-links="rawMaterials.links" :filter-defs="filterDefs"
:filter-values="filterValues" @filter-change="setFilter" @filters-reset="resetFilters" />
<RawMaterialGroupedTable
v-model:search="search"
:materials="rawMaterials.data"
:first-item="firstItem"
:pagination="pagination"
:pagination-links="rawMaterials.links"
:filter-defs="filterDefs"
:filter-values="filterValues"
@filter-change="setFilter"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
</AdminLayout>

View File

@ -1,15 +1,17 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import {
useDataTableQuery,
useDataTableQuerySync,
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableSort } from '@/types/data-table';
import type { SupplierListItem, PaginatedSuppliers } from '@/types/supplier';
import type { PaginatedSuppliers, SupplierListItem } from '@/types/supplier';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import SupplierFormModal from './form/SupplierFormModal.vue';
import { createColumns } from './table/columns';
@ -27,10 +29,11 @@ const search = ref(props.filters.search ?? '');
const formModalOpen = ref(false);
const editingSupplier = ref<SupplierListItem | null>(null);
const { query, setSearch, setSort, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/master/suppliers',
initial: { ...props.filters },
});
const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/master/suppliers',
initial: { ...props.filters },
});
useDataTableQuerySync(() => props.filters, syncFromServer);
@ -77,32 +80,41 @@ watch(
</script>
<template>
<Head title="Supplier" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Supplier
</h2>
<h2 class="text-2xl font-bold tracking-tight">Supplier</h2>
</div>
<Button v-if="can('suppliers.create')" class="shrink-0 self-start sm:self-center" @click="openCreateModal">
<Plus class="size-4" />
Tambah
</Button>
<CreateButton
v-if="can('suppliers.create')"
@click="openCreateModal"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<DataTable v-model:search="search" :columns="columns" :data="suppliers.data" :pagination="pagination"
:pagination-links="suppliers.links" :sort="currentSort" @sort-change="setSort"
@filters-reset="resetFilters" />
<DataTable
v-model:search="search"
:columns="columns"
:data="suppliers.data"
:pagination="pagination"
:pagination-links="suppliers.links"
:sort="currentSort"
@sort-change="setSort"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
<SupplierFormModal v-if="can('suppliers.create') || can('suppliers.update')" v-model:open="formModalOpen"
:supplier="editingSupplier" />
<SupplierFormModal
v-if="can('suppliers.create') || can('suppliers.update')"
v-model:open="formModalOpen"
:supplier="editingSupplier"
/>
</AdminLayout>
</template>

View File

@ -1,9 +1,5 @@
<script setup lang="ts">
import { Eye } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import Tooltip from '@/components/ui/tooltip/Tooltip.vue';
import TooltipContent from '@/components/ui/tooltip/TooltipContent.vue';
import TooltipTrigger from '@/components/ui/tooltip/TooltipTrigger.vue';
import { RowDetailAction } from '@/components/button';
import type { ActivityLogListItem } from '@/types/activity-log';
defineProps<{
@ -16,13 +12,5 @@ const emit = defineEmits<{
</script>
<template>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="size-8" @click="emit('view')">
<Eye class="size-4" />
<span class="sr-only">Lihat</span>
</Button>
</TooltipTrigger>
<TooltipContent>Lihat</TooltipContent>
</Tooltip>
<RowDetailAction tooltip="Lihat" @click="emit('view')" />
</template>

View File

@ -1,9 +1,8 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import AdminLayout from '@/layouts/AdminLayout.vue';
import RoleForm from './form/RoleForm.vue';
import BackButton from '@/components/button/BackButton.vue';
interface PermissionOption {
value: string;
@ -28,12 +27,7 @@ defineProps<{
</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/system/roles">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/system/roles" />
</div>
<RoleForm submit-url="/admin/system/roles" method="post" submit-label="Simpan" :permissions="permissions" />

View File

@ -1,8 +1,7 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { ArrowLeft } from '@lucide/vue';
import { Button } from '@/components/ui/button';
import BackButton from '@/components/button/BackButton.vue';
import AdminLayout from '@/layouts/AdminLayout.vue';
import { Head } from '@inertiajs/vue3';
import RoleForm from './form/RoleForm.vue';
interface PermissionOption {
@ -24,26 +23,25 @@ defineProps<{
</script>
<template>
<Head title="Ubah Role" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Ubah Role
</h2>
<h2 class="text-2xl font-bold tracking-tight">Ubah Role</h2>
</div>
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
<Link href="/admin/system/roles">
<ArrowLeft class="size-4" />
Kembali
</Link>
</Button>
<BackButton href="/admin/system/roles" />
</div>
<RoleForm :submit-url="`/admin/system/roles/${role.id}`" method="put" submit-label="Perbarui"
:initial-data="role" :permissions="permissions" />
<RoleForm
:submit-url="`/admin/system/roles/${role.id}`"
method="put"
submit-label="Perbarui"
:initial-data="role"
:permissions="permissions"
/>
</AdminLayout>
</template>

View File

@ -1,9 +1,6 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import CreateButton from '@/components/button/CreateButton.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import {
@ -12,8 +9,10 @@ import {
} from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableSort } from '@/types/data-table';
import { createColumns } from './table/columns';
import { Head } from '@inertiajs/vue3';
import { computed, ref, watch } from 'vue';
import type { RoleListItem } from './table/columns';
import { createColumns } from './table/columns';
interface PaginatedRoles {
current_page: number;
@ -36,10 +35,11 @@ const props = defineProps<{
const { can } = useCan();
const search = ref(props.filters.search ?? '');
const { query, setSearch, setSort, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/system/roles',
initial: { ...props.filters },
});
const { query, setSearch, setSort, resetFilters, syncFromServer } =
useDataTableQuery({
url: '/admin/system/roles',
initial: { ...props.filters },
});
useDataTableQuerySync(() => props.filters, syncFromServer);
@ -76,30 +76,36 @@ watch(
</script>
<template>
<Head title="Role & Permission" />
<AdminLayout>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<div class="space-y-1">
<h2 class="text-2xl font-bold tracking-tight">
Role & Permission
</h2>
</div>
<Button v-if="can('roles.create')" class="shrink-0 self-start sm:self-center" as-child>
<Link href="/admin/system/roles/create">
<Plus class="size-4" />
Tambah
</Link>
</Button>
<CreateButton
v-if="can('roles.create')"
href="/admin/system/roles/create"
/>
</div>
<Card class="min-w-0">
<CardContent class="min-w-0">
<DataTable v-model:search="search" :columns="columns" :data="roles.data" :pagination="pagination"
:pagination-links="roles.links" :sort="currentSort" @sort-change="setSort"
@filters-reset="resetFilters" />
<DataTable
v-model:search="search"
:columns="columns"
:data="roles.data"
:pagination="pagination"
:pagination-links="roles.links"
:sort="currentSort"
@sort-change="setSort"
@filters-reset="resetFilters"
/>
</CardContent>
</Card>
</AdminLayout>