245 lines
7.2 KiB
TypeScript
245 lines
7.2 KiB
TypeScript
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,
|
|
}
|
|
}
|