fix(FE) : validate number norm & add action, feat user & hak akses
This commit is contained in:
No files matched your search
@@ -0,0 +1,339 @@
|
||||
<script setup lang="ts">
|
||||
import BaseBreadcrumb from '@/components/shared/BaseBreadcrumb.vue';
|
||||
import sidebarItem from '@/components/layout/full/vertical-sidebar/sidebarItem';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// Detect mode from route query
|
||||
type PageMode = 'create' | 'edit' | 'view';
|
||||
const mode = computed<PageMode>(() => {
|
||||
const modeParam = route.query.mode as string;
|
||||
if (modeParam === 'edit') return 'edit';
|
||||
if (modeParam === 'view') return 'view';
|
||||
return 'create';
|
||||
});
|
||||
|
||||
// Computed readonly state
|
||||
const isReadonly = computed(() => mode.value === 'view');
|
||||
|
||||
// Dynamic page title based on mode
|
||||
const pageTitle = computed(() => {
|
||||
switch (mode.value) {
|
||||
case 'edit':
|
||||
return 'Edit Hak Akses';
|
||||
case 'view':
|
||||
return 'Detail Hak Akses';
|
||||
default:
|
||||
return 'Tambah Hak Akses';
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic breadcrumbs based on mode
|
||||
const breadcrumbText = computed(() => {
|
||||
switch (mode.value) {
|
||||
case 'edit':
|
||||
return 'Edit';
|
||||
case 'view':
|
||||
return 'Detail';
|
||||
default:
|
||||
return 'Tambah';
|
||||
}
|
||||
});
|
||||
|
||||
const page = ref({ title: pageTitle });
|
||||
const breadcrumbs = computed(() => [
|
||||
{
|
||||
text: 'Hak Akses',
|
||||
disabled: false,
|
||||
href: '/setting/hak-akses'
|
||||
},
|
||||
{
|
||||
text: breadcrumbText.value,
|
||||
disabled: true,
|
||||
href: '#'
|
||||
}
|
||||
]);
|
||||
|
||||
// Form data
|
||||
const form = ref();
|
||||
const valid = ref(true);
|
||||
const namaHakAkses = ref('');
|
||||
const status = ref('aktif');
|
||||
const selectedPages = ref<string[]>([]);
|
||||
|
||||
// Rules
|
||||
const rules = {
|
||||
required: (value: string) => !!value || 'Field ini wajib diisi'
|
||||
};
|
||||
|
||||
// Get all menu items with their pages
|
||||
const menuItems = computed(() => {
|
||||
const items: { header: string; pages: { title: string; to: string }[] }[] = [];
|
||||
|
||||
sidebarItem.forEach(section => {
|
||||
if (section.children && section.children.length > 0) {
|
||||
items.push({
|
||||
header: section.header || '',
|
||||
pages: section.children.map(child => ({
|
||||
title: child.title || '',
|
||||
to: child.to || ''
|
||||
}))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
// Toggle all pages in a section
|
||||
const toggleSection = (sectionPages: { to: string }[]) => {
|
||||
const allSelected = sectionPages.every(page => selectedPages.value.includes(page.to));
|
||||
|
||||
if (allSelected) {
|
||||
// Unselect all in this section
|
||||
sectionPages.forEach(page => {
|
||||
const index = selectedPages.value.indexOf(page.to);
|
||||
if (index > -1) {
|
||||
selectedPages.value.splice(index, 1);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Select all in this section
|
||||
sectionPages.forEach(page => {
|
||||
if (!selectedPages.value.includes(page.to)) {
|
||||
selectedPages.value.push(page.to);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Check if all pages in section are selected
|
||||
const isSectionSelected = (sectionPages: { to: string }[]) => {
|
||||
return sectionPages.length > 0 && sectionPages.every(page => selectedPages.value.includes(page.to));
|
||||
};
|
||||
|
||||
// Check if some pages in section are selected
|
||||
const isSectionIndeterminate = (sectionPages: { to: string }[]) => {
|
||||
const selectedCount = sectionPages.filter(page => selectedPages.value.includes(page.to)).length;
|
||||
return selectedCount > 0 && selectedCount < sectionPages.length;
|
||||
};
|
||||
|
||||
const handleSimpan = async () => {
|
||||
const { valid } = await form.value.validate();
|
||||
|
||||
if (valid) {
|
||||
if (selectedPages.value.length === 0) {
|
||||
alert('Pilih minimal satu halaman untuk hak akses');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log({
|
||||
namaHakAkses: namaHakAkses.value,
|
||||
status: status.value,
|
||||
pages: selectedPages.value
|
||||
});
|
||||
|
||||
alert('Data berhasil disimpan!');
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (confirm('Apakah Anda yakin ingin mereset form?')) {
|
||||
form.value.reset();
|
||||
namaHakAkses.value = '';
|
||||
status.value = 'aktif';
|
||||
selectedPages.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleKembali = () => {
|
||||
router.push('/setting/hak-akses');
|
||||
};
|
||||
|
||||
// Load data if edit or view mode
|
||||
onMounted(() => {
|
||||
if (mode.value !== 'create') {
|
||||
const id = route.query.id;
|
||||
// TODO: Load data based on id
|
||||
// For now, just mock data
|
||||
if (id === '1') {
|
||||
namaHakAkses.value = 'Super Admin';
|
||||
status.value = 'aktif';
|
||||
selectedPages.value = ['/antrean/all', '/antrean/pendaftaran', '/master/guarantor'];
|
||||
} else if (id === '2') {
|
||||
namaHakAkses.value = 'Admin Operasi';
|
||||
status.value = 'aktif';
|
||||
selectedPages.value = ['/antrean/all', '/antrean/pendaftaran'];
|
||||
} else if (id === '5') {
|
||||
namaHakAkses.value = 'Admin Rekam Medis';
|
||||
status.value = 'tidak aktif';
|
||||
selectedPages.value = ['/antrean/all'];
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-form ref="form" v-model="valid" lazy-validation>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<BaseBreadcrumb :title="pageTitle" :button-back="true" :breadcrumbs="breadcrumbs"></BaseBreadcrumb>
|
||||
</v-col>
|
||||
|
||||
<!-- Form Card -->
|
||||
<v-col cols="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-item>
|
||||
<h5 class="text-h5 font-weight-bold">Informasi Hak Akses</h5>
|
||||
</v-card-item>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<!-- Nama Hak Akses -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Nama Hak Akses <span class="text-error">*</span></v-label>
|
||||
<v-text-field
|
||||
v-model="namaHakAkses"
|
||||
placeholder="Contoh: Admin, Dokter, Perawat"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
:readonly="isReadonly"
|
||||
:bg-color="isReadonly ? 'grey-lighten-3' : undefined"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Status -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Status <span class="text-error">*</span></v-label>
|
||||
<v-switch
|
||||
v-model="status"
|
||||
true-value="aktif"
|
||||
false-value="tidak aktif"
|
||||
color="success"
|
||||
hide-details="auto"
|
||||
:readonly="isReadonly"
|
||||
:disabled="isReadonly"
|
||||
inset
|
||||
>
|
||||
<template #label>
|
||||
<span :class="status === 'aktif' ? 'text-success' : 'text-error'">
|
||||
{{ status === 'aktif' ? 'Aktif' : 'Tidak Aktif' }}
|
||||
</span>
|
||||
</template>
|
||||
</v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Akses Halaman Card -->
|
||||
<v-col cols="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-item>
|
||||
<h5 class="text-h5 font-weight-bold">Akses Halaman</h5>
|
||||
</v-card-item>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="(section, index) in menuItems"
|
||||
:key="index"
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<v-card variant="outlined">
|
||||
<div class="d-flex align-center pa-1">
|
||||
<v-checkbox
|
||||
:model-value="isSectionSelected(section.pages)"
|
||||
:indeterminate="isSectionIndeterminate(section.pages)"
|
||||
@click="!isReadonly && toggleSection(section.pages)"
|
||||
hide-details
|
||||
density="compact"
|
||||
color="primary"
|
||||
:disabled="isReadonly"
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-capitalize font-weight-bold">
|
||||
{{ section.header }}
|
||||
</span>
|
||||
</template>
|
||||
</v-checkbox>
|
||||
</div>
|
||||
|
||||
<v-divider class="mb-1"></v-divider>
|
||||
|
||||
<div class="pl-3">
|
||||
<v-checkbox
|
||||
v-for="page in section.pages"
|
||||
:key="page.to"
|
||||
v-model="selectedPages"
|
||||
:value="page.to"
|
||||
:label="page.title"
|
||||
hide-details
|
||||
density="compact"
|
||||
color="primary"
|
||||
:disabled="isReadonly"
|
||||
></v-checkbox>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Selected Pages Summary -->
|
||||
<v-row v-if="selectedPages.length > 0" class="mt-4">
|
||||
<v-col cols="12">
|
||||
<v-alert type="info" variant="tonal" density="compact">
|
||||
<div class="d-flex align-center">
|
||||
<span><strong>{{ selectedPages.length }}</strong> halaman dipilih</span>
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<v-col cols="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-end ga-3">
|
||||
<v-btn
|
||||
color="default"
|
||||
size="large"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-arrow-left"
|
||||
@click="handleKembali"
|
||||
>
|
||||
Kembali
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!isReadonly"
|
||||
color="error"
|
||||
size="large"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-reload"
|
||||
@click="handleReset"
|
||||
>
|
||||
Reset
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!isReadonly"
|
||||
color="primary"
|
||||
size="large"
|
||||
prepend-icon="mdi-content-save"
|
||||
@click="handleSimpan"
|
||||
>
|
||||
Simpan
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</template>
|
||||
@@ -0,0 +1,160 @@
|
||||
<script setup lang="ts">
|
||||
import BaseBreadcrumb from '@/components/shared/BaseBreadcrumb.vue';
|
||||
import TableAntrian from '@/components/pendaftaran/TableAntrian.vue';
|
||||
import hakAksesData from '~/data/mock/hakAkses.json';
|
||||
|
||||
interface HakAkses {
|
||||
id: number;
|
||||
namaHakAkses: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const page = ref({ title: 'Hak Akses' });
|
||||
const breadcrumbs = ref([
|
||||
{
|
||||
text: 'Setting',
|
||||
disabled: false,
|
||||
href: '#'
|
||||
},
|
||||
{
|
||||
text: 'Hak Akses',
|
||||
disabled: true,
|
||||
href: '#'
|
||||
}
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
const search = ref('');
|
||||
const hakAksesList = ref<HakAkses[]>(hakAksesData);
|
||||
|
||||
// Snackbar state
|
||||
const snackbar = ref(false);
|
||||
const snackbarMessage = ref('');
|
||||
const snackbarColor = ref('success');
|
||||
|
||||
const showSnackbar = (message: string, color: string = 'success') => {
|
||||
snackbarMessage.value = message;
|
||||
snackbarColor.value = color;
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
const headers = [
|
||||
{ title: 'No', key: 'no', width: '100px', align: 'center' as const },
|
||||
{ title: 'Nama Hak Akses', key: 'namaHakAkses', sortable: true },
|
||||
{ title: 'Status', key: 'status', sortable: true, align: 'center' as const },
|
||||
{ title: 'Actions', key: 'actions', sortable: false }
|
||||
];
|
||||
|
||||
const actions = [
|
||||
{ icon: 'mdi-eye', color: 'info', tooltip: 'View', event: 'view' },
|
||||
{ icon: 'mdi-pencil', color: 'primary', tooltip: 'Edit', event: 'edit' },
|
||||
{ icon: 'mdi-delete', color: 'error', tooltip: 'Delete', event: 'delete' }
|
||||
];
|
||||
|
||||
const handleView = (item: unknown) => {
|
||||
const data = item as HakAkses;
|
||||
router.push({
|
||||
path: '/setting/hak-akses/form',
|
||||
query: {
|
||||
mode: 'view',
|
||||
id: data.id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleEdit = (item: unknown) => {
|
||||
const data = item as HakAkses;
|
||||
router.push({
|
||||
path: '/setting/hak-akses/form',
|
||||
query: {
|
||||
mode: 'edit',
|
||||
id: data.id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (item: unknown) => {
|
||||
const data = item as HakAkses;
|
||||
if (confirm(`Apakah Anda yakin ingin menghapus hak akses "${data.namaHakAkses}"?`)) {
|
||||
// Remove from list
|
||||
const index = hakAksesList.value.findIndex(h => h.id === data.id);
|
||||
if (index > -1) {
|
||||
hakAksesList.value.splice(index, 1);
|
||||
}
|
||||
showSnackbar('Hak akses berhasil dihapus.', 'success');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<BaseBreadcrumb :title="page.title" :breadcrumbs="breadcrumbs"></BaseBreadcrumb>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-space-between align-center mb-4">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
placeholder="Search..."
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
style="max-width: 400px;"
|
||||
></v-text-field>
|
||||
|
||||
<v-btn
|
||||
color="primary"
|
||||
prepend-icon="mdi-plus"
|
||||
to="/setting/hak-akses/form"
|
||||
>
|
||||
Tambah Hak Akses
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<TableAntrian
|
||||
:headers="headers"
|
||||
:items="hakAksesList"
|
||||
:search="search"
|
||||
:actions="actions"
|
||||
min-width="900px"
|
||||
@view="handleView"
|
||||
@edit="handleEdit"
|
||||
@delete="handleDelete"
|
||||
>
|
||||
<template #item.no="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
<template #item.status="{ item }">
|
||||
<v-chip
|
||||
:color="item.status === 'aktif' ? 'success' : 'error'"
|
||||
size="small"
|
||||
>
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</TableAntrian>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Snackbar for notifications -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top"
|
||||
>
|
||||
{{ snackbarMessage }}
|
||||
<template #actions>
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
@@ -0,0 +1,283 @@
|
||||
<script setup lang="ts">
|
||||
import BaseBreadcrumb from '@/components/shared/BaseBreadcrumb.vue';
|
||||
import hakAksesData from '~/data/mock/hakAkses.json';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// Detect mode from route query
|
||||
type PageMode = 'create' | 'edit' | 'view';
|
||||
const mode = computed<PageMode>(() => {
|
||||
const modeParam = route.query.mode as string;
|
||||
if (modeParam === 'edit') return 'edit';
|
||||
if (modeParam === 'view') return 'view';
|
||||
return 'create';
|
||||
});
|
||||
|
||||
// Computed readonly state
|
||||
const isReadonly = computed(() => mode.value === 'view');
|
||||
|
||||
// Dynamic page title based on mode
|
||||
const pageTitle = computed(() => {
|
||||
switch (mode.value) {
|
||||
case 'edit':
|
||||
return 'Edit User';
|
||||
case 'view':
|
||||
return 'Detail User';
|
||||
default:
|
||||
return 'Tambah User';
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic breadcrumbs based on mode
|
||||
const breadcrumbText = computed(() => {
|
||||
switch (mode.value) {
|
||||
case 'edit':
|
||||
return 'Edit';
|
||||
case 'view':
|
||||
return 'Detail';
|
||||
default:
|
||||
return 'Tambah';
|
||||
}
|
||||
});
|
||||
|
||||
const page = ref({ title: pageTitle });
|
||||
const breadcrumbs = computed(() => [
|
||||
{
|
||||
text: 'User',
|
||||
disabled: false,
|
||||
href: '/setting/user'
|
||||
},
|
||||
{
|
||||
text: breadcrumbText.value,
|
||||
disabled: true,
|
||||
href: '#'
|
||||
}
|
||||
]);
|
||||
|
||||
// Form data
|
||||
const form = ref();
|
||||
const valid = ref(true);
|
||||
const namaUser = ref('');
|
||||
const username = ref('');
|
||||
const email = ref('');
|
||||
const hakAkses = ref<number | null>(null);
|
||||
const status = ref('aktif');
|
||||
|
||||
// Rules
|
||||
const rules = {
|
||||
required: (value: any) => !!value || 'Field ini wajib diisi',
|
||||
email: (value: string) => {
|
||||
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return pattern.test(value) || 'Format email tidak valid';
|
||||
}
|
||||
};
|
||||
|
||||
// Hak Akses options
|
||||
const hakAksesList = ref(hakAksesData);
|
||||
const hakAksesOptions = computed(() => {
|
||||
return hakAksesList.value.map(h => ({
|
||||
title: h.namaHakAkses,
|
||||
value: h.id
|
||||
}));
|
||||
});
|
||||
|
||||
const handleSimpan = async () => {
|
||||
const { valid } = await form.value.validate();
|
||||
|
||||
if (valid) {
|
||||
console.log({
|
||||
namaUser: namaUser.value,
|
||||
username: username.value,
|
||||
email: email.value,
|
||||
hakAkses: hakAkses.value,
|
||||
status: status.value
|
||||
});
|
||||
|
||||
alert('Data berhasil disimpan!');
|
||||
router.push('/setting/user');
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (confirm('Apakah Anda yakin ingin mereset form?')) {
|
||||
form.value.reset();
|
||||
namaUser.value = '';
|
||||
username.value = '';
|
||||
email.value = '';
|
||||
hakAkses.value = null;
|
||||
status.value = 'aktif';
|
||||
}
|
||||
};
|
||||
|
||||
const handleKembali = () => {
|
||||
router.push('/setting/user');
|
||||
};
|
||||
|
||||
// Load data if edit or view mode
|
||||
onMounted(() => {
|
||||
if (mode.value !== 'create') {
|
||||
const id = route.query.id;
|
||||
// TODO: Load data based on id
|
||||
// For now, just mock data
|
||||
if (id === '1') {
|
||||
namaUser.value = 'Dr. John Doe';
|
||||
username.value = 'john.doe';
|
||||
email.value = '[email protected]';
|
||||
hakAkses.value = 3; // Dokter
|
||||
status.value = 'aktif';
|
||||
} else if (id === '2') {
|
||||
namaUser.value = 'Admin Sistem';
|
||||
username.value = 'admin';
|
||||
email.value = '[email protected]';
|
||||
hakAkses.value = 1; // Super Admin
|
||||
status.value = 'aktif';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-form ref="form" v-model="valid" lazy-validation>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<BaseBreadcrumb :title="pageTitle" :button-back="true" :breadcrumbs="breadcrumbs"></BaseBreadcrumb>
|
||||
</v-col>
|
||||
|
||||
<!-- Form Card -->
|
||||
<v-col cols="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-item>
|
||||
<h5 class="text-h5 font-weight-bold">Informasi User</h5>
|
||||
</v-card-item>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<!-- Nama User -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Nama User <span class="text-error">*</span></v-label>
|
||||
<v-text-field
|
||||
v-model="namaUser"
|
||||
placeholder="Masukan nama lengkap user"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
:readonly="isReadonly"
|
||||
:bg-color="isReadonly ? 'grey-lighten-3' : undefined"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Username -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Username <span class="text-error">*</span></v-label>
|
||||
<v-text-field
|
||||
v-model="username"
|
||||
placeholder="Masukan username"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
:readonly="isReadonly"
|
||||
:bg-color="isReadonly ? 'grey-lighten-3' : undefined"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Email -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Email <span class="text-error">*</span></v-label>
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
placeholder="[email protected]"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
type="email"
|
||||
:rules="[rules.required, rules.email]"
|
||||
hide-details="auto"
|
||||
:readonly="isReadonly"
|
||||
:bg-color="isReadonly ? 'grey-lighten-3' : undefined"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- Hak Akses -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Hak Akses <span class="text-error">*</span></v-label>
|
||||
<v-autocomplete
|
||||
v-model="hakAkses"
|
||||
:items="hakAksesOptions"
|
||||
placeholder="Pilih hak akses"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[rules.required]"
|
||||
hide-details="auto"
|
||||
:readonly="isReadonly"
|
||||
:disabled="isReadonly"
|
||||
:bg-color="isReadonly ? 'grey-lighten-3' : undefined"
|
||||
></v-autocomplete>
|
||||
</v-col>
|
||||
|
||||
<!-- Status -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="mb-2 font-weight-medium">Status <span class="text-error">*</span></v-label>
|
||||
<v-switch
|
||||
v-model="status"
|
||||
true-value="aktif"
|
||||
false-value="tidak aktif"
|
||||
color="success"
|
||||
hide-details="auto"
|
||||
:readonly="isReadonly"
|
||||
:disabled="isReadonly"
|
||||
inset
|
||||
>
|
||||
<template #label>
|
||||
<span :class="status === 'aktif' ? 'text-success' : 'text-error'">
|
||||
{{ status === 'aktif' ? 'Aktif' : 'Tidak Aktif' }}
|
||||
</span>
|
||||
</template>
|
||||
</v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<v-col cols="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-end ga-3">
|
||||
<v-btn
|
||||
color="default"
|
||||
size="large"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-arrow-left"
|
||||
@click="handleKembali"
|
||||
>
|
||||
Kembali
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!isReadonly"
|
||||
color="error"
|
||||
size="large"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-reload"
|
||||
@click="handleReset"
|
||||
>
|
||||
Reset
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!isReadonly"
|
||||
color="primary"
|
||||
size="large"
|
||||
prepend-icon="mdi-content-save"
|
||||
@click="handleSimpan"
|
||||
>
|
||||
Simpan
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</template>
|
||||
@@ -0,0 +1,166 @@
|
||||
<script setup lang="ts">
|
||||
import BaseBreadcrumb from '@/components/shared/BaseBreadcrumb.vue';
|
||||
import TableAntrian from '@/components/pendaftaran/TableAntrian.vue';
|
||||
import usersData from '~/data/mock/users.json';
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
namaUser: string;
|
||||
username: string;
|
||||
email: string;
|
||||
hakAkses: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const page = ref({ title: 'User' });
|
||||
const breadcrumbs = ref([
|
||||
{
|
||||
text: 'Setting',
|
||||
disabled: false,
|
||||
href: '#'
|
||||
},
|
||||
{
|
||||
text: 'User',
|
||||
disabled: true,
|
||||
href: '#'
|
||||
}
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
const search = ref('');
|
||||
const usersList = ref<User[]>(usersData);
|
||||
|
||||
// Snackbar state
|
||||
const snackbar = ref(false);
|
||||
const snackbarMessage = ref('');
|
||||
const snackbarColor = ref('success');
|
||||
|
||||
const showSnackbar = (message: string, color: string = 'success') => {
|
||||
snackbarMessage.value = message;
|
||||
snackbarColor.value = color;
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
const headers = [
|
||||
{ title: 'No', key: 'no', width: '100px', align: 'center' as const },
|
||||
{ title: 'Nama User', key: 'namaUser', sortable: true },
|
||||
{ title: 'Username', key: 'username', sortable: true },
|
||||
{ title: 'Email', key: 'email', sortable: true },
|
||||
{ title: 'Hak Akses', key: 'hakAkses', sortable: true },
|
||||
{ title: 'Status', key: 'status', sortable: true, align: 'center' as const },
|
||||
{ title: 'Actions', key: 'actions', sortable: false, align: 'center' as const }
|
||||
];
|
||||
|
||||
const actions = [
|
||||
{ icon: 'mdi-eye', color: 'info', tooltip: 'View', event: 'view' },
|
||||
{ icon: 'mdi-pencil', color: 'primary', tooltip: 'Edit', event: 'edit' },
|
||||
{ icon: 'mdi-delete', color: 'error', tooltip: 'Delete', event: 'delete' }
|
||||
];
|
||||
|
||||
const handleView = (item: unknown) => {
|
||||
const data = item as User;
|
||||
router.push({
|
||||
path: '/setting/user/form',
|
||||
query: {
|
||||
mode: 'view',
|
||||
id: data.id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleEdit = (item: unknown) => {
|
||||
const data = item as User;
|
||||
router.push({
|
||||
path: '/setting/user/form',
|
||||
query: {
|
||||
mode: 'edit',
|
||||
id: data.id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (item: unknown) => {
|
||||
const data = item as User;
|
||||
if (confirm(`Apakah Anda yakin ingin menghapus user "${data.namaUser}"?`)) {
|
||||
// Remove from list
|
||||
const index = usersList.value.findIndex(u => u.id === data.id);
|
||||
if (index > -1) {
|
||||
usersList.value.splice(index, 1);
|
||||
}
|
||||
showSnackbar('User berhasil dihapus.', 'success');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<BaseBreadcrumb :title="page.title" :breadcrumbs="breadcrumbs"></BaseBreadcrumb>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card elevation="10">
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-space-between align-center mb-4">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
placeholder="Search..."
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
style="max-width: 400px;"
|
||||
></v-text-field>
|
||||
|
||||
<v-btn
|
||||
color="primary"
|
||||
prepend-icon="mdi-plus"
|
||||
to="/setting/user/form"
|
||||
>
|
||||
Tambah User
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<TableAntrian
|
||||
:headers="headers"
|
||||
:items="usersList"
|
||||
:search="search"
|
||||
:actions="actions"
|
||||
min-width="1200px"
|
||||
@view="handleView"
|
||||
@edit="handleEdit"
|
||||
@delete="handleDelete"
|
||||
>
|
||||
<template #item.no="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
<template #item.status="{ item }">
|
||||
<v-chip
|
||||
:color="item.status === 'aktif' ? 'success' : 'error'"
|
||||
size="small"
|
||||
>
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</TableAntrian>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Snackbar for notifications -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top"
|
||||
>
|
||||
{{ snackbarMessage }}
|
||||
<template #actions>
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="snackbar = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
Reference in New Issue
Block a user