109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
'use client';
|
||
import { create } from 'zustand';
|
||
import { formatLocalDate } from '@/lib/format';
|
||
import { mergeWorkActivities, type WorkActivity, type WorkActivityDraft, type WorkActivitySourceType } from '@/lib/work-activity';
|
||
import { loadServerData, saveServerData } from '@/lib/server-data';
|
||
|
||
interface ProgressNoteInput {
|
||
actorId: string;
|
||
sourceType: WorkActivitySourceType;
|
||
sourceId: string;
|
||
title: string;
|
||
note: string;
|
||
blocker?: string;
|
||
helperId?: string;
|
||
delayRisk?: string;
|
||
}
|
||
|
||
interface WorkActivityState {
|
||
activities: WorkActivity[];
|
||
fetchActivities: () => Promise<void>;
|
||
addActivity: (data: WorkActivityDraft) => WorkActivity;
|
||
addProgressNote: (data: ProgressNoteInput) => WorkActivity;
|
||
deleteActivity: (id: string) => void;
|
||
}
|
||
|
||
async function saveStored(
|
||
items: WorkActivity[],
|
||
setActivities?: (items: WorkActivity[]) => void,
|
||
options: { mergeRemote?: boolean } = {},
|
||
) {
|
||
try {
|
||
if (options.mergeRemote === false) {
|
||
await saveServerData('work-activities', items);
|
||
return;
|
||
}
|
||
const remote = await loadServerData<WorkActivity[]>('work-activities');
|
||
const merged = mergeWorkActivities(Array.isArray(remote) ? remote : [], items);
|
||
setActivities?.(merged);
|
||
await saveServerData('work-activities', merged);
|
||
} catch {
|
||
saveServerData('work-activities', items).catch(() => {});
|
||
}
|
||
}
|
||
|
||
async function loadStored(): Promise<WorkActivity[] | null> {
|
||
try {
|
||
return await loadServerData<WorkActivity[]>('work-activities');
|
||
} catch {}
|
||
return null;
|
||
}
|
||
|
||
function createActivityId(): string {
|
||
return `act-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
||
}
|
||
|
||
export const useWorkActivityStore = create<WorkActivityState>((set, get) => ({
|
||
activities: [],
|
||
|
||
fetchActivities: async () => {
|
||
const cached = await loadStored();
|
||
if (cached) set({ activities: cached });
|
||
},
|
||
|
||
addActivity: (data) => {
|
||
const now = new Date();
|
||
const item: WorkActivity = {
|
||
...data,
|
||
id: createActivityId(),
|
||
date: data.date ?? formatLocalDate(now),
|
||
occurredAt: data.occurredAt ?? now.toISOString(),
|
||
};
|
||
const updated = [...get().activities, item];
|
||
set({ activities: updated });
|
||
saveStored(updated, (activities) => set({ activities }));
|
||
return item;
|
||
},
|
||
|
||
addProgressNote: (data) => {
|
||
const details = [
|
||
data.note.trim(),
|
||
data.blocker?.trim() ? `阻塞:${data.blocker.trim()}` : '',
|
||
data.helperId?.trim() ? `需协助:${data.helperId.trim()}` : '',
|
||
data.delayRisk?.trim() ? `延期风险:${data.delayRisk.trim()}` : '',
|
||
].filter(Boolean);
|
||
|
||
return get().addActivity({
|
||
actorId: data.actorId,
|
||
sourceType: data.sourceType,
|
||
sourceId: data.sourceId,
|
||
action: 'progress_note_added',
|
||
category: data.blocker?.trim() || data.delayRisk?.trim() ? 'risk' : 'note',
|
||
title: data.title,
|
||
summary: `补充进展:${details.join(';')}`,
|
||
metadata: {
|
||
note: data.note.trim(),
|
||
blocker: data.blocker?.trim() || undefined,
|
||
helperId: data.helperId?.trim() || undefined,
|
||
delayRisk: data.delayRisk?.trim() || undefined,
|
||
},
|
||
});
|
||
},
|
||
|
||
deleteActivity: (id) => {
|
||
const updated = get().activities.filter((activity) => activity.id !== id);
|
||
set({ activities: updated });
|
||
saveStored(updated, (activities) => set({ activities }), { mergeRemote: false });
|
||
},
|
||
}));
|