canina/frontend/application/lib/store/usePetStore.ts
parsa aghaei aac0716046
Some checks are pending
Deploy Canina / deploy (push) Waiting to run
feat: complete all section 26 backlog tasks including pet image upload [TASK-26.18]
2026-08-05 15:45:11 +03:30

233 lines
8.4 KiB
TypeScript

"use client";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import api from "../services/api";
import { toast } from "sonner";
import { useUserStore } from "./userStore";
export interface Reminder {
id: string;
title: string;
time: string;
frequency: "روزانه" | "هفتگی";
productId?: string;
completedDates: string[]; // List of ISO dates when this was completed
}
export interface HealthLog {
id: string;
date: string;
appetite: "عالی" | "متوسط" | "کم";
energy: "زیاد" | "نرمال" | "بی‌حال";
digestion: "نرمال" | "حساس" | "مشکل‌دار";
note?: string;
}
export interface PetConsumption {
productId: string;
packageSize: number;
remaining: number;
}
export interface PetProfile {
id: string;
name: string;
type: "سگ" | "گربه";
breed: string;
age: number;
weight: number;
activityLevel: "کم" | "متوسط" | "زیاد";
medicalConditions: string[];
imageUrl?: string;
image?: string;
reminders: Reminder[];
logs: HealthLog[];
consumptions: PetConsumption[];
}
interface PetStore {
pets: PetProfile[];
activePetId: string | null;
addPet: (pet: Omit<PetProfile, "id">) => void;
removePet: (id: string) => void;
setActivePet: (id: string) => void;
getActivePet: () => PetProfile | null;
updatePet: (id: string, updates: Partial<PetProfile>) => void;
addReminder: (petId: string, reminder: Omit<Reminder, "id" | "completedDates">) => Promise<void>;
toggleReminder: (petId: string, reminderId: string, date: string) => Promise<void>;
addHealthLog: (petId: string, log: Omit<HealthLog, "id" | "date">) => Promise<void>;
setPets: (pets: Record<string, unknown>[]) => void;
reset: () => void;
}
export const usePetStore = create<PetStore>()(
persist(
(set, get) => ({
pets: [],
activePetId: null,
reset: () => set({ pets: [], activePetId: null }),
addPet: async (pet) => {
const { useUserStore } = await import("./userStore");
const isLoggedIn = useUserStore.getState().isLoggedIn;
if (isLoggedIn) {
try {
await api.post("/pets", {
name: pet.name,
type: pet.type,
breed: pet.breed || "",
weight: Number(pet.weight) || 0,
age: Number(pet.age) || 0,
activityLevel: pet.activityLevel || "متوسط",
medicalConditions: pet.medicalConditions || []
});
await useUserStore.getState().fetchProfile();
} catch (error) {
console.error("Failed to add pet via backend API:", error);
toast.error("خطا در ثبت پت در سرور");
}
} else {
const id = Math.random().toString(36).substring(7);
const newPet = {
...pet,
id,
reminders: pet.reminders || [],
logs: pet.logs || [],
consumptions: pet.consumptions || []
};
set((state) => ({
pets: [...state.pets, newPet],
activePetId: state.activePetId || id,
}));
}
},
removePet: async (id) => {
const { useUserStore } = await import("./userStore");
const isLoggedIn = useUserStore.getState().isLoggedIn;
if (isLoggedIn) {
try {
await api.delete(`/pets/${id}`);
await useUserStore.getState().fetchProfile();
} catch (error) {
console.error("Failed to remove pet via backend API:", error);
toast.error("خطا در حذف پت از سرور");
}
} else {
set((state) => {
const newPets = state.pets.filter((p) => p.id !== id);
return {
pets: newPets,
activePetId: state.activePetId === id ? (newPets[0]?.id || null) : state.activePetId,
};
});
}
},
setActivePet: (id) => set({ activePetId: id }),
getActivePet: () => {
const { pets, activePetId } = get();
try {
if (!useUserStore.getState().isLoggedIn) {
return null;
}
} catch {
// If store module not resolved yet
}
return pets.find((p) => p.id === activePetId) || null;
},
updatePet: async (id, updates) => {
set((state) => ({
pets: state.pets.map((p) => (p.id === id ? { ...p, ...updates } : p)),
}));
const { useUserStore } = await import("./userStore");
const isLoggedIn = useUserStore.getState().isLoggedIn;
if (isLoggedIn) {
try {
await api.patch(`/pets/${id}`, {
name: updates.name,
type: updates.type,
breed: updates.breed,
weight: updates.weight ? Number(updates.weight) : undefined,
age: updates.age ? Number(updates.age) : undefined,
activityLevel: updates.activityLevel,
medicalConditions: updates.medicalConditions
});
await useUserStore.getState().fetchProfile();
} catch (error) {
console.error("Failed to update pet via backend API:", error);
toast.error("خطا در ویرایش اطلاعات پت در سرور");
}
}
},
addReminder: async (petId, reminder) => {
try {
await api.post(`/pets/${petId}/reminders`, reminder);
const { useUserStore } = await import("./userStore");
await useUserStore.getState().fetchProfile();
} catch (error) {
console.error("Failed to add reminder:", error);
toast.error("خطا در ثبت یادآور");
}
},
toggleReminder: async (petId, reminderId, date) => {
try {
await api.post(`/pets/${petId}/reminders/${reminderId}/toggle`, { date });
const { useUserStore } = await import("./userStore");
await useUserStore.getState().fetchProfile();
} catch (error) {
console.error("Failed to toggle reminder:", error);
toast.error("خطا در تغییر وضعیت یادآور");
}
},
addHealthLog: async (petId, log) => {
try {
await api.post(`/pets/${petId}/health-logs`, log);
const { useUserStore } = await import("./userStore");
await useUserStore.getState().fetchProfile();
} catch (error) {
console.error("Failed to add health log:", error);
toast.error("خطا در ثبت گزارش سلامتی");
}
},
setPets: (backendPets) => {
const currentLocalPets = get().pets;
const mappedPets: PetProfile[] = backendPets.map(bp => {
const existingLocal = currentLocalPets.find(lp => lp.id === bp.id);
return {
id: String(bp.id),
name: String(bp.name),
type: (bp.type as PetProfile['type']) || "سگ",
breed: String(bp.breed || ''),
age: Number(bp.age || 0),
weight: Number(bp.weight || 0),
activityLevel: (bp.activityLevel as PetProfile['activityLevel']) || "متوسط",
medicalConditions: (bp.medicalConditions as Array<{ condition: string }>)?.map((mc) => mc.condition) || [],
image: (bp.imageUrl as string) || undefined,
reminders: (bp.reminders as Array<{ id: string; title: string; time: string; frequency: Reminder['frequency']; productId?: string; completions?: Array<{ completedDate: string }> }>)?.map((r) => ({
id: r.id,
title: r.title,
time: r.time,
frequency: r.frequency,
productId: r.productId,
completedDates: r.completions?.map((c) => c.completedDate) || []
})) || [],
logs: (bp.healthLogs as Array<{ id: string; loggedDate: string; appetite: HealthLog['appetite']; energy: HealthLog['energy']; digestion: HealthLog['digestion']; note?: string }>)?.map((hl) => ({
id: hl.id,
date: hl.loggedDate,
appetite: hl.appetite,
energy: hl.energy,
digestion: hl.digestion,
note: hl.note || undefined
})) || [],
consumptions: existingLocal?.consumptions || []
};
});
set({ pets: mappedPets, activePetId: get().activePetId && mappedPets.some(p => p.id === get().activePetId) ? get().activePetId : (mappedPets[0]?.id || null) });
},
}),
{ name: "canina-pets" }
)
);