223 lines
5.6 KiB
Vue
223 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue';
|
|
import api from '@/utils/api';
|
|
import AppRightDialog from '~/components/shared/AppRightDialog.vue';
|
|
import { useSnackbarStore } from '~/store/snackbar';
|
|
import type { ModalMode } from '~/types/setting/menu';
|
|
import type { RoleMaster, RoleMasterResponse } from '~/types/setting/roleMaster';
|
|
import { useValidation } from '~/composables/useValidation';
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
modelValue: boolean;
|
|
mode?: ModalMode;
|
|
roleId?: number | null;
|
|
}>(),
|
|
{
|
|
mode: 'create',
|
|
roleId: null,
|
|
},
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: boolean): void;
|
|
(e: 'saved'): void;
|
|
}>();
|
|
|
|
const dialog = ref(props.modelValue);
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(newValue) => {
|
|
dialog.value = newValue;
|
|
},
|
|
);
|
|
|
|
watch(dialog, (newValue) => {
|
|
if (!newValue) emit('update:modelValue', false);
|
|
});
|
|
|
|
const snackbarStore = useSnackbarStore();
|
|
const { required } = useValidation();
|
|
|
|
const formRef = ref<any>(null);
|
|
const valid = ref(false);
|
|
const isSaving = ref(false);
|
|
const isLoadingDetail = ref(false);
|
|
|
|
const name = ref('');
|
|
const slug = ref('');
|
|
const active = ref(true);
|
|
|
|
const isDetailMode = computed(() => props.mode === 'detail');
|
|
const isEditMode = computed(() => props.mode === 'edit');
|
|
const isCreateMode = computed(() => props.mode === 'create');
|
|
const isFormDisabled = computed(() => isDetailMode.value || isSaving.value || isLoadingDetail.value);
|
|
|
|
const titleText = computed(() => {
|
|
if (isCreateMode.value) return 'Tambah Role';
|
|
if (isEditMode.value) return 'Edit Role';
|
|
return 'Detail Role';
|
|
});
|
|
|
|
const submitLabel = computed(() => (isEditMode.value ? 'Update' : 'Save'));
|
|
|
|
const resetForm = () => {
|
|
name.value = '';
|
|
slug.value = '';
|
|
active.value = true;
|
|
valid.value = false;
|
|
formRef.value?.resetValidation?.();
|
|
};
|
|
|
|
let lastLoadedKey = '';
|
|
|
|
const fetchRoleDetail = async () => {
|
|
if (!props.roleId) return;
|
|
|
|
const key = `${props.mode}:${props.roleId}`;
|
|
if (lastLoadedKey === key) return;
|
|
lastLoadedKey = key;
|
|
|
|
isLoadingDetail.value = true;
|
|
try {
|
|
const response = await api.get<RoleMasterResponse>(`/api/v1/roles/master/${props.roleId}`);
|
|
const data: RoleMaster | undefined = response.data?.data;
|
|
if (!data) throw new Error('Data role tidak ditemukan');
|
|
|
|
name.value = data.name ?? '';
|
|
slug.value = data.slug ?? '';
|
|
active.value = typeof data.active === 'boolean' ? data.active : true;
|
|
} catch (error: any) {
|
|
const message = error?.response?.data?.message || error?.message || 'Gagal memuat detail role';
|
|
snackbarStore.showSnackbar(message, 'error');
|
|
} finally {
|
|
isLoadingDetail.value = false;
|
|
}
|
|
};
|
|
|
|
watch(
|
|
[dialog, () => props.mode, () => props.roleId],
|
|
async ([isOpen, mode]) => {
|
|
if (!isOpen) return;
|
|
|
|
if (mode === 'create') {
|
|
lastLoadedKey = '';
|
|
resetForm();
|
|
return;
|
|
}
|
|
|
|
await fetchRoleDetail();
|
|
},
|
|
{ immediate: false },
|
|
);
|
|
|
|
const close = () => {
|
|
emit('update:modelValue', false);
|
|
lastLoadedKey = '';
|
|
resetForm();
|
|
};
|
|
|
|
const save = () => {
|
|
const run = async () => {
|
|
if (isDetailMode.value) return;
|
|
if (isSaving.value) return;
|
|
isSaving.value = true;
|
|
|
|
try {
|
|
await formRef.value?.validate?.();
|
|
if (!valid.value) {
|
|
snackbarStore.showSnackbar('Lengkapi form terlebih dahulu', 'error');
|
|
return;
|
|
}
|
|
|
|
const payload: Record<string, any> = {
|
|
name: name.value,
|
|
slug: slug.value,
|
|
active: active.value,
|
|
};
|
|
|
|
if (isCreateMode.value) {
|
|
await api.post('/api/v1/roles/master', payload);
|
|
snackbarStore.showSnackbar('Role berhasil ditambahkan', 'success');
|
|
} else if (isEditMode.value) {
|
|
if (!props.roleId) throw new Error('ID role tidak ditemukan');
|
|
await api.put(`/api/v1/roles/master/${props.roleId}`, payload);
|
|
snackbarStore.showSnackbar('Role berhasil diperbarui', 'success');
|
|
}
|
|
|
|
emit('saved');
|
|
close();
|
|
} catch (error: any) {
|
|
const message = error?.response?.data?.message || error?.message || 'Gagal menyimpan role';
|
|
snackbarStore.showSnackbar(message, 'error');
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
};
|
|
|
|
run();
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<AppRightDialog v-model="dialog" :max-width="500" persistent>
|
|
<v-card>
|
|
<v-form v-model="valid" ref="formRef" @submit.prevent="save">
|
|
<v-card-title class="d-flex align-center justify-space-between pa-3">
|
|
<span class="ml-3 text-h6">{{ titleText }}</span>
|
|
<v-btn icon variant="text" @click="close">
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
</v-card-title>
|
|
|
|
<v-divider />
|
|
|
|
<v-card-text>
|
|
<div>
|
|
<label class="font-weight-medium required">Nama Role</label>
|
|
<v-text-field
|
|
v-model="name"
|
|
class="mt-2"
|
|
variant="outlined"
|
|
density="compact"
|
|
:readonly="isFormDisabled"
|
|
:rules="[required]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="font-weight-medium required">Slug</label>
|
|
<v-text-field
|
|
v-model="slug"
|
|
class="mt-2"
|
|
variant="outlined"
|
|
density="compact"
|
|
:readonly="isFormDisabled"
|
|
:rules="[required]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="font-weight-medium required">Status</label>
|
|
<v-switch v-model="active" class="mt-2" color="primary" inset :readonly="isFormDisabled" hide-details>
|
|
<template #label>
|
|
<span>{{ active ? 'Aktif' : 'Nonaktif' }}</span>
|
|
</template>
|
|
</v-switch>
|
|
</div>
|
|
</v-card-text>
|
|
|
|
<v-divider />
|
|
|
|
<v-card-actions class="pa-4">
|
|
<v-spacer />
|
|
<v-btn v-if="!isDetailMode" class="w-50" color="primary" type="submit" variant="flat" :loading="isSaving" :disabled="isSaving">
|
|
{{ submitLabel }}
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-form>
|
|
</v-card>
|
|
</AppRightDialog>
|
|
</template>
|