Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读 - 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移 - 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
'use client';
|
|
import { create } from 'zustand';
|
|
import type { TaskWorklog } from '@/lib/task-worklog';
|
|
import {
|
|
createTaskWorklogDomain,
|
|
deleteTaskWorklogDomain,
|
|
listTaskWorklogsDomain,
|
|
} from '@/lib/domain-api';
|
|
import { scheduleSaveWithOptimisticRollback } from '@/lib/optimistic-persistence';
|
|
import { SERVER_DATA_CACHE_MS } from '@/lib/server-data';
|
|
|
|
let lastWorklogsFetchAt = 0;
|
|
|
|
interface TaskWorklogState {
|
|
worklogs: TaskWorklog[];
|
|
loaded: boolean;
|
|
fetchWorklogs: (options?: { force?: boolean }) => Promise<void>;
|
|
addWorklog: (data: Omit<TaskWorklog, 'id' | 'createdAt'>) => void;
|
|
deleteWorklog: (id: string) => void;
|
|
getActualHours: (taskId: string) => number;
|
|
}
|
|
|
|
export const useTaskWorklogStore = create<TaskWorklogState>((set, get) => ({
|
|
worklogs: [],
|
|
loaded: false,
|
|
|
|
fetchWorklogs: async (options) => {
|
|
if (!options?.force && get().loaded && Date.now() - lastWorklogsFetchAt < SERVER_DATA_CACHE_MS) return;
|
|
const cached = await listTaskWorklogsDomain().catch(() => []);
|
|
if (!options?.force && get().loaded && Date.now() - lastWorklogsFetchAt < SERVER_DATA_CACHE_MS) return;
|
|
lastWorklogsFetchAt = Date.now();
|
|
set({ worklogs: cached ?? [], loaded: true });
|
|
},
|
|
|
|
addWorklog: (data) => {
|
|
const previous = get().worklogs;
|
|
const item: TaskWorklog = {
|
|
...data,
|
|
id: `wl-${Date.now()}`,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
const updated = [...previous, item];
|
|
set({ worklogs: updated, loaded: true });
|
|
scheduleSaveWithOptimisticRollback({
|
|
save: async () => {
|
|
const created = await createTaskWorklogDomain(item);
|
|
set({
|
|
worklogs: get().worklogs.map((worklog) => (worklog.id === item.id ? { ...item, ...created } : worklog)),
|
|
loaded: true,
|
|
});
|
|
},
|
|
expected: updated,
|
|
getCurrent: () => get().worklogs,
|
|
rollback: () => set({ worklogs: previous, loaded: true }),
|
|
});
|
|
},
|
|
|
|
deleteWorklog: (id) => {
|
|
const previous = get().worklogs;
|
|
const updated = previous.filter((w) => w.id !== id);
|
|
set({ worklogs: updated, loaded: true });
|
|
scheduleSaveWithOptimisticRollback({
|
|
save: async () => {
|
|
await deleteTaskWorklogDomain(id);
|
|
},
|
|
expected: updated,
|
|
getCurrent: () => get().worklogs,
|
|
rollback: () => set({ worklogs: previous, loaded: true }),
|
|
});
|
|
},
|
|
|
|
getActualHours: (taskId) => {
|
|
return get().worklogs
|
|
.filter((w) => w.taskId === taskId)
|
|
.reduce((sum, w) => sum + w.hours, 0);
|
|
},
|
|
}));
|