'use client'; import { create } from 'zustand'; import type { OvertimeRecord } from '@/lib/overtime'; import type { DictItem } from '@/lib/requirement'; import { calcDuration } from '@/lib/overtime'; import { createOvertimeRecordDomain, deleteOvertimeRecordDomain, listOvertimeRecordsDomain, updateOvertimeRecordDomain, } from '@/lib/domain-api'; import { scheduleSaveWithOptimisticRollback } from '@/lib/optimistic-persistence'; import { loadServerData, saveServerData, SERVER_DATA_CACHE_MS } from '@/lib/server-data'; const PRESET_REASONS: DictItem[] = [ { id: 'reason-1', name: '需求变更', createdAt: '2024-01-01' }, { id: 'reason-2', name: '需求新增', createdAt: '2024-01-01' }, { id: 'reason-3', name: '版本冲刺', createdAt: '2024-01-01' }, { id: 'reason-4', name: 'Bug修复', createdAt: '2024-01-01' }, { id: 'reason-5', name: '线上问题修复', createdAt: '2024-01-01' }, { id: 'reason-6', name: '测试问题整改', createdAt: '2024-01-01' }, { id: 'reason-7', name: '技术难点攻关', createdAt: '2024-01-01' }, { id: 'reason-8', name: '联调问题', createdAt: '2024-01-01' }, { id: 'reason-9', name: '上游交付延误', createdAt: '2024-01-01' }, { id: 'reason-10', name: '工作量超负荷', createdAt: '2024-01-01' }, { id: 'reason-11', name: '返工修改', createdAt: '2024-01-01' }, ]; const MOCK_RECORDS: OvertimeRecord[] = []; let lastOvertimeFetchAt = 0; interface OvertimeState { records: OvertimeRecord[]; reasons: DictItem[]; loading: boolean; loaded: boolean; fetchRecords: (options?: { force?: boolean }) => Promise; createRecord: (data: Omit) => void; updateRecord: (id: string, data: Partial) => void; deleteRecord: (id: string) => void; addReason: (name: string) => void; updateReason: (id: string, name: string) => void; deleteReason: (id: string) => void; } async function loadStoredReasons(): Promise { try { const stored = await loadServerData<{ records?: OvertimeRecord[]; reasons?: DictItem[] }>('overtime'); return Array.isArray(stored?.reasons) ? stored.reasons : null; } catch {} return null; } export const useOvertimeStore = create((set, get) => ({ records: MOCK_RECORDS, reasons: PRESET_REASONS, loading: false, loaded: false, fetchRecords: async (options) => { if (!options?.force && get().loaded && Date.now() - lastOvertimeFetchAt < SERVER_DATA_CACHE_MS) return; const [storedReasons, domainRecords] = await Promise.all([ loadStoredReasons(), listOvertimeRecordsDomain().catch(() => null), ]); if (!options?.force && get().loaded && Date.now() - lastOvertimeFetchAt < SERVER_DATA_CACHE_MS) return; lastOvertimeFetchAt = Date.now(); set({ records: domainRecords ?? MOCK_RECORDS, reasons: storedReasons ?? PRESET_REASONS, loaded: true, }); }, createRecord: (data) => { const previous = get().records; const duration = calcDuration(data.startTime, data.endTime); const record: OvertimeRecord = { ...data, id: `ot-${Date.now()}`, duration, createdAt: new Date().toISOString().slice(0, 10), }; const records = [...previous, record]; set({ records, loaded: true }); scheduleSaveWithOptimisticRollback({ save: async () => { const created = await createOvertimeRecordDomain(record); set({ records: get().records.map((item) => (item.id === record.id ? { ...record, ...created } : item)), loaded: true, }); }, expected: records, getCurrent: () => get().records, rollback: () => set({ records: previous, loaded: true }), }); }, updateRecord: (id, data) => { const previous = get().records; const records = previous.map((r) => { if (r.id !== id) return r; const updated = { ...r, ...data }; if (data.startTime || data.endTime) { updated.duration = calcDuration(updated.startTime, updated.endTime); } return updated; }); set({ records, loaded: true }); scheduleSaveWithOptimisticRollback({ save: async () => { await updateOvertimeRecordDomain(id, records.find((record) => record.id === id) ?? {}); }, expected: records, getCurrent: () => get().records, rollback: () => set({ records: previous, loaded: true }), }); }, deleteRecord: (id) => { const previous = get().records; const records = previous.filter((r) => r.id !== id); set({ records, loaded: true }); scheduleSaveWithOptimisticRollback({ save: async () => { await deleteOvertimeRecordDomain(id); }, expected: records, getCurrent: () => get().records, rollback: () => set({ records: previous, loaded: true }), }); }, addReason: (name) => { const previous = get().reasons; const item: DictItem = { id: `reason-${Date.now()}`, name, createdAt: new Date().toISOString().slice(0, 10) }; const reasons = [...previous, item]; set({ reasons, loaded: true }); scheduleSaveWithOptimisticRollback({ save: () => saveOvertimeReasons(reasons), expected: reasons, getCurrent: () => get().reasons, rollback: () => set({ reasons: previous, loaded: true }), }); }, updateReason: (id, name) => { const previous = get().reasons; const reasons = previous.map((r) => r.id === id ? { ...r, name } : r); set({ reasons, loaded: true }); scheduleSaveWithOptimisticRollback({ save: () => saveOvertimeReasons(reasons), expected: reasons, getCurrent: () => get().reasons, rollback: () => set({ reasons: previous, loaded: true }), }); }, deleteReason: (id) => { const previous = get().reasons; const reasons = previous.filter((r) => r.id !== id); set({ reasons, loaded: true }); scheduleSaveWithOptimisticRollback({ save: () => saveOvertimeReasons(reasons), expected: reasons, getCurrent: () => get().reasons, rollback: () => set({ reasons: previous, loaded: true }), }); }, })); async function saveOvertimeReasons(reasons: DictItem[]) { await saveServerData('overtime', { records: [], reasons }); }