'use client'; import { create } from 'zustand'; import type { Bug, BugStatus, BugLog } from '@/lib/bug'; import { canBugTransition, generateBugNo } from '@/lib/bug'; const STORAGE_KEY = 'ftb_bugs_v1'; function saveLocal(items: Bug[]) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); } catch {} } function loadLocal(): Bug[] | null { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch {} return null; } function makeLog(action: BugLog['action'], operator: string, from?: string, to?: string, remark?: string): BugLog { return { id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, action, fromValue: from, toValue: to, operator, remark, createdAt: new Date().toISOString() }; } interface BugState { bugs: Bug[]; fetchBugs: () => void; createBug: (data: Omit, operator: string) => Bug; updateBug: (id: string, data: Partial) => void; deleteBug: (id: string) => void; changeStatus: (id: string, to: BugStatus, operator: string, extra?: { resolution?: string }) => { ok: boolean; message?: string }; transferBug: (id: string, newAssigneeId: string, operator: string, remark?: string) => { ok: boolean; message?: string }; getByTestCase: (caseId: string) => Bug[]; getByVersion: (versionId: string) => Bug[]; getByAssignee: (assigneeId: string) => Bug[]; } export const useBugStore = create((set, get) => ({ bugs: [], fetchBugs: () => { const cached = loadLocal(); if (cached) set({ bugs: cached }); }, createBug: (data, operator) => { const list = get().bugs; const now = new Date().toISOString(); const log = makeLog('create', operator); const bug: Bug = { ...data, id: `bug-${Date.now()}`, bugNo: generateBugNo(list), status: 'open', logs: [log], createdAt: now, updatedAt: now, }; const updated = [...list, bug]; set({ bugs: updated }); saveLocal(updated); return bug; }, updateBug: (id, data) => { const updated = get().bugs.map((b) => b.id === id ? { ...b, ...data, updatedAt: new Date().toISOString() } : b, ); set({ bugs: updated }); saveLocal(updated); }, deleteBug: (id) => { const updated = get().bugs.filter((b) => b.id !== id); set({ bugs: updated }); saveLocal(updated); }, changeStatus: (id, to, operator, extra) => { const bug = get().bugs.find((b) => b.id === id); if (!bug) return { ok: false, message: 'Bug不存在' }; if (!canBugTransition(bug.status, to)) { return { ok: false, message: `不允许从「${bug.status}」流转到「${to}」` }; } const now = new Date().toISOString(); const patch: Partial = { status: to }; if (to === 'fixed') patch.resolvedAt = now; if (to === 'closed') patch.closedAt = now; if (extra?.resolution) patch.resolution = extra.resolution; const log = makeLog(to === 'fixed' ? 'resolve' : 'status_change', operator, bug.status, to, extra?.resolution); patch.logs = [...(bug.logs || []), log]; get().updateBug(id, patch); return { ok: true }; }, transferBug: (id, newAssigneeId, operator, remark) => { const bug = get().bugs.find((b) => b.id === id); if (!bug) return { ok: false, message: 'Bug不存在' }; if (bug.assigneeId === newAssigneeId) return { ok: false, message: '已是当前负责人' }; const log = makeLog('transfer', operator, bug.assigneeId, newAssigneeId, remark); get().updateBug(id, { assigneeId: newAssigneeId, logs: [...(bug.logs || []), log] }); return { ok: true }; }, getByTestCase: (caseId) => { return get().bugs.filter((b) => b.testCaseId === caseId); }, getByVersion: (versionId) => { return get().bugs.filter((b) => b.versionId === versionId); }, getByAssignee: (assigneeId) => { return get().bugs.filter((b) => b.assigneeId === assigneeId); }, }));