'use client'; import { create } from 'zustand'; import type { DevTask, DevTaskStatus } from '@/lib/dev-task'; import { canTransition, generateTaskNo } from '@/lib/dev-task'; const STORAGE_KEY = 'ftb_dev_tasks_v1'; function saveLocal(items: DevTask[]) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); } catch {} } function loadLocal(): DevTask[] | null { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch {} return null; } interface DevTaskState { tasks: DevTask[]; fetchTasks: () => void; createTask: (data: Omit) => DevTask; updateTask: (id: string, data: Partial) => void; deleteTask: (id: string) => void; changeStatus: (id: string, to: DevTaskStatus) => { ok: boolean; message?: string }; setBlocked: (id: string, blocked: boolean, reason?: string, blockedById?: string) => void; syncActualHours: (taskId: string, hours: number) => void; getByRequirement: (requirementId: string) => DevTask[]; getByVersionViaRequirements: (requirementIds: string[]) => DevTask[]; getByAssignee: (assigneeId: string) => DevTask[]; } export const useDevTaskStore = create((set, get) => ({ tasks: [], fetchTasks: () => { const cached = loadLocal(); if (cached) set({ tasks: cached }); }, createTask: (data) => { const list = get().tasks; const now = new Date().toISOString(); const task: DevTask = { ...data, id: `task-${Date.now()}`, taskNo: generateTaskNo(list), actualHours: 0, isBlocked: false, createdAt: now, updatedAt: now, }; const updated = [...list, task]; set({ tasks: updated }); saveLocal(updated); return task; }, updateTask: (id, data) => { const updated = get().tasks.map((t) => t.id === id ? { ...t, ...data, updatedAt: new Date().toISOString() } : t, ); set({ tasks: updated }); saveLocal(updated); }, deleteTask: (id) => { const updated = get().tasks.filter((t) => t.id !== id); set({ tasks: updated }); saveLocal(updated); }, changeStatus: (id, to) => { const task = get().tasks.find((t) => t.id === id); if (!task) return { ok: false, message: '任务不存在' }; if (!canTransition(task.status, to)) { return { ok: false, message: `不允许从「${task.status}」流转到「${to}」` }; } const completedAt = to === 'done' ? new Date().toISOString().slice(0, 10) : task.completedAt; get().updateTask(id, { status: to, completedAt }); return { ok: true }; }, setBlocked: (id, blocked, reason, blockedById) => { get().updateTask(id, { isBlocked: blocked, blockReason: blocked ? reason : undefined, blockedById: blocked ? blockedById : undefined, }); }, syncActualHours: (taskId, hours) => { get().updateTask(taskId, { actualHours: hours }); }, getByRequirement: (requirementId) => { return get().tasks.filter((t) => t.requirementId === requirementId); }, getByVersionViaRequirements: (requirementIds) => { const idSet = new Set(requirementIds); return get().tasks.filter((t) => idSet.has(t.requirementId)); }, getByAssignee: (assigneeId) => { return get().tasks.filter((t) => t.assigneeId === assigneeId); }, }));