96b96bcaaa
Split current project diagnostics into a dedicated analysis document and trim the main architecture strategy to focus on core guidance. This makes the documentation easier to navigate and separates observed issues from recommended architectural principles.docs: reorganize architecture strategy documentation Split current project diagnostics into a dedicated analysis document and trim the main architecture strategy to focus on core guidance. This makes the documentation easier to navigate and separates observed issues from recommended architectural principles.
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
|
|
}
|
|
|
|
export 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,
|
|
}
|
|
}
|