- Fix seed-products.ts TS error (implicit any) and BOM handling - Re-run full seed to restore correct Persian encoding in DB - Fix productService query→search param mismatch - Fix IngredientWiki hardcoded port 4000→use settingsStore - Restore seed-products-data.json from git after accidental corruption
218 lines
7.5 KiB
TypeScript
218 lines
7.5 KiB
TypeScript
"use client";
|
|
import { create } from "zustand";
|
|
import { persist } from "zustand/middleware";
|
|
import api from "../services/api";
|
|
import { toast } from "sonner";
|
|
|
|
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[];
|
|
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: any[]) => void;
|
|
}
|
|
|
|
export const usePetStore = create<PetStore>()(
|
|
persist(
|
|
(set, get) => ({
|
|
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();
|
|
return pets.find((p) => p.id === activePetId) || null;
|
|
},
|
|
updatePet: async (id, updates) => {
|
|
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("خطا در ویرایش اطلاعات پت در سرور");
|
|
}
|
|
} else {
|
|
set((state) => ({
|
|
pets: state.pets.map((p) => (p.id === id ? { ...p, ...updates } : p)),
|
|
}));
|
|
}
|
|
},
|
|
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 mappedPets: PetProfile[] = backendPets.map(bp => ({
|
|
id: bp.id,
|
|
name: bp.name,
|
|
type: bp.type as any,
|
|
breed: bp.breed,
|
|
age: Number(bp.age),
|
|
weight: Number(bp.weight),
|
|
activityLevel: bp.activityLevel as any,
|
|
medicalConditions: bp.medicalConditions?.map((mc: any) => mc.condition) || [],
|
|
image: bp.imageUrl || undefined,
|
|
reminders: bp.reminders?.map((r: any) => ({
|
|
id: r.id,
|
|
title: r.title,
|
|
time: r.time,
|
|
frequency: r.frequency as any,
|
|
productId: r.productId,
|
|
completedDates: r.completions?.map((c: any) => c.completedDate) || []
|
|
})) || [],
|
|
logs: bp.healthLogs?.map((hl: any) => ({
|
|
id: hl.id,
|
|
date: hl.loggedDate,
|
|
appetite: hl.appetite as any,
|
|
energy: hl.energy as any,
|
|
digestion: hl.digestion as any,
|
|
note: hl.note || undefined
|
|
})) || [],
|
|
consumptions: []
|
|
}));
|
|
set({ pets: mappedPets, activePetId: get().activePetId && mappedPets.some(p => p.id === get().activePetId) ? get().activePetId : (mappedPets[0]?.id || null) });
|
|
},
|
|
}),
|
|
{ name: "canina-pets" }
|
|
)
|
|
);
|