topic.ts (1915B)
1 import { create } from "zustand"; 2 import { api } from "../lib/api"; 3 4 export interface Topic { 5 id: string; 6 name: string; 7 color: string; 8 sort_order: number; 9 created_at: number; 10 session_count: number | null; 11 } 12 13 interface TopicState { 14 topics: Topic[]; 15 selectedTopicId: string | null; 16 loading: boolean; 17 fetchTopics: () => Promise<void>; 18 createTopic: (name: string, color?: string) => Promise<void>; 19 updateTopic: (id: string, patch: Partial<Pick<Topic, "name" | "color" | "sort_order">>) => Promise<void>; 20 deleteTopic: (id: string) => Promise<void>; 21 selectTopic: (id: string | null) => void; 22 } 23 24 export const useTopicStore = create<TopicState>((set) => ({ 25 topics: [], 26 selectedTopicId: null, 27 loading: false, 28 29 fetchTopics: async () => { 30 set({ loading: true }); 31 try { 32 const topics = await api.get<Topic[]>("/topics"); 33 set({ topics }); 34 } catch { 35 // 401 handled by api helper (redirects to login) 36 } finally { 37 set({ loading: false }); 38 } 39 }, 40 41 createTopic: async (name, color) => { 42 try { 43 const topic = await api.post<Topic>("/topics", { name, color }); 44 set((s) => ({ topics: [...s.topics, topic] })); 45 } catch { 46 // API error — don't add to list 47 } 48 }, 49 50 updateTopic: async (id, patch) => { 51 try { 52 const updated = await api.patch<Topic>(`/topics/${id}`, patch); 53 set((s) => ({ 54 topics: s.topics.map((t) => (t.id === id ? updated : t)), 55 })); 56 } catch { 57 // API error — don't update local state 58 } 59 }, 60 61 deleteTopic: async (id) => { 62 try { 63 await api.delete(`/topics/${id}`); 64 set((s) => ({ 65 topics: s.topics.filter((t) => t.id !== id), 66 selectedTopicId: s.selectedTopicId === id ? null : s.selectedTopicId, 67 })); 68 } catch { 69 // API error — don't remove from list 70 } 71 }, 72 73 selectTopic: (id) => set({ selectedTopicId: id }), 74 }));