feat: add SingleRecord component for student maintenance with CRUD functionality
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
import { computed, type ComputedRef, ref, type Ref } from 'vue'
|
||||
|
||||
type DialogMode = 'create' | 'edit' | 'view'
|
||||
|
||||
interface UseMaintenanceCrudFlowOptions<T extends { id: number }> {
|
||||
records: ComputedRef<T[]>
|
||||
editingId: Ref<number | null>
|
||||
dialogMode: Ref<DialogMode>
|
||||
dialogVisible: Ref<boolean>
|
||||
isLoading: Ref<boolean>
|
||||
isSaving: Ref<boolean>
|
||||
isDirty: Readonly<Ref<boolean>> | ComputedRef<boolean>
|
||||
clearAllErrors: () => void
|
||||
resetForm: () => void
|
||||
openEditDialog: (record: T) => void
|
||||
openViewDialog: (record: T) => void
|
||||
removeRecord: (id: number) => void
|
||||
describeRecord: (record: T) => string
|
||||
onCloseReset?: () => void
|
||||
onAfterDelete?: (deletedId: number) => void
|
||||
}
|
||||
|
||||
interface UseMaintenanceCrudFlowResult<T extends { id: number }> {
|
||||
confirmCloseVisible: Ref<boolean>
|
||||
confirmSaveVisible: Ref<boolean>
|
||||
confirmDeleteVisible: Ref<boolean>
|
||||
confirmSwitchVisible: Ref<boolean>
|
||||
confirmNavigateVisible: Ref<boolean>
|
||||
pendingDelete: Ref<T | null>
|
||||
pendingDeleteLabel: ComputedRef<string>
|
||||
currentRecordIndex: ComputedRef<number>
|
||||
currentEditingRecord: ComputedRef<T | null>
|
||||
hasPrevRecord: ComputedRef<boolean>
|
||||
hasNextRecord: ComputedRef<boolean>
|
||||
isEditMode: ComputedRef<boolean>
|
||||
isViewMode: ComputedRef<boolean>
|
||||
openAdjacentRecord: (direction: 'prev' | 'next') => void
|
||||
openEdgeRecord: (position: 'first' | 'last') => void
|
||||
switchToEditMode: () => void
|
||||
switchToViewMode: () => void
|
||||
confirmSwitch: () => void
|
||||
confirmNavigate: () => void
|
||||
requestDeleteConfirmation: (record: T) => void
|
||||
requestDeleteCurrent: () => void
|
||||
confirmDelete: () => void
|
||||
requestCloseDialog: () => void
|
||||
confirmClose: () => void
|
||||
closeDialog: () => void
|
||||
handleDialogVisibility: (nextValue: boolean) => void
|
||||
}
|
||||
|
||||
export function useMaintenanceCrudFlow<T extends { id: number }>(
|
||||
options: UseMaintenanceCrudFlowOptions<T>
|
||||
): UseMaintenanceCrudFlowResult<T> {
|
||||
const confirmCloseVisible = ref(false)
|
||||
const confirmSaveVisible = ref(false)
|
||||
const confirmDeleteVisible = ref(false)
|
||||
const confirmSwitchVisible = ref(false)
|
||||
const confirmNavigateVisible = ref(false)
|
||||
const pendingDelete = ref<T | null>(null) as Ref<T | null>
|
||||
const pendingSwitchTarget = ref<T | null>(null)
|
||||
const pendingNavigateTarget = ref<T | null>(null)
|
||||
|
||||
const isEditMode = computed(() => options.dialogMode.value === 'edit')
|
||||
const isViewMode = computed(() => options.dialogMode.value === 'view')
|
||||
const currentRecordIndex = computed(() =>
|
||||
options.records.value.findIndex((item) => item.id === options.editingId.value)
|
||||
)
|
||||
const currentEditingRecord = computed(
|
||||
() => options.records.value.find((item) => item.id === options.editingId.value) || null
|
||||
)
|
||||
const hasPrevRecord = computed(() => currentRecordIndex.value > 0)
|
||||
const hasNextRecord = computed(
|
||||
() =>
|
||||
currentRecordIndex.value >= 0 && currentRecordIndex.value < options.records.value.length - 1
|
||||
)
|
||||
const pendingDeleteLabel = computed(() => {
|
||||
if (!pendingDelete.value) return '這筆資料'
|
||||
return options.describeRecord(pendingDelete.value)
|
||||
})
|
||||
|
||||
function openAdjacentRecord(direction: 'prev' | 'next') {
|
||||
if (!isViewMode.value && !isEditMode.value) return
|
||||
const index = currentRecordIndex.value
|
||||
if (index < 0) return
|
||||
const targetIndex = direction === 'prev' ? index - 1 : index + 1
|
||||
const target = options.records.value[targetIndex]
|
||||
if (!target) return
|
||||
if (isEditMode.value) {
|
||||
if (options.isDirty.value) {
|
||||
pendingNavigateTarget.value = target
|
||||
confirmNavigateVisible.value = true
|
||||
return
|
||||
}
|
||||
options.openEditDialog(target)
|
||||
return
|
||||
}
|
||||
options.openViewDialog(target)
|
||||
}
|
||||
|
||||
function openEdgeRecord(position: 'first' | 'last') {
|
||||
if (!isViewMode.value && !isEditMode.value) return
|
||||
if (options.records.value.length === 0) return
|
||||
const target = position === 'first' ? options.records.value[0] : options.records.value.at(-1)
|
||||
if (!target) return
|
||||
if (isEditMode.value) {
|
||||
if (options.isDirty.value) {
|
||||
pendingNavigateTarget.value = target
|
||||
confirmNavigateVisible.value = true
|
||||
return
|
||||
}
|
||||
options.openEditDialog(target)
|
||||
return
|
||||
}
|
||||
options.openViewDialog(target)
|
||||
}
|
||||
|
||||
function switchToEditMode() {
|
||||
if (!isViewMode.value) return
|
||||
const current = currentEditingRecord.value
|
||||
if (!current) return
|
||||
options.openEditDialog(current)
|
||||
}
|
||||
|
||||
function switchToViewMode() {
|
||||
if (!isEditMode.value) return
|
||||
const current = currentEditingRecord.value
|
||||
if (!current) return
|
||||
if (options.isDirty.value) {
|
||||
pendingSwitchTarget.value = current
|
||||
confirmSwitchVisible.value = true
|
||||
return
|
||||
}
|
||||
options.openViewDialog(current)
|
||||
}
|
||||
|
||||
function confirmSwitch() {
|
||||
const target = pendingSwitchTarget.value
|
||||
pendingSwitchTarget.value = null
|
||||
confirmSwitchVisible.value = false
|
||||
if (!target) return
|
||||
options.openViewDialog(target)
|
||||
}
|
||||
|
||||
function confirmNavigate() {
|
||||
const target = pendingNavigateTarget.value
|
||||
pendingNavigateTarget.value = null
|
||||
confirmNavigateVisible.value = false
|
||||
if (!target) return
|
||||
options.openEditDialog(target)
|
||||
}
|
||||
|
||||
function requestDeleteConfirmation(record: T) {
|
||||
pendingDelete.value = record
|
||||
confirmDeleteVisible.value = true
|
||||
}
|
||||
|
||||
function requestDeleteCurrent() {
|
||||
const current = currentEditingRecord.value
|
||||
if (!current) return
|
||||
requestDeleteConfirmation(current)
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
options.dialogVisible.value = false
|
||||
options.isLoading.value = false
|
||||
options.isSaving.value = false
|
||||
confirmCloseVisible.value = false
|
||||
confirmSaveVisible.value = false
|
||||
confirmDeleteVisible.value = false
|
||||
confirmSwitchVisible.value = false
|
||||
confirmNavigateVisible.value = false
|
||||
pendingDelete.value = null
|
||||
pendingSwitchTarget.value = null
|
||||
pendingNavigateTarget.value = null
|
||||
options.dialogMode.value = 'create'
|
||||
options.editingId.value = null
|
||||
options.clearAllErrors()
|
||||
options.resetForm()
|
||||
options.onCloseReset?.()
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
if (!pendingDelete.value) return
|
||||
const deletedId = pendingDelete.value.id
|
||||
options.removeRecord(deletedId)
|
||||
pendingDelete.value = null
|
||||
confirmDeleteVisible.value = false
|
||||
options.onAfterDelete?.(deletedId)
|
||||
if (options.editingId.value === deletedId) {
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
|
||||
function requestCloseDialog() {
|
||||
if (options.isDirty.value && !options.isSaving.value) {
|
||||
confirmCloseVisible.value = true
|
||||
return
|
||||
}
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
function confirmClose() {
|
||||
confirmCloseVisible.value = false
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
function handleDialogVisibility(nextValue: boolean) {
|
||||
if (nextValue) {
|
||||
options.dialogVisible.value = true
|
||||
return
|
||||
}
|
||||
requestCloseDialog()
|
||||
}
|
||||
|
||||
return {
|
||||
confirmCloseVisible,
|
||||
confirmSaveVisible,
|
||||
confirmDeleteVisible,
|
||||
confirmSwitchVisible,
|
||||
confirmNavigateVisible,
|
||||
pendingDelete,
|
||||
pendingDeleteLabel,
|
||||
currentRecordIndex,
|
||||
currentEditingRecord,
|
||||
hasPrevRecord,
|
||||
hasNextRecord,
|
||||
isEditMode,
|
||||
isViewMode,
|
||||
openAdjacentRecord,
|
||||
openEdgeRecord,
|
||||
switchToEditMode,
|
||||
switchToViewMode,
|
||||
confirmSwitch,
|
||||
confirmNavigate,
|
||||
requestDeleteConfirmation,
|
||||
requestDeleteCurrent,
|
||||
confirmDelete,
|
||||
requestCloseDialog,
|
||||
confirmClose,
|
||||
closeDialog,
|
||||
handleDialogVisibility,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user