639 lines
36 KiB
TypeScript
639 lines
36 KiB
TypeScript
"use client";
|
||
import React, { useState, useEffect } from "react";
|
||
import Link from "next/link";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
import {
|
||
Dog,
|
||
Cat,
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
ShieldCheck,
|
||
Activity,
|
||
Pill,
|
||
Sparkle,
|
||
Check,
|
||
Download
|
||
} from "lucide-react";
|
||
import { cn } from "../lib/utils";
|
||
import { PRODUCTS } from "../lib/data/products";
|
||
import { productService } from "../lib/services/productService";
|
||
|
||
import { useRouter } from "next/navigation";
|
||
import { useUserStore } from "../lib/store/userStore";
|
||
import { useUIStore } from "../lib/store/uiStore";
|
||
import { usePetStore } from "../lib/store/usePetStore";
|
||
import api from "../lib/services/api";
|
||
|
||
interface SmartAdvisorProps {
|
||
onComplete?: (data: Record<string, unknown>) => void;
|
||
}
|
||
|
||
const CURRENT_SYMPTOMS = [
|
||
{ id: "none_symptom", label: "هیچکدام (سلامت کامل و بدون علامت)", condition: "هیچکدام (سلامت کامل)" },
|
||
{ id: "joint_pain", label: "درد مفاصل و لنگیدن", condition: "درد مفاصل" },
|
||
{ id: "hair_loss", label: "ریزش مو، خارش و خشکی پوست", condition: "ریزش مو" },
|
||
{ id: "diarrhea", label: "اسهال، یبوست و مشکلات گوارشی", condition: "اسهال" },
|
||
{ id: "appetite", label: "بیاشتهایی و کاهش وزن", condition: "بیاشتهایی" },
|
||
{ id: "dental", label: "جرم دندان و بوی بد دهان", condition: "جرم دندان" },
|
||
{ id: "eye", label: "ترشحات یا مشکلات چشم", condition: "چشم" },
|
||
{ id: "puppy_growth", label: "رشد سریع و تعادل استخوانی", condition: "رشد سریع تولهسگ" },
|
||
{ id: "tendon", label: "سفتی عضلات و ضعف تاندون", condition: "تاندون" },
|
||
];
|
||
|
||
const MEDICAL_HISTORIES = [
|
||
{ id: "none_history", label: "هیچکدام (بدون سابقه بیماری و جراحی)", condition: "هیچکدام (بدون سابقه بیماری)" },
|
||
{ id: "joint_surgery", label: "سابقه جراحی مفاصل/استخوان", condition: "جراحی مفاصل" },
|
||
{ id: "recent_birth", label: "زایمان اخیر / شیردهی فعال", condition: "زایمان اخیر" },
|
||
{ id: "pregnancy", label: "دوران بارداری", condition: "بارداری" },
|
||
{ id: "weakness", label: "ضعف بعد از بیماری / نقاهت", condition: "ضعف بعد از بیماری" },
|
||
{ id: "heart", label: "نارسایی یا حساسیت قلبی", condition: "نارسایی قلبی" },
|
||
{ id: "parasite", label: "سابقه آلودگی انگلی", condition: "آلودگی انگلی" },
|
||
];
|
||
|
||
export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorProps & { rules?: unknown[] }) {
|
||
const router = useRouter();
|
||
const { isLoggedIn } = useUserStore();
|
||
const setLoginModalOpen = useUIStore(state => state.setLoginModalOpen);
|
||
const { addPet, setActivePet } = usePetStore();
|
||
const getText = useSettingsStore(state => state.getText);
|
||
|
||
const [advisorRules, setAdvisorRules] = useState<unknown[]>(rules || []);
|
||
|
||
useEffect(() => {
|
||
if (rules && rules.length > 0) {
|
||
setAdvisorRules(rules);
|
||
return;
|
||
}
|
||
|
||
let isMounted = true;
|
||
api.get('/smart-advisor/rules')
|
||
.then(res => {
|
||
if (isMounted && Array.isArray(res.data)) {
|
||
setAdvisorRules(res.data);
|
||
}
|
||
})
|
||
.catch(err => console.error("[SmartAdvisor] Failed to fetch rules:", err));
|
||
|
||
return () => {
|
||
isMounted = false;
|
||
};
|
||
}, [rules?.length]);
|
||
|
||
const [step, setStep] = useState(1);
|
||
|
||
// Step 1 Data
|
||
const [name, setName] = useState("");
|
||
const [type, setType] = useState<"سگ" | "گربه">("سگ");
|
||
const [breed, setBreed] = useState("");
|
||
const [showError, setShowError] = useState(false);
|
||
const [shake, setShake] = useState(false);
|
||
|
||
// Step 2 Data
|
||
const [age, setAge] = useState<number>(3);
|
||
const [weight, setWeight] = useState<number>(10);
|
||
const [activityLevel, setActivityLevel] = useState<"کم" | "متوسط" | "زیاد">("متوسط");
|
||
|
||
// Step 3 Data (Symptoms) & Step 4 Data (History) - default to none
|
||
const [currentSymptoms, setCurrentSymptoms] = useState<string[]>(["هیچکدام (سلامت کامل)"]);
|
||
const [medicalConditions, setMedicalConditions] = useState<string[]>(["هیچکدام (بدون سابقه بیماری)"]);
|
||
|
||
const toggleSymptom = (condition: string) => {
|
||
if (condition === "هیچکدام (سلامت کامل)") {
|
||
setCurrentSymptoms(["هیچکدام (سلامت کامل)"]);
|
||
return;
|
||
}
|
||
setCurrentSymptoms(prev => {
|
||
const filtered = prev.filter(c => c !== "هیچکدام (سلامت کامل)");
|
||
if (filtered.includes(condition)) {
|
||
const next = filtered.filter(c => c !== condition);
|
||
return next.length === 0 ? ["هیچکدام (سلامت کامل)"] : next;
|
||
} else {
|
||
return [...filtered, condition];
|
||
}
|
||
});
|
||
};
|
||
|
||
const toggleMedicalHistory = (condition: string) => {
|
||
if (condition === "هیچکدام (بدون سابقه بیماری)") {
|
||
setMedicalConditions(["هیچکدام (بدون سابقه بیماری)"]);
|
||
return;
|
||
}
|
||
setMedicalConditions(prev => {
|
||
const filtered = prev.filter(c => c !== "هیچکدام (بدون سابقه بیماری)");
|
||
if (filtered.includes(condition)) {
|
||
const next = filtered.filter(c => c !== condition);
|
||
return next.length === 0 ? ["هیچکدام (بدون سابقه بیماری)"] : next;
|
||
} else {
|
||
return [...filtered, condition];
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleNext = () => {
|
||
if (step === 1) {
|
||
if (!name.trim() || !breed.trim()) {
|
||
setShowError(true);
|
||
setShake(true);
|
||
setTimeout(() => setShake(false), 500);
|
||
return;
|
||
}
|
||
}
|
||
setStep(s => s + 1);
|
||
};
|
||
const handleBack = () => setStep(s => s - 1);
|
||
|
||
const calculateRecommendation = async () => {
|
||
const rawConditions = Array.from(new Set([...currentSymptoms, ...medicalConditions]));
|
||
const combinedConditions = rawConditions.filter(c => !c.includes("هیچکدام"));
|
||
|
||
let matchedProd: Record<string, unknown> | null = null;
|
||
|
||
// First, check fetched advisor rules
|
||
if (advisorRules && advisorRules.length > 0) {
|
||
const foundRule = advisorRules.find((r) => {
|
||
const ruleObj = r as Record<string, unknown>;
|
||
const targetPetType = ruleObj.targetPetType as string | undefined;
|
||
const condition = ruleObj.condition as string | undefined;
|
||
const petMatch = !targetPetType || targetPetType === "هر دو" || targetPetType === type;
|
||
const conditionMatch = combinedConditions.some(c => condition?.includes(c) || c.includes(condition || ''));
|
||
return petMatch && conditionMatch;
|
||
}) as Record<string, unknown> | undefined;
|
||
if (foundRule && foundRule.product) {
|
||
matchedProd = foundRule.product as Record<string, unknown>;
|
||
}
|
||
}
|
||
|
||
// Second, query dynamic products API
|
||
if (!matchedProd) {
|
||
try {
|
||
const liveRes = await productService.getProducts({ petType: type, limit: 50 });
|
||
if (liveRes.data && liveRes.data.length > 0) {
|
||
const found = liveRes.data.find(p => {
|
||
const matchPet = p.suitableFor === "هر دو" || p.suitableFor === type;
|
||
const matchSymptom = p.symptoms?.some((s: string) => combinedConditions.some(c => s.includes(c) || c.includes(s)));
|
||
return matchPet && matchSymptom;
|
||
});
|
||
matchedProd = (found || liveRes.data[0]) as unknown as Record<string, unknown>;
|
||
}
|
||
} catch (e) {
|
||
console.error("[SmartAdvisor] Dynamic product fetch failed, using fallback", e);
|
||
}
|
||
}
|
||
|
||
if (!matchedProd) {
|
||
const fallback = PRODUCTS.find(p => p.suitableFor === "هر دو" || p.suitableFor === type) || PRODUCTS[0];
|
||
matchedProd = fallback as unknown as Record<string, unknown>;
|
||
}
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
const rawConditions = Array.from(new Set([...currentSymptoms, ...medicalConditions]));
|
||
const combinedConditions = rawConditions.filter(c => !c.includes("هیچکدام"));
|
||
const data = {
|
||
name, type, breed, age, weight, activityLevel, medicalConditions: combinedConditions
|
||
};
|
||
|
||
calculateRecommendation();
|
||
|
||
if (onComplete) {
|
||
onComplete(data);
|
||
} else {
|
||
if (!isLoggedIn) {
|
||
setLoginModalOpen(true, data);
|
||
} else {
|
||
const newPet = { ...data, id: Date.now().toString(), reminders: [], logs: [], consumptions: [] };
|
||
addPet(newPet);
|
||
setActivePet(newPet.id);
|
||
router.push('/profile');
|
||
}
|
||
}
|
||
};
|
||
|
||
return (
|
||
<section id="canino-advisor" className="py-8 sm:py-16 bg-gradient-to-b from-white via-medical-gray-50/50 to-white">
|
||
<div className="max-w-5xl mx-auto px-4">
|
||
{/* Header Title */}
|
||
<div className="text-center mb-6 sm:mb-10">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
className="inline-flex items-center gap-2 px-3.5 py-1.5 sm:px-4 sm:py-2 bg-canina-blue/5 rounded-full mb-3 sm:mb-4 border border-canina-blue/10"
|
||
>
|
||
<Sparkle className="w-4 h-4 text-canina-blue animate-spin" />
|
||
<span suppressHydrationWarning className="text-canina-blue text-[11px] sm:text-xs font-black uppercase tracking-widest font-vazir">{getText('advisor_badge', "سیستم پایش و تجویز هوشمند")}</span>
|
||
</motion.div>
|
||
<h2 suppressHydrationWarning className="text-2xl sm:text-4xl lg:text-5xl font-black text-medical-gray-900 mb-2 sm:mb-4 font-lalezar tracking-wide">{getText('advisor_title', "دستیار سلامت و الگوریتم پایش کنینا")}</h2>
|
||
<p suppressHydrationWarning className="text-medical-gray-600 font-medium text-xs sm:text-base font-shabnam max-w-2xl mx-auto leading-relaxed">
|
||
{getText('advisor_subtitle', "با ثبت اطلاعات همدم خود در ۴ قدم ساده، از الگوریتم علمی کنینا برای تجویز دقیق مکمل و پایش سلامت بهرهمند شوید.")}
|
||
</p>
|
||
|
||
<div className="mt-4 flex justify-center">
|
||
<Link
|
||
href="/catalog"
|
||
className="inline-flex items-center gap-2 px-4 py-2 bg-medical-gray-50 border border-medical-gray-200 rounded-xl text-medical-gray-600 text-xs font-bold hover:bg-canina-blue hover:text-white transition-all shadow-sm"
|
||
>
|
||
<Download className="w-4 h-4" />
|
||
<span>مشاهده کاتالوگ و جدول دوز آلمان</span>
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Value Proposition Cards (Explaining Why & How) */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
className="grid grid-cols-1 md:grid-cols-3 gap-4 sm:gap-6 mb-8 sm:mb-12"
|
||
>
|
||
<div className="bg-white p-4 sm:p-6 rounded-2xl sm:rounded-[2.5rem] border border-medical-gray-200/80 shadow-md sm:shadow-lg hover:shadow-xl transition-all group flex flex-col justify-between">
|
||
<div>
|
||
<div className="flex items-center gap-3 sm:block mb-2 sm:mb-0">
|
||
<div className="w-9 h-9 sm:w-12 sm:h-12 bg-canina-blue/10 rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue sm:mb-4 group-hover:bg-canina-blue group-hover:text-white transition-all shadow-sm flex-shrink-0">
|
||
<Pill className="w-5 h-5 sm:w-6 sm:h-6" />
|
||
</div>
|
||
<h3 className="text-base sm:text-lg font-black text-medical-gray-900 sm:mb-2 font-vazir">چرا ثبت شناسنامه؟</h3>
|
||
</div>
|
||
<p className="text-xs text-medical-gray-500 font-medium leading-relaxed font-vazir">
|
||
مکملهای دارویی کنینا غلیظ و تخصصی هستند؛ الگوریتم هوشمند ما بر اساس سن، وزن و جثه پت شما، دوز دقیق مصرفی روزانه را محاسبه میکند.
|
||
</p>
|
||
</div>
|
||
<div className="mt-3 sm:mt-4 pt-3 sm:pt-4 border-t border-medical-gray-100 text-[10px] font-black text-canina-blue flex items-center gap-1 font-vazir">
|
||
<Check className="w-3.5 h-3.5" />
|
||
تضمین ایمنی و عدم اوردوز
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white p-4 sm:p-6 rounded-2xl sm:rounded-[2.5rem] border border-medical-gray-200/80 shadow-md sm:shadow-lg hover:shadow-xl transition-all group flex flex-col justify-between">
|
||
<div>
|
||
<div className="flex items-center gap-3 sm:block mb-2 sm:mb-0">
|
||
<div className="w-9 h-9 sm:w-12 sm:h-12 bg-canina-blue/10 rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue sm:mb-4 group-hover:bg-canina-blue group-hover:text-white transition-all shadow-sm flex-shrink-0">
|
||
<Activity className="w-5 h-5 sm:w-6 sm:h-6" />
|
||
</div>
|
||
<h3 className="text-base sm:text-lg font-black text-medical-gray-900 sm:mb-2 font-vazir">تطبیق علائم بالینی</h3>
|
||
</div>
|
||
<p className="text-xs text-medical-gray-500 font-medium leading-relaxed font-vazir">
|
||
با انتخاب علائمی مثل لنگیدن یا ریزش مو، دقیقترین راهکار مکمل آلمانی به همراه دلایل علمی به شما پیشنهاد میشود.
|
||
</p>
|
||
</div>
|
||
<div className="mt-3 sm:mt-4 pt-3 sm:pt-4 border-t border-medical-gray-100 text-[10px] font-black text-canina-blue flex items-center gap-1 font-vazir">
|
||
<Check className="w-3.5 h-3.5" />
|
||
پیشنهاد هدفمند و تخصصی
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white p-4 sm:p-6 rounded-2xl sm:rounded-[2.5rem] border border-medical-gray-200/80 shadow-md sm:shadow-lg hover:shadow-xl transition-all group flex flex-col justify-between">
|
||
<div>
|
||
<div className="flex items-center gap-3 sm:block mb-2 sm:mb-0">
|
||
<div className="w-9 h-9 sm:w-12 sm:h-12 bg-canina-blue/10 rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue sm:mb-4 group-hover:bg-canina-blue group-hover:text-white transition-all shadow-sm flex-shrink-0">
|
||
<ShieldCheck className="w-5 h-5 sm:w-6 sm:h-6" />
|
||
</div>
|
||
<h3 className="text-base sm:text-lg font-black text-medical-gray-900 sm:mb-2 font-vazir">پایش موجودی و یادآور</h3>
|
||
</div>
|
||
<p className="text-xs text-medical-gray-500 font-medium leading-relaxed font-vazir">
|
||
زمانهای مصرف مکملها تنظیم شده و سیستم قبل از اتمام دوره درمانی، هشدار شارژ مجدد ارسال خواهد کرد.
|
||
</p>
|
||
</div>
|
||
<div className="mt-3 sm:mt-4 pt-3 sm:pt-4 border-t border-medical-gray-100 text-[10px] font-black text-canina-blue flex items-center gap-1 font-vazir">
|
||
<Check className="w-3.5 h-3.5" />
|
||
تکمیل منظم دوره درمان
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
|
||
<div className="bg-medical-gray-50 rounded-[2.5rem] border border-medical-gray-200/80 p-5 sm:p-8 lg:p-10 shadow-xl shadow-medical-gray-200/40 relative overflow-hidden">
|
||
{/* Progress Indicator */}
|
||
<div className="absolute top-0 left-0 w-full h-1.5 bg-medical-gray-200">
|
||
<motion.div
|
||
className="h-full bg-canina-blue"
|
||
initial={{ width: "0%" }}
|
||
animate={{ width: `${(step / 4) * 100}%` }}
|
||
transition={{ duration: 0.4 }}
|
||
/>
|
||
</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-6"
|
||
>
|
||
<div className="text-center">
|
||
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1.5 font-vazir tracking-tight">{getText('advisor_step1_title', "گام اول: مشخصات پت")}</h3>
|
||
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step1_desc', "همدم شما رو با چه اسمی صدا میزنید؟")}</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-3 sm:gap-4 max-w-md mx-auto">
|
||
<button
|
||
type="button"
|
||
onClick={() => setType("سگ")}
|
||
className={`flex flex-col items-center gap-2.5 p-5 sm:p-6 rounded-2xl border-2 transition-all cursor-pointer ${type === "سگ" ? 'border-canina-blue bg-white shadow-md shadow-canina-blue/10 text-canina-blue scale-102' : 'border-medical-gray-200 bg-white text-medical-gray-500 hover:border-canina-blue/30'}`}
|
||
>
|
||
<Dog className="w-8 h-8 sm:w-10 sm:h-10" />
|
||
<span className="text-sm sm:text-base font-black font-vazir">سگ</span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setType("گربه")}
|
||
className={`flex flex-col items-center gap-2.5 p-5 sm:p-6 rounded-2xl border-2 transition-all cursor-pointer ${type === "گربه" ? 'border-canina-blue bg-white shadow-md shadow-canina-blue/10 text-canina-blue scale-102' : 'border-medical-gray-200 bg-white text-medical-gray-500 hover:border-canina-blue/30'}`}
|
||
>
|
||
<Cat className="w-8 h-8 sm:w-10 sm:h-10" />
|
||
<span className="text-sm sm:text-base font-black font-vazir">گربه</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="grid md:grid-cols-2 gap-4 max-w-xl mx-auto">
|
||
<div className="space-y-1.5">
|
||
<label className="text-xs font-black text-medical-gray-600 pr-1">{getText('advisor_pet_name_label', "نام پت")}</label>
|
||
<input
|
||
type="text"
|
||
value={name}
|
||
onChange={e => {
|
||
setName(e.target.value);
|
||
if (e.target.value.trim() && breed.trim()) setShowError(false);
|
||
}}
|
||
placeholder={getText('advisor_pet_name_placeholder', "مثال: لوسی")}
|
||
className={cn(
|
||
"w-full bg-white border rounded-xl py-3 px-4 focus:ring-2 outline-none font-bold text-sm font-vazir placeholder-medical-gray-400 text-medical-gray-900 transition-all",
|
||
showError && !name.trim()
|
||
? "border-red-500 focus:ring-red-500/30"
|
||
: "border-medical-gray-200 focus:ring-canina-blue/30 focus:border-canina-blue"
|
||
)}
|
||
/>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<label className="text-xs font-black text-medical-gray-600 pr-1">{getText('advisor_pet_breed_label', "نژاد")}</label>
|
||
<input
|
||
type="text"
|
||
value={breed}
|
||
onChange={e => {
|
||
setBreed(e.target.value);
|
||
if (name.trim() && e.target.value.trim()) setShowError(false);
|
||
}}
|
||
placeholder={getText('advisor_pet_breed_placeholder', "مثال: ژرمن شپرد")}
|
||
className={cn(
|
||
"w-full bg-white border rounded-xl py-3 px-4 focus:ring-2 outline-none font-bold text-sm font-vazir placeholder-medical-gray-400 text-medical-gray-900 transition-all",
|
||
showError && !breed.trim()
|
||
? "border-red-500 focus:ring-red-500/30"
|
||
: "border-medical-gray-200 focus:ring-canina-blue/30 focus:border-canina-blue"
|
||
)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="max-w-md mx-auto pt-2">
|
||
<motion.button
|
||
type="button"
|
||
onClick={handleNext}
|
||
animate={shake ? { x: [-10, 10, -10, 10, -5, 5, -2, 2, 0] } : { x: 0 }}
|
||
transition={{ duration: 0.4 }}
|
||
className={cn(
|
||
"w-full whitespace-nowrap bg-canina-blue text-white px-6 sm:px-10 py-3 sm:py-4 rounded-full font-bold text-sm sm:text-base hover:bg-canina-blue/90 hover:shadow-xl focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-2 group font-vazir min-h-[48px] shadow-md shadow-canina-blue/20 cursor-pointer",
|
||
(!name.trim() || !breed.trim()) ? "opacity-60" : ""
|
||
)}
|
||
>
|
||
<span>{getText('advisor_submit_btn', "ثبت هویت و ادامه")}</span>
|
||
<ChevronLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||
</motion.button>
|
||
{showError && (!name.trim() || !breed.trim()) && (
|
||
<p className="text-center text-xs font-bold text-red-500 mt-2 font-vazir animate-pulse">
|
||
{getText('advisor_validation_warning', "⚠️ لطفاً ابتدا نام و نژاد پت را وارد کنید.")}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</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-6 max-w-xl mx-auto"
|
||
>
|
||
<div className="text-center">
|
||
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1.5 font-vazir tracking-tight">{getText('advisor_step2_title', "گام دوم: پایش فیزیکی")}</h3>
|
||
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step2_desc', "اطلاعات فیزیکی به دوزبندی دقیق مکملها کمک میکند")}</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||
{/* Age Input Box */}
|
||
<div className="bg-white p-3.5 rounded-2xl border border-medical-gray-200 shadow-xs space-y-2">
|
||
<label className="block text-center text-xs font-black text-medical-gray-700 font-vazir">{getText('advisor_age_label', "سن حیوان (سال)")}</label>
|
||
<div className="flex items-center justify-between gap-1.5 bg-medical-gray-50 rounded-xl p-1 border border-medical-gray-200">
|
||
<button
|
||
type="button"
|
||
onClick={() => setAge(Math.max(0, age - 1))}
|
||
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
|
||
>
|
||
-
|
||
</button>
|
||
<div className="flex-1 flex items-center justify-center gap-1" dir="ltr">
|
||
<input
|
||
type="text"
|
||
inputMode="numeric"
|
||
pattern="[0-9]*"
|
||
value={age === 0 ? '' : age.toString()}
|
||
onChange={(e) => {
|
||
const val = e.target.value.replace(/[^0-9]/g, '');
|
||
setAge(val === '' ? 0 : Math.min(30, parseInt(val, 10)));
|
||
}}
|
||
className="w-14 text-center text-lg font-black text-canina-blue font-mono outline-none bg-transparent"
|
||
/>
|
||
<span className="text-[11px] font-bold text-medical-gray-400 font-vazir">سال</span>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => setAge(Math.min(30, age + 1))}
|
||
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Weight Input Box */}
|
||
<div className="bg-white p-3.5 rounded-2xl border border-medical-gray-200 shadow-xs space-y-2">
|
||
<label className="block text-center text-xs font-black text-medical-gray-700 font-vazir">{getText('advisor_weight_label', "وزن حیوان (کیلوگرم)")}</label>
|
||
<div className="flex items-center justify-between gap-1.5 bg-medical-gray-50 rounded-xl p-1 border border-medical-gray-200">
|
||
<button
|
||
type="button"
|
||
onClick={() => setWeight(Math.max(1, weight - 1))}
|
||
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
|
||
>
|
||
-
|
||
</button>
|
||
<div className="flex-1 flex items-center justify-center gap-1" dir="ltr">
|
||
<input
|
||
type="text"
|
||
inputMode="numeric"
|
||
pattern="[0-9]*"
|
||
value={weight === 0 ? '' : weight.toString()}
|
||
onChange={(e) => {
|
||
const val = e.target.value.replace(/[^0-9]/g, '');
|
||
setWeight(val === '' ? 0 : Math.min(120, parseInt(val, 10)));
|
||
}}
|
||
className="w-14 text-center text-lg font-black text-canina-blue font-mono outline-none bg-transparent"
|
||
/>
|
||
<span className="text-[11px] font-bold text-medical-gray-400 font-vazir">kg</span>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => setWeight(Math.min(120, weight + 1))}
|
||
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Activity Level */}
|
||
<div className="space-y-2">
|
||
<label className="block text-center text-xs font-black text-medical-gray-600 font-vazir">{getText('advisor_activity_label', "سطح فعالیت روزانه")}</label>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
{["کم", "متوسط", "زیاد"].map(level => (
|
||
<button
|
||
key={level}
|
||
type="button"
|
||
onClick={() => setActivityLevel(level as "کم" | "متوسط" | "زیاد")}
|
||
className={`py-2.5 rounded-xl border-2 transition-all font-bold text-xs sm:text-sm cursor-pointer ${activityLevel === level ? 'border-canina-blue bg-white text-canina-blue shadow-sm' : 'border-transparent bg-white/70 text-medical-gray-500 hover:bg-white'}`}
|
||
>
|
||
{level}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Buttons (matching Hero layout) */}
|
||
<div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
|
||
<button
|
||
type="button"
|
||
onClick={handleBack}
|
||
className="whitespace-nowrap bg-white border-2 border-medical-gray-300 text-medical-gray-700 px-4 sm:px-6 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-medical-gray-50 transition-all flex items-center gap-1.5 cursor-pointer"
|
||
>
|
||
<ChevronRight className="w-4 h-4" />
|
||
<span>{getText('advisor_back', "قبلی")}</span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={handleNext}
|
||
className="whitespace-nowrap bg-canina-blue text-white px-5 sm:px-8 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-canina-blue/90 hover:shadow-lg transition-all flex items-center gap-1.5 shadow-md shadow-canina-blue/20 cursor-pointer"
|
||
>
|
||
<span>{getText('advisor_next_symptoms', "علائم بالینی")}</span>
|
||
<ChevronLeft className="w-4 h-4" />
|
||
</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-5 max-w-2xl mx-auto"
|
||
>
|
||
<div className="text-center">
|
||
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1 font-vazir tracking-tight">{getText('advisor_step3_symptoms_title', "گام سوم: علائم بالینی فعلی")}</h3>
|
||
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step3_symptoms_desc', "آیا همدم شما در حال حاضر علائم زیر را دارد؟")}</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2.5 max-h-[50vh] sm:max-h-none overflow-y-auto pr-1">
|
||
{CURRENT_SYMPTOMS.map((opt) => (
|
||
<button
|
||
key={opt.id}
|
||
type="button"
|
||
onClick={() => toggleSymptom(opt.condition)}
|
||
className={`flex items-center gap-3 p-3.5 rounded-xl border transition-all text-right cursor-pointer ${currentSymptoms.includes(opt.condition) ? 'border-canina-blue bg-canina-blue/5 shadow-xs text-canina-blue' : 'border-medical-gray-200 bg-white text-medical-gray-700 hover:border-canina-blue/40'}`}
|
||
>
|
||
<div className={`w-5 h-5 rounded-md flex items-center justify-center border shrink-0 ${currentSymptoms.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 bg-white'}`}>
|
||
{currentSymptoms.includes(opt.condition) && <Check className="w-3.5 h-3.5 stroke-[3]" />}
|
||
</div>
|
||
<span className="text-xs sm:text-sm font-bold font-vazir leading-tight">{opt.label}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
|
||
<button
|
||
type="button"
|
||
onClick={handleBack}
|
||
className="whitespace-nowrap bg-white border-2 border-medical-gray-300 text-medical-gray-700 px-4 sm:px-6 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-medical-gray-50 transition-all flex items-center gap-1.5 cursor-pointer"
|
||
>
|
||
<ChevronRight className="w-4 h-4" />
|
||
<span>{getText('advisor_back', "قبلی")}</span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={handleNext}
|
||
className="whitespace-nowrap bg-canina-blue text-white px-5 sm:px-8 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-canina-blue/90 hover:shadow-lg transition-all flex items-center gap-1.5 shadow-md shadow-canina-blue/20 cursor-pointer"
|
||
>
|
||
<span>{getText('advisor_next_history', "سوابق پزشکی")}</span>
|
||
<ChevronLeft className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
|
||
{step === 4 && (
|
||
<motion.div
|
||
key="step4"
|
||
initial={{ opacity: 0, x: 20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
exit={{ opacity: 0, x: -20 }}
|
||
className="space-y-5 max-w-2xl mx-auto"
|
||
>
|
||
<div className="text-center">
|
||
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1 font-vazir tracking-tight">{getText('advisor_step4_history_title', "گام چهارم: سوابق پزشکی")}</h3>
|
||
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step4_history_desc', "در صورت وجود سابقه جراحی، بارداری یا نارسایی، آن را مشخص کنید")}</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2.5 max-h-[50vh] sm:max-h-none overflow-y-auto pr-1">
|
||
{MEDICAL_HISTORIES.map((opt) => (
|
||
<button
|
||
key={opt.id}
|
||
type="button"
|
||
onClick={() => toggleMedicalHistory(opt.condition)}
|
||
className={`flex items-center gap-3 p-3.5 rounded-xl border transition-all text-right cursor-pointer ${medicalConditions.includes(opt.condition) ? 'border-canina-blue bg-canina-blue/5 shadow-xs text-canina-blue' : 'border-medical-gray-200 bg-white text-medical-gray-700 hover:border-canina-blue/40'}`}
|
||
>
|
||
<div className={`w-5 h-5 rounded-md flex items-center justify-center border shrink-0 ${medicalConditions.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 bg-white'}`}>
|
||
{medicalConditions.includes(opt.condition) && <Check className="w-3.5 h-3.5 stroke-[3]" />}
|
||
</div>
|
||
<span className="text-xs sm:text-sm font-bold font-vazir leading-tight">{opt.label}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
|
||
<button
|
||
type="button"
|
||
onClick={handleBack}
|
||
className="whitespace-nowrap bg-white border-2 border-medical-gray-300 text-medical-gray-700 px-4 sm:px-6 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-medical-gray-50 transition-all flex items-center gap-1.5 cursor-pointer"
|
||
>
|
||
<ChevronRight className="w-4 h-4" />
|
||
<span>{getText('advisor_back', "قبلی")}</span>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={handleSubmit}
|
||
className="whitespace-nowrap bg-emerald-600 text-white px-6 sm:px-9 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-emerald-700 hover:shadow-lg transition-all flex items-center gap-1.5 shadow-md shadow-emerald-600/20 cursor-pointer"
|
||
>
|
||
<span>{getText('advisor_complete', "صدور شناسنامه و پیشنهاد")}</span>
|
||
<ChevronLeft className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|