refactor: standardize table row actions by implementing reusable RowEditAction and RowDeleteAction components
This commit is contained in:
parent
06e313d168
commit
2f8ece4b0a
28
resources/js/components/button/BackButton.vue
Normal file
28
resources/js/components/button/BackButton.vue
Normal 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>
|
||||
28
resources/js/components/button/CreateButton.vue
Normal file
28
resources/js/components/button/CreateButton.vue
Normal 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>
|
||||
22
resources/js/components/button/RowApproveAction.vue
Normal file
22
resources/js/components/button/RowApproveAction.vue
Normal 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>
|
||||
61
resources/js/components/button/RowDeleteAction.vue
Normal file
61
resources/js/components/button/RowDeleteAction.vue
Normal 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>
|
||||
29
resources/js/components/button/RowDetailAction.vue
Normal file
29
resources/js/components/button/RowDetailAction.vue
Normal 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>
|
||||
28
resources/js/components/button/RowEditAction.vue
Normal file
28
resources/js/components/button/RowEditAction.vue
Normal 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>
|
||||
22
resources/js/components/button/RowRejectAction.vue
Normal file
22
resources/js/components/button/RowRejectAction.vue
Normal 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>
|
||||
7
resources/js/components/button/index.ts
Normal file
7
resources/js/components/button/index.ts
Normal 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';
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user