canina/frontend/application/components/SmartAdvisor.tsx
parsa aghaei 7615aa0e51 fix: resolve seed encoding, search param, and wiki data source issues
- 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
2026-07-11 15:40:49 +03:30

310 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import { useSettingsStore } from "../lib/store/settingsStore";
import {
Dog,
Cat,
ChevronLeft,
ChevronRight,
ShieldCheck,
Activity,
Bone,
Sparkles,
Pill,
Sparkle
} from "lucide-react";
import { toPersian, cn } from "../lib/utils";
import { useRouter } from "next/navigation";
import { useUserStore } from "../lib/store/userStore";
import { useUIStore } from "../lib/store/uiStore";
import { usePetStore } from "../lib/store/usePetStore";
interface SmartAdvisorProps {
onComplete?: (data: any) => void;
}
const MEDICAL_OPTIONS = [
{ id: "joint_surgery", label: "جراحی مفاصل", condition: "جراحی مفاصل" },
{ id: "recent_birth", label: "زایمان اخیر", condition: "زایمان اخیر" },
{ id: "pregnancy", label: "بارداری", condition: "بارداری" },
{ id: "digestion", label: "مشکلات گوارشی", condition: "مشکلات گوارشی" },
{ id: "hair_loss", label: "ریزش موی شدید", condition: "ریزش موی شدید" },
{ id: "appetite", label: "بی‌اشتهایی", condition: "بی‌اشتهایی" },
];
export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorProps & { rules?: any[] }) {
const router = useRouter();
const { isLoggedIn } = useUserStore();
const setLoginModalOpen = useUIStore(state => state.setLoginModalOpen);
const { addPet, setActivePet } = usePetStore();
const texts = useSettingsStore(state => state.texts);
const medicalOptions = React.useMemo(() => {
const jsonStr = texts['medical_options'];
if (jsonStr) {
try {
return JSON.parse(jsonStr) as typeof MEDICAL_OPTIONS;
} catch (e) {
console.error("Failed to parse medical_options JSON:", e);
}
}
return MEDICAL_OPTIONS;
}, [texts]);
const [step, setStep] = useState(1);
// Step 1 Data
const [name, setName] = useState("");
const [type, setType] = useState<"سگ" | "گربه">("سگ");
const [breed, setBreed] = useState("");
// Step 2 Data
const [age, setAge] = useState<number>(3);
const [weight, setWeight] = useState<number>(10);
const [activityLevel, setActivityLevel] = useState<"کم" | "متوسط" | "زیاد">("متوسط");
// Step 3 Data
const [medicalConditions, setMedicalConditions] = useState<string[]>([]);
const handleNext = () => setStep(s => s + 1);
const handleBack = () => setStep(s => s - 1);
const handleSubmit = () => {
const data = {
name, type, breed, age, weight, activityLevel, medicalConditions
};
if (onComplete) {
onComplete(data);
} else {
if (!isLoggedIn) {
setLoginModalOpen(true, data);
} else {
const newPet: any = { ...data, id: Date.now().toString(), reminders: [], logs: [], consumptions: [] };
addPet(newPet);
setActivePet(newPet.id);
router.push('/profile');
}
}
};
return (
<section id="canino-advisor" className="py-24 bg-white">
<div className="max-w-4xl mx-auto px-4">
<div className="text-center mb-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
className="inline-flex items-center gap-2 px-4 py-2 bg-canina-blue/5 rounded-full mb-6"
>
<Sparkle className="w-4 h-4 text-canina-blue" />
<span className="text-canina-blue text-xs font-black uppercase tracking-widest font-vazir">تکنولوژی پایش هوشمند</span>
</motion.div>
<h2 className="text-4xl lg:text-5xl font-black text-medical-gray-900 mb-4 font-vazir italic">دستیار سلامت کانینا</h2>
<p className="text-medical-gray-600 font-bold text-lg font-vazir">فقط چند قدم تا صدور شناسنامه هوشمند و دریافت پیشنهادات تخصصی</p>
</div>
<div className="bg-medical-gray-50 rounded-[3.5rem] border border-medical-gray-100 p-8 lg:p-12 shadow-2xl shadow-medical-gray-200/50 relative overflow-hidden">
{/* Progress Indicator */}
<div className="absolute top-0 left-0 w-full h-2 bg-medical-gray-200">
<motion.div
className="h-full bg-canina-blue"
initial={{ width: "0%" }}
animate={{ width: `${(step / 3) * 100}%` }}
transition={{ duration: 0.5 }}
/>
</div>
<AnimatePresence mode="wait">
{step === 1 && (
<motion.div
key="step1"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">گام اول: هویت بصری</h3>
<p className="text-medical-gray-600 font-bold font-vazir">همدم شما رو با چه اسمی صدا میزنید؟</p>
</div>
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => setType("سگ")}
className={`flex flex-col items-center gap-4 p-8 rounded-[2.5rem] border-4 transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none ${type === "سگ" ? 'border-canina-blue bg-white shadow-xl shadow-canina-blue/10 scale-105' : 'border-transparent bg-white/50 text-medical-gray-400 hover:bg-white'}`}
>
<Dog className={`w-12 h-12 ${type === "سگ" ? 'text-canina-blue' : 'text-current'}`} />
<span className="text-lg font-black font-vazir">سگ</span>
</button>
<button
onClick={() => setType("گربه")}
className={`flex flex-col items-center gap-4 p-8 rounded-[2.5rem] border-4 transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none ${type === "گربه" ? 'border-canina-blue bg-white shadow-xl shadow-canina-blue/10 scale-105' : 'border-transparent bg-white/50 text-medical-gray-400 hover:bg-white'}`}
>
<Cat className={`w-12 h-12 ${type === "گربه" ? 'text-canina-blue' : 'text-current'}`} />
<span className="text-lg font-black font-vazir">گربه</span>
</button>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-xs font-black text-medical-gray-600 pr-2">نام پت</label>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="لوسی، تدی..."
className="w-full bg-white border border-medical-gray-300 rounded-2xl py-4 px-6 focus:ring-2 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40 outline-none font-black text-lg font-vazir placeholder-medical-gray-500 text-medical-gray-900"
/>
</div>
<div className="space-y-2">
<label className="text-xs font-black text-medical-gray-600 pr-2">نژاد</label>
<input
type="text"
value={breed}
onChange={e => setBreed(e.target.value)}
placeholder="ژرمن، پرشین..."
className="w-full bg-white border border-medical-gray-300 rounded-2xl py-4 px-6 focus:ring-2 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40 outline-none font-bold font-vazir placeholder-medical-gray-500 text-medical-gray-900"
/>
</div>
</div>
<button
onClick={handleNext}
disabled={!name || !breed}
className="w-full py-6 rounded-3xl font-black text-lg flex items-center justify-center gap-3 font-vazir min-h-[48px] transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:shadow-none disabled:border-2 disabled:border-gray-400 bg-medical-gray-900 text-white hover:bg-canina-blue shadow-xl shadow-medical-gray-900/10"
>
ثبت هویت و ادامه
<ChevronLeft className="w-5 h-5" />
</button>
{(!name || !breed) && (
<p className="text-center text-xs font-black text-amber-600 mt-2 font-vazir animate-pulse">
لطفاً ابتدا نام و نژاد پت را در بالا وارد کنید.
</p>
)}
</motion.div>
)}
{step === 2 && (
<motion.div
key="step2"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">گام دوم: پایش فیزیکی</h3>
<p className="text-medical-gray-600 font-bold font-vazir">اطلاعات فیزیکی دقیق به دوزبندی صحیح مکملها کمک میکند</p>
</div>
<div className="grid grid-cols-2 gap-6">
<div className="space-y-4">
<label className="block text-center text-sm font-black text-medical-gray-800 font-vazir">سن حیوان (سال)</label>
<div className="flex items-center gap-4 bg-white rounded-3xl p-2 border-2 border-medical-gray-200">
<button onClick={() => setAge(Math.max(0, age - 1))} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">-</button>
<span className="flex-1 text-center text-2xl font-black text-canina-blue font-vazir">{toPersian(age.toString())} <span className="text-sm font-bold text-medical-gray-400">سال</span></span>
<button onClick={() => setAge(age + 1)} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">+</button>
</div>
</div>
<div className="space-y-4">
<label className="block text-center text-sm font-black text-medical-gray-800 font-vazir">وزن حیوان (کیلوگرم)</label>
<div className="flex items-center gap-4 bg-white rounded-3xl p-2 border-2 border-medical-gray-200">
<button onClick={() => setWeight(Math.max(1, weight - 1))} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">-</button>
<span className="flex-1 text-center text-2xl font-black text-canina-blue font-vazir">{toPersian(weight.toString())} <span className="text-sm font-bold text-medical-gray-400">kg</span></span>
<button onClick={() => setWeight(weight + 1)} className="w-12 h-12 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white focus-visible:ring-2 focus-visible:ring-canina-blue focus-visible:outline-none transition-all shadow-sm font-black text-lg">+</button>
</div>
</div>
</div>
<div className="space-y-4">
<label className="block text-center text-xs font-black text-medical-gray-600 uppercase tracking-widest font-vazir">سطح فعالیت روزانه</label>
<div className="grid grid-cols-3 gap-3">
{["کم", "متوسط", "زیاد"].map(level => (
<button
key={level}
onClick={() => setActivityLevel(level as any)}
className={`py-4 rounded-2xl border-2 transition-all font-black italic focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${activityLevel === level ? 'border-canina-blue bg-white text-canina-blue shadow-lg' : 'border-transparent bg-white/50 text-medical-gray-500 hover:bg-white'}`}
>
{level}
</button>
))}
</div>
</div>
<div className="flex gap-4 pt-4">
<button
onClick={handleBack}
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
>
<ChevronRight className="w-5 h-5" />
قبلی
</button>
<button
onClick={handleNext}
className="flex-[2] py-6 bg-medical-gray-900 text-white rounded-3xl font-black text-lg hover:bg-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-medical-gray-900/10 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
>
گام نهایی سلامت
<ChevronLeft className="w-5 h-5" />
</button>
</div>
</motion.div>
)}
{step === 3 && (
<motion.div
key="step3"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">گام آخر: سوابق و حساسیتها</h3>
<p className="text-medical-gray-600 font-bold font-vazir">در صورت وجود هر یک از موارد زیر، آن را تیک بزنید</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{medicalOptions.map((opt) => (
<button
key={opt.id}
onClick={() => {
setMedicalConditions(prev =>
prev.includes(opt.condition) ? prev.filter(c => c !== opt.condition) : [...prev, opt.condition]
);
}}
className={`flex items-center gap-4 p-5 rounded-2xl border-2 transition-all text-right focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${medicalConditions.includes(opt.condition) ? 'border-canina-blue bg-white shadow-md' : 'border-transparent bg-white/50 text-medical-gray-600 hover:bg-white'}`}
>
<div className={`w-8 h-8 rounded-lg flex items-center justify-center border ${medicalConditions.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 text-transparent'}`}>
<ShieldCheck className="w-5 h-5" />
</div>
<span className={`text-sm font-black font-vazir ${medicalConditions.includes(opt.condition) ? 'text-canina-blue' : 'text-medical-gray-900'}`}>{opt.label}</span>
</button>
))}
</div>
<div className="flex gap-4 pt-4">
<button
onClick={handleBack}
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
>
<ChevronRight className="w-5 h-5" />
قبلی
</button>
<button
onClick={handleSubmit}
className="flex-[2] py-6 bg-canina-blue text-white rounded-3xl font-black text-lg hover:bg-indigo-700 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-canina-blue/20 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
>
تکمیل و صدور شناسنامه
<ChevronLeft className="w-5 h-5" />
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</section>
);
}