import type { StudentRecord } from '@/stores/students' import { computed, type ComputedRef, ref, type Ref } from 'vue' interface GradeOption { title: string value: number } interface StudentFormState { studentId: string name: string department: string grade: number enrollYear: number credits: number advisor: string email: string phone: string status: string } interface UseStudentMaintenanceFormOptions { departments: string[] gradeOptions: GradeOption[] enrollYears: number[] statuses: string[] students: ComputedRef editingId: Ref highlightedId: Ref } type SaveSummaryItem = { key: string label: string before: string | null after: string } const fieldLabels: Record = { studentId: '學號', name: '姓名', department: '系所', grade: '年級', enrollYear: '入學年度', credits: '已修學分', advisor: '指導老師', email: 'Email', phone: '電話', status: '狀態', } function createDefaultForm( departments: string[], gradeOptions: GradeOption[], enrollYears: number[], statuses: string[] ): StudentFormState { return { studentId: '', name: '', department: departments[0] ?? '', grade: gradeOptions[0]?.value ?? 1, enrollYear: enrollYears[0] ?? 2024, credits: 0, advisor: '', email: '', phone: '', status: statuses[0] ?? '', } } function createEmptyFieldErrors() { return { studentId: [], name: [], department: [], grade: [], enrollYear: [], credits: [], advisor: [], email: [], phone: [], status: [], } as Record } export function useStudentMaintenanceForm(options: UseStudentMaintenanceFormOptions) { const form = ref( createDefaultForm( options.departments, options.gradeOptions, options.enrollYears, options.statuses ) ) const initialForm = ref({ ...form.value }) const fieldErrors = ref(createEmptyFieldErrors()) const isDirty = computed(() => JSON.stringify(form.value) !== JSON.stringify(initialForm.value)) function gradeLabel(grade: number) { return options.gradeOptions.find((option) => option.value === grade)?.title ?? `年級 ${grade}` } function formatSummaryValue(key: string, value: string | number | null | undefined) { if (value === null || value === undefined || value === '') return '—' if (key === 'grade') return gradeLabel(Number(value)) return String(value) } const saveSummary = computed(() => { const before = initialForm.value const after = form.value const entries = Object.keys(fieldLabels).map((key) => { const fieldKey = key as keyof StudentFormState return { key, label: fieldLabels[fieldKey], before: options.editingId.value ? formatSummaryValue(key, before[fieldKey]) : null, after: formatSummaryValue(key, after[fieldKey]), } }) if (!options.editingId.value) return entries return entries.filter((entry) => entry.before !== entry.after) }) const errorSummary = computed(() => { const entries = Object.entries(fieldErrors.value).flatMap(([field, messages]) => messages.map((message) => ({ field, message })) ) return entries.slice(0, 3) }) function setForm(nextForm: StudentFormState) { form.value = { ...nextForm } } function syncInitialForm() { initialForm.value = { ...form.value } } function resetForm() { form.value = createDefaultForm( options.departments, options.gradeOptions, options.enrollYears, options.statuses ) syncInitialForm() clearAllErrors() } function clearAllErrors() { fieldErrors.value = createEmptyFieldErrors() } function clearFieldError(field: keyof StudentFormState | string) { if (!fieldErrors.value[field as keyof StudentFormState]?.length) return fieldErrors.value[field as keyof StudentFormState] = [] } function validateForm() { const errors: Array<{ field: keyof StudentFormState; message: string }> = [] const studentId = form.value.studentId.trim() const name = form.value.name.trim() const email = form.value.email.trim() const credits = form.value.credits if (!studentId) errors.push({ field: 'studentId', message: '請輸入學號' }) if (!name) errors.push({ field: 'name', message: '請輸入姓名' }) if (!form.value.department) errors.push({ field: 'department', message: '請選擇系所' }) if (!form.value.grade) errors.push({ field: 'grade', message: '請選擇年級' }) if (!form.value.enrollYear) errors.push({ field: 'enrollYear', message: '請選擇入學年度' }) if (credits === null || Number.isNaN(credits)) { errors.push({ field: 'credits', message: '請輸入已修學分' }) } else if (credits < 0) { errors.push({ field: 'credits', message: '已修學分不可小於 0' }) } if (!form.value.status) errors.push({ field: 'status', message: '請選擇狀態' }) if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { errors.push({ field: 'email', message: 'Email 格式不正確' }) } const duplicate = options.students.value.find( (item) => item.studentId === studentId && item.id !== options.editingId.value ) if (studentId && duplicate) { errors.push({ field: 'studentId', message: '學號已存在,請確認是否重複' }) } return errors } function statusColor(status: string) { if (status === '在學') return 'success' if (status === '休學') return 'warning' if (status === '畢業') return 'secondary' return 'default' } function rowProps(data: { item: StudentRecord }) { return { class: data.item.id === options.highlightedId.value ? 'is-highlighted' : '', } } const isPlaceholderValue = (value: string | null) => value === '—' return { errorSummary, fieldErrors, form, initialForm, isDirty, isPlaceholderValue, saveSummary, clearAllErrors, clearFieldError, formatSummaryValue, gradeLabel, resetForm, rowProps, setForm, statusColor, syncInitialForm, validateForm, } } export type { SaveSummaryItem, StudentFormState }