Files
antrean-operasi/pages/setting/halaman/form.vue
T
2026-02-23 14:29:56 +07:00

356 lines
15 KiB
Vue

<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { useKeycloakRoles } from '@/composables/useKeycloakRoles';
interface Halaman {
id: number;
name: string;
url: string;
level: number;
parent?: number;
icon?: string;
role: string[];
}
const route = useRoute();
const router = useRouter();
// Dynamic page title
const pageTitle = 'Edit Role Halaman';
definePageMeta({
middleware: 'auth',
buttonBack: true,
pageTitle: 'Edit Role Halaman',
breadcrumbs: [
{ text: 'Setting' },
{ text: 'Halaman', href: '/setting/halaman' },
{ text: 'Edit Role' }
]
});
// Form data
const form = ref();
const valid = ref(true);
const halamanData = ref<Halaman | null>(null);
const selectedRoles = ref<string[]>([]);
const loading = ref(false);
const loadingData = ref(false);
// Get roles from Keycloak
const { roles: keycloakRoles, loading: loadingRoles, error: rolesError, fetchRoles, getRoleOptions } = useKeycloakRoles();
// Available roles (dynamic from Keycloak)
const availableRoles = computed(() => getRoleOptions());
// 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;
};
// Get parent name
const parentName = computed(() => {
if (!halamanData.value || halamanData.value.level === 1) return null;
// We need to load all pages to find parent
return 'Header Group';
});
// Load data from API
const loadData = async () => {
const id = route.query.id;
if (!id) {
showSnackbar('ID halaman tidak ditemukan', 'error');
setTimeout(() => {
router.push('/setting/halaman');
}, 2000);
return;
}
loadingData.value = true;
try {
const response = await $fetch(`/api/halaman/${id}`);
if (response && typeof response === 'object' && 'success' in response && response.success && 'data' in response) {
const data = response.data as Halaman;
halamanData.value = data;
selectedRoles.value = [...data.role];
} else {
const message = response && typeof response === 'object' && 'message' in response && typeof response.message === 'string'
? response.message
: 'Data halaman tidak ditemukan';
showSnackbar(message, 'error');
setTimeout(() => {
router.push('/setting/halaman');
}, 2000);
}
} catch (error) {
console.error('Error loading halaman:', error);
showSnackbar('Gagal memuat data', 'error');
setTimeout(() => {
router.push('/setting/halaman');
}, 2000);
} finally {
loadingData.value = false;
}
};
const handleSimpan = async () => {
if (!halamanData.value) return;
loading.value = true;
try {
const payload = {
role: selectedRoles.value
};
const response = await $fetch(`/api/halaman/${halamanData.value.id}`, {
method: 'PUT',
body: payload
});
if (response && typeof response === 'object' && 'success' in response && response.success) {
const message = 'message' in response && typeof response.message === 'string'
? response.message
: 'Role berhasil diperbarui';
showSnackbar(message, 'success');
// Redirect to list after 1 second
setTimeout(() => {
router.push('/setting/halaman');
}, 1000);
} else {
const message = response && typeof response === 'object' && 'message' in response && typeof response.message === 'string'
? response.message
: 'Gagal menyimpan data';
showSnackbar(message, 'error');
}
} catch (error) {
console.error('Error saving role:', error);
showSnackbar('Terjadi kesalahan saat menyimpan data', 'error');
} finally {
loading.value = false;
}
};
const handleKembali = () => {
router.push('/setting/halaman');
};
// Load data on mount
onMounted(async () => {
// Fetch roles from Keycloak first
await fetchRoles();
// Then load halaman data
await loadData();
});
</script>
<template>
<v-form ref="form" v-model="valid" lazy-validation>
<v-row>
<!-- Loading State -->
<v-col v-if="loadingData" cols="12">
<v-card elevation="10">
<v-card-text class="text-center pa-8">
<v-progress-circular indeterminate color="primary" size="64"></v-progress-circular>
<p class="text-body-1 mt-4">Memuat data...</p>
</v-card-text>
</v-card>
</v-col>
<!-- Form Content -->
<template v-else-if="halamanData">
<!-- Info Halaman Card -->
<v-col cols="12">
<v-card elevation="10">
<v-card-item class="pa-4">
<div class="d-flex align-center ga-3">
<v-avatar size="48" class="rounded-lg bg-light-primary">
<Icon icon="solar:document-text-bold-duotone" class="text-primary" height="24" />
</v-avatar>
<div>
<h5 class="text-h5 font-weight-bold">{{ halamanData.name }}</h5>
<p class="text-caption text-medium-emphasis mb-0">{{ halamanData.url }}</p>
</div>
</div>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<div class="d-flex align-center ga-2 mb-2">
<Icon icon="solar:layers-minimalistic-bold-duotone" class="text-primary" height="18" />
<span class="text-body-2 font-weight-medium">Level:</span>
<v-chip size="small" color="primary" variant="tonal">
{{ halamanData.level === 1 ? 'Header Group' : 'Menu Item' }}
</v-chip>
</div>
</v-col>
<v-col cols="12" md="6">
<div class="d-flex align-center ga-2 mb-2">
<Icon icon="solar:shield-user-bold-duotone" class="text-secondary" height="18" />
<span class="text-body-2 font-weight-medium">Akses Saat Ini:</span>
<v-chip
v-if="halamanData.role.length === 0"
size="small"
color="error"
variant="tonal"
>
Belum ada role
</v-chip>
<template v-else>
<v-chip
v-for="role in halamanData.role"
:key="role"
size="small"
color="success"
variant="tonal"
>
{{ role }}
</v-chip>
</template>
</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<!-- Edit Role Card -->
<v-col cols="12">
<v-card elevation="10">
<v-card-item>
<div class="d-flex align-center justify-space-between">
<h5 class="text-h5 font-weight-bold">Atur Akses Role</h5>
<v-chip size="small" color="info" variant="tonal">
{{ selectedRoles.length }} role dipilih
</v-chip>
</div>
</v-card-item>
<v-divider></v-divider>
<v-card-text>
<v-alert
v-if="rolesError"
type="error"
variant="tonal"
density="compact"
class="mb-4"
>
<div class="d-flex align-center">
<span class="text-body-2">
Gagal memuat roles: {{ rolesError }}
</span>
</div>
</v-alert>
<v-row>
<v-col cols="12">
<v-label class="mb-3 font-weight-medium">
Pilih Role yang Dapat Mengakses Halaman Ini:
</v-label>
<v-card variant="outlined" class="pa-4">
<div v-if="loadingRoles" class="d-flex justify-center align-center pa-4">
<v-progress-circular indeterminate color="primary" size="32"></v-progress-circular>
<span class="ml-3 text-body-2">Memuat roles...</span>
</div>
<div v-else-if="availableRoles.length === 0" class="text-center pa-4">
<Icon icon="solar:box-minimalistic-bold-duotone" height="48" class="text-disabled mb-2" />
<p class="text-body-2 text-disabled mb-0">Tidak ada roles tersedia</p>
</div>
<div v-else class="d-flex flex-column ga-3">
<v-checkbox
v-for="role in availableRoles"
:key="role.value"
v-model="selectedRoles"
:value="role.value"
hide-details
density="comfortable"
color="primary"
>
<template #label>
<div class="d-flex align-center ga-2">
<v-avatar size="32" class="rounded-md" :color="role.value === 'superadmin' ? 'error' : 'primary'">
<Icon
:icon="role.value === 'superadmin' ? 'solar:shield-star-bold-duotone' : 'solar:shield-user-bold-duotone'"
class="text-white"
height="18"
/>
</v-avatar>
<div>
<span class="text-body-1 font-weight-medium">{{ role.title }}</span>
</div>
</div>
</template>
</v-checkbox>
</div>
</v-card>
</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
color="primary"
size="large"
prepend-icon="mdi-content-save"
@click="handleSimpan"
:loading="loading"
:disabled="loading"
>
Simpan Perubahan
</v-btn>
</div>
</v-card-text>
</v-card>
</v-col>
</template>
</v-row>
</v-form>
<!-- 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>