feat(division): impl division list+entry

feat(form): add accessibility improvements to form components

- Add labelFor prop to Label component for better form element association
- Enhance Combobox with ARIA attributes for better screen reader support
- Update form fields with proper IDs and label associations

feat(pagination): adjust button width based on page number length

Add dynamic button sizing for pagination items to accommodate different digit lengths (1-99, 100-999, 1000+). This improves visual consistency when displaying varying page numbers.

feat(modal): add reusable dialog component and refactor division form

- Create new Dialog.vue component with configurable size and outside click prevention
- Replace inline dialog implementation in division list with new Dialog component
- Fix formatting in entry-form.vue

feat(data-table): add click handling for action cells

Implement handleActionCellClick function to manage click events on action cells, triggering dropdown buttons when clicked outside interactive elements. Add cursor-pointer class and click handler to action cells for better UX.

refactor(custom-ui): centralize action event strings in types

Replace hardcoded action event strings with constants from types.ts to improve maintainability and reduce potential typos

feat(confirmation): add reusable confirmation modal components

- Implement base confirmation.vue component with customizable props
- Create record-specific record-confirmation.vue for data operations
- Add comprehensive README.md documentation for usage
- Integrate confirmation flow in division list component

refactor(components): move dialog component to base directory and update imports

The dialog component was moved from custom-ui/modal to base/modal to better reflect its shared usage across the application. All import paths referencing the old location have been updated accordingly.

refactor(select): reorganize imports and adjust conditional formatting

- Reorder imports in Select.vue for better organization
- Adjust logical operator formatting in SelectContent.vue for consistency
This commit is contained in:
Khafid Prayoga
2025-09-03 09:45:58 +07:00
parent b6d30eb154
commit a9c286bd0a
17 changed files with 598 additions and 57 deletions
+83 -24
View File
@@ -4,6 +4,9 @@ import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
import type { PaginationMeta } from '~/components/pub/custom-ui/pagination/pagination.type'
import { refDebounced, useUrlSearchParams } from '@vueuse/core'
import AppDivisonEntryForm from '~/components/app/divison/entry-form.vue'
import Dialog from '~/components/pub/base/modal/dialog.vue'
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
import { division as divisionConf, schema as schemaConf } from './entry'
import { defaultQuery, querySchema } from './schema.query'
@@ -13,7 +16,10 @@ const data = ref([])
const isLoading = reactive<DataTableLoader>({
isTableLoading: false,
})
const isDialogOpen = ref(false)
// Dialog state
const isFormEntryDialogOpen = ref(false)
const isRecordConfirmationOpen = ref(false)
// URL state management
const queryParams = useUrlSearchParams('history', {
@@ -67,7 +73,7 @@ const headerPrep: HeaderPrep = {
label: 'Tambah Divisi',
icon: 'i-lucide-send',
onClick: () => {
isDialogOpen.value = true
isFormEntryDialogOpen.value = true
},
},
}
@@ -136,12 +142,38 @@ function handlePageChange(page: number) {
queryParams.page = page
}
async function handleDeleteRow(record: any) {
try {
// TODO : hit backend request untuk delete
console.log('Deleting record:', record)
// Simulate API call
// const response = await xfetch(`/api/v1/division/${record.id}`, {
// method: 'DELETE'
// })
// Refresh data setelah berhasil delete
await getDivisionList()
// TODO: Show success message
console.log('Record deleted successfully')
} catch (error) {
console.error('Error deleting record:', error)
// TODO: Show error message
} finally {
// Reset record state
recId.value = 0
recAction.value = ''
recItem.value = null
}
}
// #endregion region
// #region Form event handlers
function onCancelForm(resetForm: () => void) {
isDialogOpen.value = false
isFormEntryDialogOpen.value = false
setTimeout(() => {
resetForm()
}, 500)
@@ -160,7 +192,7 @@ async function onSubmitForm(values: any, resetForm: () => void) {
// })
// If successful, mark as success and close dialog
isDialogOpen.value = false
isFormEntryDialogOpen.value = false
isSuccess = true
// Refresh data after successful submission
@@ -208,34 +240,61 @@ watch(debouncedSearch, (newValue) => {
queryParams.page = 1 // Reset to first page when searching
}
})
// Watch for row actions
watch(recId, () => {
switch (recAction.value) {
case ActionEvents.showEdit:
// TODO: Handle edit action
// isFormEntryDialogOpen.value = true
break
case ActionEvents.showConfirmDelete:
// Trigger confirmation modal open
isRecordConfirmationOpen.value = true
break
}
})
// Handle confirmation result
function handleConfirmDelete(record: any, action: string) {
console.log('Confirmed action:', action, 'for record:', record)
handleDeleteRow(record)
}
function handleCancelConfirmation() {
// Reset record state when cancelled
recId.value = 0
recAction.value = ''
recItem.value = null
}
// #endregion
</script>
<template>
<div class="rounded-md border p-4">
<div class="rounded-md border p-4">
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
<AppDivisonList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
<Dialog v-model:open="isDialogOpen">
<DialogContent
class="sm:max-w-[425px]"
@interact-outside="(e) => e.preventDefault()"
@pointer-down-outside="(e) => e.preventDefault()"
>
<DialogHeader>
<DialogTitle>Tambah Divisi</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<AppDivisonEntryForm
:division="divisionConf"
:schema="schemaConf"
:initial-values="{ name: '', code: '', parentId: '' }"
@submit="onSubmitForm"
@cancel="onCancelForm"
/>
</DialogContent>
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Divisi" size="lg">
<AppDivisonEntryForm
:division="divisionConf" :schema="schemaConf"
:initial-values="{ name: '', code: '', parentId: '' }" @submit="onSubmitForm" @cancel="onCancelForm"
/>
</Dialog>
<!-- Record Confirmation Modal -->
<RecordConfirmation
v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
@confirm="handleConfirmDelete" @cancel="handleCancelConfirmation"
>
<template #default="{ record }">
<div class="text-sm">
<p><strong>ID:</strong> {{ record?.id }}</p>
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
</div>
</template>
</RecordConfirmation>
</div>
</template>