import { nextTick, type Ref } from 'vue' interface UseCrudCommandsOptions { clearAllErrors: () => void dialogMode: Ref<'create' | 'edit' | 'view'> dialogVisible: Ref editingId: Ref fieldErrors: Ref> form: Ref highlightedId: Ref isDirty: Readonly> isLoading: Ref isSaving: Ref isViewMode: Readonly> loadDelay?: number loadSequence: Ref resetForm: () => void saveDelay?: number scrollToField: (field: string) => void setForm: (payload: TPayload) => void syncInitialForm: () => void toFormPayload: (record: TRecord) => TPayload toSavePayload: (form: TPayload) => TPayload updateRecord: (id: number, payload: TPayload) => unknown createRecord: (payload: TPayload) => number validateForm: () => Array<{ field: string; message: string }> } export function useCrudCommands( options: UseCrudCommandsOptions ) { function openAddDialog() { options.loadSequence.value += 1 options.dialogMode.value = 'create' options.editingId.value = null options.resetForm() options.isLoading.value = false options.dialogVisible.value = true } function loadRecord(record: TRecord, mode: 'edit' | 'view') { options.loadSequence.value += 1 const sequence = options.loadSequence.value options.dialogMode.value = mode options.editingId.value = record.id options.dialogVisible.value = true options.isLoading.value = true options.clearAllErrors() window.setTimeout(() => { if (sequence !== options.loadSequence.value || !options.dialogVisible.value) return options.setForm(options.toFormPayload(record)) options.syncInitialForm() options.isLoading.value = false }, options.loadDelay ?? 350) } function openEditDialog(record: TRecord) { loadRecord(record, 'edit') } function openViewDialog(record: TRecord) { loadRecord(record, 'view') } async function requestSaveConfirmation(confirmSaveVisible: Ref) { if ( options.isSaving.value || options.isLoading.value || !options.isDirty.value || options.isViewMode.value ) { return } options.clearAllErrors() const errors = options.validateForm() if (errors.length > 0) { for (const error of errors) { options.fieldErrors.value[error.field] = [error.message] } await nextTick() const firstError = errors[0] if (firstError) options.scrollToField(firstError.field) return } confirmSaveVisible.value = true } function confirmSave(confirmSaveVisible: Ref) { confirmSaveVisible.value = false return saveRecord() } async function saveRecord() { if (options.isSaving.value || options.isLoading.value) return options.isSaving.value = true await new Promise((resolve) => window.setTimeout(resolve, options.saveDelay ?? 450)) const payload = options.toSavePayload(options.form.value) if (options.editingId.value) { const updated = options.updateRecord(options.editingId.value, payload) if (updated) options.highlightedId.value = options.editingId.value } else { const createdId = options.createRecord(payload) options.highlightedId.value = createdId } options.syncInitialForm() options.dialogVisible.value = false options.isSaving.value = false window.setTimeout(() => { options.highlightedId.value = null }, 1600) } return { confirmSave, openAddDialog, openEditDialog, openViewDialog, requestSaveConfirmation, saveRecord, } }