From ce785f20929459d2dba9231cc6a2a22cad31e932 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Sat, 4 Oct 2025 06:55:12 +0700 Subject: [PATCH] dev: hotfix + data table + genCrudHandler + crud-base --- .../pub/base/data-table/data-table.vue | 14 +-- app/handlers/_handler.ts | 104 ++++++++++++++++++ app/services/_crud-base.ts | 77 +++++++++++++ 3 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 app/services/_crud-base.ts diff --git a/app/components/pub/base/data-table/data-table.vue b/app/components/pub/base/data-table/data-table.vue index 1127b4e4..9acd021b 100644 --- a/app/components/pub/base/data-table/data-table.vue +++ b/app/components/pub/base/data-table/data-table.vue @@ -10,9 +10,9 @@ const props = defineProps<{ cols: Col[] header: Th[][] keys: string[] - funcParsed: RecStrFuncUnknown - funcHtml: RecStrFuncUnknown - funcComponent: RecStrFuncComponent + funcParsed?: RecStrFuncUnknown + funcHtml?: RecStrFuncUnknown + funcComponent?: RecStrFuncComponent selectMode?: 'single' | 'multiple' modelValue?: any[] | any }>() @@ -118,16 +118,16 @@ function handleActionCellClick(event: Event, _cellRef: string) { diff --git a/app/handlers/_handler.ts b/app/handlers/_handler.ts index 4148c726..bcb1f808 100644 --- a/app/handlers/_handler.ts +++ b/app/handlers/_handler.ts @@ -104,6 +104,110 @@ export function createCrudHandler(crud: { } } +// Factory for CRUD handler state and actions +export function genCrudHandler(crud: { + create: (...args: any[]) => Promise + update: (...args: any[]) => Promise + remove: (...args: any[]) => Promise +}) { + const recId = ref(0) + const recAction = ref('') + const recItem = ref(null) + const isReadonly = ref(false) + const isProcessing = ref(false) + const isFormEntryDialogOpen = ref(false) + const isRecordConfirmationOpen = ref(false) + + function onResetState() { + recId.value = 0 + recAction.value = '' + recItem.value = null + } + + async function handleActionSave(values: any, refresh: () => void, reset: () => void, toast: ToastFn) { + isProcessing.value = true + await handleAsyncAction<[any], any>({ + action: crud.create, + args: [values], + toast, + successMessage: 'Data berhasil disimpan', + errorMessage: 'Gagal menyimpan data', + onSuccess: () => { + isFormEntryDialogOpen.value = false + if (refresh) refresh() + }, + onFinally: (isSuccess: boolean) => { + if (isSuccess) setTimeout(reset, 500) + isProcessing.value = false + }, + }) + } + + async function handleActionEdit( + id: number | string, + values: any, + refresh: () => void, + reset: () => void, + toast: ToastFn, + ) { + isProcessing.value = true + await handleAsyncAction<[number | string, any], any>({ + action: crud.update, + args: [id, values], + toast, + successMessage: 'Data berhasil diubah', + errorMessage: 'Gagal mengubah data', + onSuccess: () => { + isFormEntryDialogOpen.value = false + if (refresh) refresh() + }, + onFinally: (isSuccess: boolean) => { + if (isSuccess) setTimeout(reset, 500) + isProcessing.value = false + }, + }) + } + + async function handleActionRemove(id: number | string, refresh: () => void, toast: ToastFn) { + isProcessing.value = true + await handleAsyncAction<[number | string], any>({ + action: crud.remove, + args: [id], + toast, + successMessage: 'Data berhasil dihapus', + errorMessage: 'Gagal menghapus data', + onSuccess: () => { + isRecordConfirmationOpen.value = false + if (refresh) refresh() + }, + onFinally: () => { + isProcessing.value = false + }, + }) + } + + function handleCancelForm(reset: () => void) { + isFormEntryDialogOpen.value = false + isReadonly.value = false + setTimeout(reset, 300) + } + + return { + recId, + recAction, + recItem, + isReadonly, + isProcessing, + isFormEntryDialogOpen, + isRecordConfirmationOpen, + onResetState, + handleActionSave, + handleActionEdit, + handleActionRemove, + handleCancelForm, + } +} + // Reusable async handler for CRUD actions with toast and state management export type ToastFn = (params: { title: string; description: string; variant: 'default' | 'destructive' }) => void diff --git a/app/services/_crud-base.ts b/app/services/_crud-base.ts new file mode 100644 index 00000000..f9d33744 --- /dev/null +++ b/app/services/_crud-base.ts @@ -0,0 +1,77 @@ +import { xfetch } from '~/composables/useXfetch' + +export async function create(path: string, data: any) { + try { + const resp = await xfetch(path, 'POST', data) + const result: any = {} + result.success = resp.success + result.body = (resp.body as Record) || {} + return result + } catch (error) { + console.error('Error posting division:', error) + throw new Error('Failed to post division') + } +} + +export async function getList(path: string, params: any = null) { + try { + let url = path + if (params && typeof params === 'object' && Object.keys(params).length > 0) { + const searchParams = new URLSearchParams() + for (const key in params) { + if (params[key] !== null && params[key] !== undefined && params[key] !== '') { + searchParams.append(key, params[key]) + } + } + const queryString = searchParams.toString() + if (queryString) url += `?${queryString}` + } + const resp = await xfetch(path, 'GET') + const result: any = {} + result.success = resp.success + result.body = (resp.body as Record) || {} + return result + } catch (error) { + console.error('Error fetching divisions:', error) + throw new Error('Failed to fetch divisions') + } +} + +export async function getDetail(path: string, id: number | string) { + try { + const resp = await xfetch(`${path}/${id}`, 'GET') + const result: any = {} + result.success = resp.success + result.body = (resp.body as Record) || {} + return result + } catch (error) { + console.error('Error fetching division detail:', error) + throw new Error('Failed to get division detail') + } +} + +export async function update(path: string, id: number | string, data: any) { + try { + const resp = await xfetch(`${path}/${id}`, 'PATCH', data) + const result: any = {} + result.success = resp.success + result.body = (resp.body as Record) || {} + return result + } catch (error) { + console.error('Error putting division:', error) + throw new Error('Failed to put division') + } +} + +export async function remove(path: string, id: number | string) { + try { + const resp = await xfetch(`${path}/${id}`, 'DELETE') + const result: any = {} + result.success = resp.success + result.body = (resp.body as Record) || {} + return result + } catch (error) { + console.error('Error deleting data:', error) + throw new Error('Failed to delete division') + } +}