diff --git a/components/apps/setting/pages/AddPageModal.vue b/components/apps/setting/pages/AddPageModal.vue index 308291b..3ac77eb 100644 --- a/components/apps/setting/pages/AddPageModal.vue +++ b/components/apps/setting/pages/AddPageModal.vue @@ -2,6 +2,7 @@ import { computed, ref, watch } from 'vue'; import { Icon } from '@iconify/vue'; import api from '@/utils/api'; +import AppRightDialog from '~/components/shared/AppRightDialog.vue'; import { useSnackbarStore } from '~/store/snackbar'; import type {ModalMode,ParentPageOption} from '~/types/setting/menu'; import { useValidation } from '~/composables/useValidation'; @@ -65,7 +66,7 @@ const titleText = computed(() => { }); -const submitLabel = computed(() => (isEditMode.value ? 'Update' : 'Save')); +const submitLabel = computed(() => (isEditMode.value ? 'Update' : 'Simpan')); const resetForm = () => { entryType.value = 'main'; @@ -259,16 +260,29 @@ const close = () => { lastLoadedKey = ''; resetForm(); }; + +const resetCurrent = async () => { + if (isDetailMode.value) return; + if (isSaving.value || isLoadingDetail.value) return; + + if (isCreateMode.value) { + resetForm(); + return; + } + + // For edit mode, restore to the last saved/loaded state + lastLoadedKey = ''; + await fetchPageDetail(); +}; diff --git a/components/apps/setting/pages/PageRow.vue b/components/apps/setting/pages/PageRow.vue index cc7bc8d..1fff959 100644 --- a/components/apps/setting/pages/PageRow.vue +++ b/components/apps/setting/pages/PageRow.vue @@ -24,12 +24,20 @@ const handleDelete = (page: RolePage) => emit('delete', page);
- - - + + +
-
{{ page.name }}
+
{{ page.name }} + +

{{ page.url }}

diff --git a/components/apps/setting/permission/PermissionRow.vue b/components/apps/setting/permission/PermissionRow.vue new file mode 100644 index 0000000..d762f5c --- /dev/null +++ b/components/apps/setting/permission/PermissionRow.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/components/apps/setting/permission/RoleList.vue b/components/apps/setting/permission/RoleList.vue new file mode 100644 index 0000000..f58d4f0 --- /dev/null +++ b/components/apps/setting/permission/RoleList.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/components/apps/setting/permission/RoleModal.vue b/components/apps/setting/permission/RoleModal.vue new file mode 100644 index 0000000..ae04170 --- /dev/null +++ b/components/apps/setting/permission/RoleModal.vue @@ -0,0 +1,206 @@ + + + diff --git a/components/layout/full/vertical-sidebar/sidebarItem.ts b/components/layout/full/vertical-sidebar/sidebarItem.ts index 144bc70..3586b75 100644 --- a/components/layout/full/vertical-sidebar/sidebarItem.ts +++ b/components/layout/full/vertical-sidebar/sidebarItem.ts @@ -33,6 +33,10 @@ const sidebarItem: menu[] = [ { title: 'Menu', to: '/apps/setting/pages', + }, + { + title: 'Role Permission', + to: '/apps/setting/permission', } ] }, diff --git a/components/shared/AppRightDialog.vue b/components/shared/AppRightDialog.vue new file mode 100644 index 0000000..c38b9eb --- /dev/null +++ b/components/shared/AppRightDialog.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/components/shared/DialogConfrim.vue b/components/shared/DialogConfrim.vue index 5464bbb..93d625b 100644 --- a/components/shared/DialogConfrim.vue +++ b/components/shared/DialogConfrim.vue @@ -76,6 +76,7 @@ const onConfirm = () => { :color="props.cancelColor" variant="tonal" flat + prepend-icon="mdi-cancel" :disabled="props.loading" @click="onCancel" > @@ -86,6 +87,7 @@ const onConfirm = () => { :color="props.confirmColor" variant="tonal" flat + prepend-icon="mdi-check" :loading="props.loading" :disabled="props.loading" @click="onConfirm" diff --git a/pages/apps/setting/pages/index.vue b/pages/apps/setting/pages/index.vue index e34d956..b9d3b89 100644 --- a/pages/apps/setting/pages/index.vue +++ b/pages/apps/setting/pages/index.vue @@ -1,9 +1,10 @@ + + + + diff --git a/pages/apps/setting/permission/index.vue b/pages/apps/setting/permission/index.vue new file mode 100644 index 0000000..bbbb11e --- /dev/null +++ b/pages/apps/setting/permission/index.vue @@ -0,0 +1,149 @@ + + + diff --git a/pages/auth/Verify.vue b/pages/auth/Verify.vue new file mode 100644 index 0000000..c379f37 --- /dev/null +++ b/pages/auth/Verify.vue @@ -0,0 +1,67 @@ + + + + + \ No newline at end of file diff --git a/redirect.html b/redirect.html new file mode 100644 index 0000000..9dd570f --- /dev/null +++ b/redirect.html @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/server/api/auth/keycloak-callback.get.ts b/server/api/auth/keycloak-callback.get.ts index 2163d14..a238cf8 100644 --- a/server/api/auth/keycloak-callback.get.ts +++ b/server/api/auth/keycloak-callback.get.ts @@ -154,7 +154,7 @@ export default defineEventHandler(async (event) => { }, accessToken: tokens.access_token, refreshToken: tokens.refresh_token, - // idToken: tokens.id_token, + idToken: tokens.id_token, expiresAt: (nowInSeconds + sessionDurationSeconds) * 1000, }; diff --git a/server/api/auth/sessionUser.get.ts b/server/api/auth/sessionUser.get.ts index 4b2a6cc..cd59be6 100644 --- a/server/api/auth/sessionUser.get.ts +++ b/server/api/auth/sessionUser.get.ts @@ -43,7 +43,11 @@ export default defineEventHandler(async (event) => { }); } - return session; + return { + ...session, + payloadIdToken : session.idToken ? parseJwtPayload(session.idToken) : null, + payloadAccessToken: accessPayload + }; } catch (error: any) { if (error?.statusCode) { throw error; diff --git a/types/setting/roleMaster.ts b/types/setting/roleMaster.ts new file mode 100644 index 0000000..64678d2 --- /dev/null +++ b/types/setting/roleMaster.ts @@ -0,0 +1,28 @@ +export interface PaginationMeta { + page: number; + limit: number; + total: number; + total_pages: number; +} + +export interface RoleMaster { + id: number; + name: string; + active: boolean; + created_at: string | null; + updated_at: string | null; + select?: boolean | null; +} + +export interface RoleMasterResponse { + status: string; + message: string; + data: RoleMaster; +} + +export interface RoleMasterListResponse { + status: string; + message: string; + data: RoleMaster[]; + meta: PaginationMeta; +} diff --git a/types/setting/rolePermission.ts b/types/setting/rolePermission.ts new file mode 100644 index 0000000..95adf84 --- /dev/null +++ b/types/setting/rolePermission.ts @@ -0,0 +1,32 @@ +export interface RoleAccessPermission { + id: number | null; + create: boolean; + read: boolean; + update: boolean; + delete: boolean; + disable: boolean; +} + +export interface RoleAccessItem { + id: number; + name: string; + icon: string; + url: string; + group: string; + level: number; + sort: number; + active: boolean; + permission: RoleAccessPermission; + children?: RoleAccessItem[]; +} + +export interface RolePermissionData { + role: string; + access: RoleAccessItem[]; +} + +export interface RolePermissionResponse { + status: string; + message: string; + data: RolePermissionData; +} diff --git a/utils/api.ts b/utils/api.ts index 436bdf9..eabb14b 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -1,6 +1,8 @@ import axios from 'axios' +import { useAuth } from '~/composables/useAuth'; const config = useRuntimeConfig() +const { sessionData } = useAuth(); const api = axios.create({ baseURL: config.public.baseUrl @@ -33,7 +35,7 @@ const refreshAccessToken = async (): Promise => { method: 'POST', body: JSON.stringify({ refresh_token: refreshToken, - provider: 'keycloak', + provider: sessionData.value?.user?.auth_provider || 'keycloak' }), }) diff --git a/utils/datetime.ts b/utils/datetime.ts new file mode 100644 index 0000000..d8b3794 --- /dev/null +++ b/utils/datetime.ts @@ -0,0 +1,31 @@ +type DateInput = Date | string | number | null | undefined; + +const pad2 = (value: number) => String(value).padStart(2, '0'); + +const toDate = (input: DateInput): Date | null => { + if (input == null) return null; + + if (input instanceof Date) { + return Number.isNaN(input.getTime()) ? null : input; + } + + const date = new Date(input); + return Number.isNaN(date.getTime()) ? null : date; +}; + +/** + * Format datetime to `d-m-Y H:i` (e.g. `07-04-2026 14:05`). + * Uses local time. + */ +export const formatDateTime = (input: DateInput, fallback = '-') => { + const date = toDate(input); + if (!date) return fallback; + + const day = pad2(date.getDate()); + const month = pad2(date.getMonth() + 1); + const year = String(date.getFullYear()); + const hours = pad2(date.getHours()); + const minutes = pad2(date.getMinutes()); + + return `${day}-${month}-${year} ${hours}:${minutes}`; +};