fix(database): fix baseSlug format in products seed to populate product symptoms successfully + format cleanup in ArchivePage
This commit is contained in:
parent
6dafe86344
commit
717a61a225
@ -139,10 +139,6 @@ async function main() {
|
|||||||
const cleanIngredients = item.ingredients || '';
|
const cleanIngredients = item.ingredients || '';
|
||||||
const cleanDosage = item.usage_and_dosage || '';
|
const cleanDosage = item.usage_and_dosage || '';
|
||||||
const suitableFor = determineSuitableFor(baseName, cleanDesc);
|
const suitableFor = determineSuitableFor(baseName, cleanDesc);
|
||||||
const baseSlug = slugify(baseName);
|
|
||||||
const categorySlug = item.category_slug || 'supplements';
|
|
||||||
const category = await findOrCreateCategory(categorySlug);
|
|
||||||
|
|
||||||
// Split baseName into nameFa and nameEn
|
// Split baseName into nameFa and nameEn
|
||||||
let baseNameFa = baseName;
|
let baseNameFa = baseName;
|
||||||
let baseNameEn = baseName;
|
let baseNameEn = baseName;
|
||||||
@ -152,6 +148,10 @@ async function main() {
|
|||||||
baseNameFa = matchName[2].trim();
|
baseNameFa = matchName[2].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseSlug = slugify(baseNameEn);
|
||||||
|
const categorySlug = item.category_slug || 'supplements';
|
||||||
|
const category = await findOrCreateCategory(categorySlug);
|
||||||
|
|
||||||
for (const variant of item.variants) {
|
for (const variant of item.variants) {
|
||||||
const artNo = variant.art_no;
|
const artNo = variant.art_no;
|
||||||
const barcode = variant.barcode;
|
const barcode = variant.barcode;
|
||||||
|
|||||||
@ -6,16 +6,16 @@ import { productService } from "../lib/services/productService";
|
|||||||
import { motion, AnimatePresence } from "motion/react";
|
import { motion, AnimatePresence } from "motion/react";
|
||||||
import { ProductCardSkeleton } from "./Skeleton";
|
import { ProductCardSkeleton } from "./Skeleton";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
Filter,
|
Filter,
|
||||||
Dog,
|
Dog,
|
||||||
Cat,
|
Cat,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Activity,
|
Activity,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Stethoscope,
|
Stethoscope,
|
||||||
HeartPulse,
|
HeartPulse,
|
||||||
Heart,
|
Heart,
|
||||||
@ -41,7 +41,7 @@ const CATEGORY_MAP: Record<string, { category?: string; query?: string; symptoms
|
|||||||
"immune": { category: "immune" },
|
"immune": { category: "immune" },
|
||||||
"energy": { category: "energy" },
|
"energy": { category: "energy" },
|
||||||
"special-care": { category: "special-care" },
|
"special-care": { category: "special-care" },
|
||||||
|
|
||||||
// Persian Fallbacks (for robust matching)
|
// Persian Fallbacks (for robust matching)
|
||||||
"مفاصل و استخوان": { category: "joints" },
|
"مفاصل و استخوان": { category: "joints" },
|
||||||
"تقویت سیستم ایمنی و گوارش": { category: "immune" },
|
"تقویت سیستم ایمنی و گوارش": { category: "immune" },
|
||||||
@ -52,7 +52,7 @@ const CATEGORY_MAP: Record<string, { category?: string; query?: string; symptoms
|
|||||||
"آرتروز سگهای پیر": { category: "joints", symptoms: ["درد مفاصل", "سختی در بلند شدن", "لنگیدن"] },
|
"آرتروز سگهای پیر": { category: "joints", symptoms: ["درد مفاصل", "سختی در بلند شدن", "لنگیدن"] },
|
||||||
"رشد استخوانی تولهسگ": { category: "joints", symptoms: ["رشد سریع تولهسگ"] },
|
"رشد استخوانی تولهسگ": { category: "joints", symptoms: ["رشد سریع تولهسگ"] },
|
||||||
"تقویت رباط و تاندون": { category: "joints", query: "تاندون" },
|
"تقویت رباط و تاندون": { category: "joints", query: "تاندون" },
|
||||||
|
|
||||||
"پیشگیری از بیماری": { category: "immune", symptoms: ["ضعف بعد از بیماری"] },
|
"پیشگیری از بیماری": { category: "immune", symptoms: ["ضعف بعد از بیماری"] },
|
||||||
"رفع اسهال و یبوست": { category: "immune", symptoms: ["اسهال", "یبوست", "اسهال مزمن"] },
|
"رفع اسهال و یبوست": { category: "immune", symptoms: ["اسهال", "یبوست", "اسهال مزمن"] },
|
||||||
"پروبیوتیکها": { category: "immune", query: "فیبر" },
|
"پروبیوتیکها": { category: "immune", query: "فیبر" },
|
||||||
@ -81,14 +81,14 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
|
|||||||
|
|
||||||
const compatibility = useMemo(() => {
|
const compatibility = useMemo(() => {
|
||||||
if (!activePet) return null;
|
if (!activePet) return null;
|
||||||
|
|
||||||
const sameSpecies = product.suitableFor === activePet.type || product.suitableFor === "هر دو";
|
const sameSpecies = product.suitableFor === activePet.type || product.suitableFor === "هر دو";
|
||||||
const helpsSymptom = (activePet.medicalConditions || []).some(s => product.symptoms.includes(s));
|
const helpsSymptom = (activePet.medicalConditions || []).some(s => product.symptoms.includes(s));
|
||||||
|
|
||||||
if (!sameSpecies) return { type: 'alert', text: `مخصوص ${product.suitableFor}` };
|
if (!sameSpecies) return { type: 'alert', text: `مخصوص ${product.suitableFor}` };
|
||||||
if (helpsSymptom) return { type: 'success', text: `توصیه شده برای راهکار ${activePet.name}` };
|
if (helpsSymptom) return { type: 'success', text: `توصیه شده برای راهکار ${activePet.name}` };
|
||||||
if (sameSpecies) return { type: 'neutral', text: `مناسب برای ${activePet.name}` };
|
if (sameSpecies) return { type: 'neutral', text: `مناسب برای ${activePet.name}` };
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [product, activePet]);
|
}, [product, activePet]);
|
||||||
|
|
||||||
@ -105,30 +105,29 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
|
|||||||
{product.specialBadge}
|
{product.specialBadge}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{compatibility && (
|
{compatibility && (
|
||||||
<div className={`absolute top-14 right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${
|
<div className={`absolute top-14 right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${compatibility.type === 'alert' ? 'bg-amber-100 text-amber-700' :
|
||||||
compatibility.type === 'alert' ? 'bg-amber-100 text-amber-700' :
|
compatibility.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-medical-gray-100 text-medical-gray-600'
|
||||||
compatibility.type === 'success' ? 'bg-green-100 text-green-700' : 'bg-medical-gray-100 text-medical-gray-600'
|
}`}>
|
||||||
}`}>
|
|
||||||
{compatibility.type === 'alert' ? <AlertCircle className="w-2.5 h-2.5" /> : compatibility.type === 'success' ? <Heart className="w-2.5 h-2.5" /> : <Sparkles className="w-2.5 h-2.5" />}
|
{compatibility.type === 'alert' ? <AlertCircle className="w-2.5 h-2.5" /> : compatibility.type === 'success' ? <Heart className="w-2.5 h-2.5" /> : <Sparkles className="w-2.5 h-2.5" />}
|
||||||
{compatibility.text}
|
{compatibility.text}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="aspect-square bg-medical-gray-50 flex items-center justify-center p-8 overflow-hidden relative">
|
<div className="aspect-square bg-medical-gray-50 flex items-center justify-center p-8 overflow-hidden relative">
|
||||||
<SafeImage src={product.image} alt={product.name} className="w-full h-full group-hover:scale-110 transition-transform duration-700" imgClassName="object-contain" />
|
<SafeImage src={product.image} alt={product.name} className="w-full h-full group-hover:scale-110 transition-transform duration-700" imgClassName="object-contain" />
|
||||||
<div className="absolute bottom-4 left-4 flex gap-2">
|
<div className="absolute bottom-4 left-4 flex gap-2">
|
||||||
{(product.suitableFor === "سگ" || product.suitableFor === "هر دو") && (
|
{(product.suitableFor === "سگ" || product.suitableFor === "هر دو") && (
|
||||||
<div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm">
|
<div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm">
|
||||||
<Dog className="w-4 h-4" />
|
<Dog className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(product.suitableFor === "گربه" || product.suitableFor === "هر دو") && (
|
{(product.suitableFor === "گربه" || product.suitableFor === "هر دو") && (
|
||||||
<div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm">
|
<div className="w-7 h-7 bg-white rounded-lg flex items-center justify-center text-medical-gray-400 border border-medical-gray-100 shadow-sm">
|
||||||
<Cat className="w-4 h-4" />
|
<Cat className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -144,24 +143,24 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-1 mb-6">
|
<div className="flex flex-wrap gap-1">
|
||||||
{product.symptoms.slice(0, 2).map((s, i) => (
|
{product.symptoms.slice(0, 2).map((s, i) => (
|
||||||
<span key={i} className="text-[9px] font-bold bg-medical-gray-100 text-medical-gray-500 px-2 py-0.5 rounded-md">
|
<span key={i} className="text-[9px] font-bold bg-medical-gray-100 text-medical-gray-500 px-2 py-0.5 rounded-md">
|
||||||
{s}
|
{s}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-auto flex items-center justify-between pt-4 border-t border-medical-gray-50">
|
<div className="mt-auto flex items-center justify-between pt-4 border-t border-medical-gray-50">
|
||||||
<span className="font-black text-medical-gray-900 font-vazir">{product.price}</span>
|
<span className="font-black text-medical-gray-900 font-vazir">{product.price}</span>
|
||||||
<button
|
<button
|
||||||
onClick={handleAddToCart}
|
onClick={handleAddToCart}
|
||||||
disabled={isAdding}
|
disabled={isAdding}
|
||||||
className="w-10 h-10 rounded-xl bg-medical-gray-50 text-canina-blue flex items-center justify-center group-hover:bg-canina-blue group-hover:text-white transition-all disabled:opacity-50"
|
className="w-10 h-10 rounded-xl bg-medical-gray-50 text-canina-blue flex items-center justify-center group-hover:bg-canina-blue group-hover:text-white transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isAdding ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}
|
{isAdding ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -170,12 +169,12 @@ const ArchiveProductCard: React.FC<{ product: Product, onClick: (p: Product) =>
|
|||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
export default function ArchivePage({
|
export default function ArchivePage({
|
||||||
initialCategory = "all",
|
initialCategory = "all",
|
||||||
initialSearch = "",
|
initialSearch = "",
|
||||||
initialPetType = "all",
|
initialPetType = "all",
|
||||||
initialSymptoms = ""
|
initialSymptoms = ""
|
||||||
}: {
|
}: {
|
||||||
initialCategory?: string,
|
initialCategory?: string,
|
||||||
initialSearch?: string,
|
initialSearch?: string,
|
||||||
initialPetType?: string,
|
initialPetType?: string,
|
||||||
@ -228,7 +227,7 @@ export default function ArchivePage({
|
|||||||
// Normalize Input (Trim and lowercase)
|
// Normalize Input (Trim and lowercase)
|
||||||
const normSearch = (initialSearch || "").trim().toLowerCase();
|
const normSearch = (initialSearch || "").trim().toLowerCase();
|
||||||
const normCat = initialCategory.trim().toLowerCase();
|
const normCat = initialCategory.trim().toLowerCase();
|
||||||
|
|
||||||
const mapKey = normSearch || normCat;
|
const mapKey = normSearch || normCat;
|
||||||
const mapping = CATEGORY_MAP[mapKey];
|
const mapping = CATEGORY_MAP[mapKey];
|
||||||
|
|
||||||
@ -237,7 +236,7 @@ export default function ArchivePage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
|
|
||||||
// Reset ALL other filters when a new category/solution is selected from menu
|
// Reset ALL other filters when a new category/solution is selected from menu
|
||||||
setSelectedPet("all");
|
setSelectedPet("all");
|
||||||
setActiveSymptoms([]);
|
setActiveSymptoms([]);
|
||||||
@ -252,7 +251,7 @@ export default function ArchivePage({
|
|||||||
} else {
|
} else {
|
||||||
setSelectedCategory(initialCategory);
|
setSelectedCategory(initialCategory);
|
||||||
setSearchQuery(initialSearch);
|
setSearchQuery(initialSearch);
|
||||||
|
|
||||||
if (initialSearch && symptoms.includes(initialSearch)) {
|
if (initialSearch && symptoms.includes(initialSearch)) {
|
||||||
setActiveSymptoms([initialSearch]);
|
setActiveSymptoms([initialSearch]);
|
||||||
}
|
}
|
||||||
@ -261,7 +260,7 @@ export default function ArchivePage({
|
|||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}, 400);
|
}, 400);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [initialCategory, initialSearch, symptoms]);
|
}, [initialCategory, initialSearch, symptoms]);
|
||||||
|
|
||||||
@ -279,34 +278,34 @@ export default function ArchivePage({
|
|||||||
}, [selectedCategory, selectedPet, searchQuery, activeSymptoms]);
|
}, [selectedCategory, selectedPet, searchQuery, activeSymptoms]);
|
||||||
|
|
||||||
const fetchProducts = async (resetPage = true) => {
|
const fetchProducts = async (resetPage = true) => {
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
try {
|
try {
|
||||||
const res = await productService.getProducts({
|
const res = await productService.getProducts({
|
||||||
category: selectedCategory,
|
category: selectedCategory,
|
||||||
petType: selectedPet,
|
petType: selectedPet,
|
||||||
query: searchQuery,
|
query: searchQuery,
|
||||||
// Send the first active symptom to the backend for server-side filtering
|
// Send the first active symptom to the backend for server-side filtering
|
||||||
symptom: activeSymptoms.length === 1 ? activeSymptoms[0] : undefined,
|
symptom: activeSymptoms.length === 1 ? activeSymptoms[0] : undefined,
|
||||||
page: resetPage ? 1 : page,
|
page: resetPage ? 1 : page,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = res.data;
|
let result = res.data;
|
||||||
// If multiple symptoms selected, do client-side intersection (rare case)
|
// If multiple symptoms selected, do client-side intersection (rare case)
|
||||||
if (activeSymptoms.length > 1) {
|
if (activeSymptoms.length > 1) {
|
||||||
result = result.filter(p => activeSymptoms.every(s => p.symptoms.includes(s)));
|
result = result.filter(p => activeSymptoms.every(s => p.symptoms.includes(s)));
|
||||||
}
|
|
||||||
|
|
||||||
setFilteredProducts(result);
|
|
||||||
setMeta(res.meta);
|
|
||||||
if (resetPage) setPage(1);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to fetch products:", error);
|
|
||||||
toast.error("خطا در دریافت لیست محصولات از سرور. لطفاً صفحه را رفرش کنید.");
|
|
||||||
} finally {
|
|
||||||
setIsUpdating(false);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
setFilteredProducts(result);
|
||||||
|
setMeta(res.meta);
|
||||||
|
if (resetPage) setPage(1);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch products:", error);
|
||||||
|
toast.error("خطا در دریافت لیست محصولات از سرور. لطفاً صفحه را رفرش کنید.");
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProducts(true);
|
fetchProducts(true);
|
||||||
@ -342,17 +341,17 @@ export default function ArchivePage({
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-medical-gray-50 pb-20 px-4 pt-10 font-vazir" dir="rtl">
|
<div className="min-h-screen bg-medical-gray-50 pb-20 px-4 pt-10 font-vazir" dir="rtl">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
|
|
||||||
{/* Mobile Navigation & Breadcrumbs */}
|
{/* Mobile Navigation & Breadcrumbs */}
|
||||||
<div className="lg:hidden mb-10 space-y-4">
|
<div className="lg:hidden mb-10 space-y-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
className="flex items-center gap-2 text-medical-gray-500 font-bold hover:text-canina-blue transition-colors"
|
className="flex items-center gap-2 text-medical-gray-500 font-bold hover:text-canina-blue transition-colors"
|
||||||
>
|
>
|
||||||
<ChevronRight className="w-5 h-5 font-vazir" />
|
<ChevronRight className="w-5 h-5 font-vazir" />
|
||||||
<span>بازگشت</span>
|
<span>بازگشت</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest overflow-x-auto whitespace-nowrap pb-2">
|
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest overflow-x-auto whitespace-nowrap pb-2">
|
||||||
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>کانینا</span>
|
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>کانینا</span>
|
||||||
<ChevronLeft className="w-2.5 h-2.5" />
|
<ChevronLeft className="w-2.5 h-2.5" />
|
||||||
@ -361,174 +360,173 @@ export default function ArchivePage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop Breadcrumbs */}
|
{/* Desktop Breadcrumbs */}
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-10">
|
<div className="flex items-center gap-2 text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-10">
|
||||||
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>خانه</span>
|
<span className="cursor-pointer hover:text-canina-blue" onClick={() => router.push('/')}>خانه</span>
|
||||||
<ChevronLeft className="w-2.5 h-2.5" />
|
<ChevronLeft className="w-2.5 h-2.5" />
|
||||||
<span className="cursor-pointer hover:text-canina-blue" onClick={() => { setSelectedCategory("all"); }}>کاتالوگ</span>
|
<span className="cursor-pointer hover:text-canina-blue" onClick={() => { setSelectedCategory("all"); }}>کاتالوگ</span>
|
||||||
{selectedCategory !== "all" && (
|
{selectedCategory !== "all" && (
|
||||||
<>
|
<>
|
||||||
<ChevronLeft className="w-2.5 h-2.5" />
|
<ChevronLeft className="w-2.5 h-2.5" />
|
||||||
<span className="text-canina-blue">{CATEGORY_LABELS[selectedCategory] || selectedCategory}</span>
|
<span className="text-canina-blue">{CATEGORY_LABELS[selectedCategory] || selectedCategory}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
|
|
||||||
{/* Sidebar Filters */}
|
{/* Sidebar Filters */}
|
||||||
<aside className="lg:w-72 flex-shrink-0 space-y-8">
|
<aside className="lg:w-72 flex-shrink-0 space-y-8">
|
||||||
<div className="bg-white rounded-[2rem] p-6 border border-medical-gray-200 shadow-sm">
|
<div className="bg-white rounded-[2rem] p-6 border border-medical-gray-200 shadow-sm">
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
<Filter className="w-5 h-5 text-canina-blue" />
|
<Filter className="w-5 h-5 text-canina-blue" />
|
||||||
<h3 className="font-black text-medical-gray-900">فیلترهای تخصصی</h3>
|
<h3 className="font-black text-medical-gray-900">فیلترهای تخصصی</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Pet Type */}
|
||||||
|
<div>
|
||||||
|
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">نوع پت</span>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
{[
|
||||||
|
{ id: "all", label: "هر دو", icon: <Activity className="w-3 h-3" /> },
|
||||||
|
{ id: "سگ", label: "سگ", icon: <Dog className="w-3 h-3" /> },
|
||||||
|
{ id: "گربه", label: "گربه", icon: <Cat className="w-3 h-3" /> },
|
||||||
|
].map(pet => (
|
||||||
|
<button
|
||||||
|
key={pet.id}
|
||||||
|
onClick={() => setSelectedPet(pet.id as any)}
|
||||||
|
className={`py-2 rounded-xl text-[10px] font-black border transition-all flex flex-col items-center gap-1 ${selectedPet === pet.id ? 'bg-canina-blue border-canina-blue text-white shadow-lg shadow-canina-blue/20' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}
|
||||||
|
>
|
||||||
|
{pet.icon}
|
||||||
|
{pet.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
{/* Solutions */}
|
||||||
{/* Pet Type */}
|
<div>
|
||||||
<div>
|
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">راهکار درمانی</span>
|
||||||
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">نوع پت</span>
|
<div className="flex flex-col gap-2">
|
||||||
<div className="grid grid-cols-3 gap-2">
|
{categories.map(cat => (
|
||||||
{[
|
<button
|
||||||
{ id: "all", label: "هر دو", icon: <Activity className="w-3 h-3" /> },
|
key={cat.id}
|
||||||
{ id: "سگ", label: "سگ", icon: <Dog className="w-3 h-3" /> },
|
onClick={() => setSelectedCategory(cat.id)}
|
||||||
{ id: "گربه", label: "گربه", icon: <Cat className="w-3 h-3" /> },
|
className={`flex items-center gap-3 px-4 py-3 rounded-2xl text-xs font-bold transition-all border ${selectedCategory === cat.id ? 'bg-canina-blue border-canina-blue text-white' : 'bg-white border-medical-gray-100 text-medical-gray-600 hover:border-canina-blue/30'}`}
|
||||||
].map(pet => (
|
>
|
||||||
<button
|
{cat.icon}
|
||||||
key={pet.id}
|
{cat.label}
|
||||||
onClick={() => setSelectedPet(pet.id as any)}
|
</button>
|
||||||
className={`py-2 rounded-xl text-[10px] font-black border transition-all flex flex-col items-center gap-1 ${selectedPet === pet.id ? 'bg-canina-blue border-canina-blue text-white shadow-lg shadow-canina-blue/20' : 'bg-white border-medical-gray-200 text-medical-gray-400'}`}
|
))}
|
||||||
>
|
</div>
|
||||||
{pet.icon}
|
|
||||||
{pet.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Solutions */}
|
|
||||||
<div>
|
|
||||||
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block mb-3">راهکار درمانی</span>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{categories.map(cat => (
|
|
||||||
<button
|
|
||||||
key={cat.id}
|
|
||||||
onClick={() => setSelectedCategory(cat.id)}
|
|
||||||
className={`flex items-center gap-3 px-4 py-3 rounded-2xl text-xs font-bold transition-all border ${selectedCategory === cat.id ? 'bg-canina-blue border-canina-blue text-white' : 'bg-white border-medical-gray-100 text-medical-gray-600 hover:border-canina-blue/30'}`}
|
|
||||||
>
|
|
||||||
{cat.icon}
|
|
||||||
{cat.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Symptoms Pill Tags */}
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">جستجو بر اساس علائم</span>
|
|
||||||
{activeSymptoms.length > 0 && (
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveSymptoms([])}
|
|
||||||
className="text-[10px] font-bold text-canina-blue hover:underline"
|
|
||||||
>
|
|
||||||
پاک کردن
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-1.5 max-h-52 overflow-y-auto scrollbar-thin scrollbar-thumb-medical-gray-200 scrollbar-track-transparent pr-0.5">
|
|
||||||
{symptoms.length === 0 ? (
|
|
||||||
<span className="text-[10px] text-medical-gray-400">در حال بارگذاری...</span>
|
|
||||||
) : symptoms.map(s => (
|
|
||||||
<button
|
|
||||||
key={s}
|
|
||||||
onClick={() => toggleSymptom(s)}
|
|
||||||
className={`px-2.5 py-1 rounded-full text-[10px] font-bold transition-all border whitespace-nowrap ${
|
|
||||||
activeSymptoms.includes(s)
|
|
||||||
? 'bg-canina-blue border-canina-blue text-white shadow-sm shadow-canina-blue/30'
|
|
||||||
: 'bg-medical-gray-50 border-medical-gray-200 text-medical-gray-500 hover:border-canina-blue/40 hover:text-canina-blue'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{s}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Symptoms Pill Tags */}
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest">جستجو بر اساس علائم</span>
|
||||||
|
{activeSymptoms.length > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveSymptoms([])}
|
||||||
|
className="text-[10px] font-bold text-canina-blue hover:underline"
|
||||||
|
>
|
||||||
|
پاک کردن
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5 max-h-52 overflow-y-auto scrollbar-thin scrollbar-thumb-medical-gray-200 scrollbar-track-transparent pr-0.5">
|
||||||
|
{symptoms.length === 0 ? (
|
||||||
|
<span className="text-[10px] text-medical-gray-400">در حال بارگذاری...</span>
|
||||||
|
) : symptoms.map(s => (
|
||||||
|
<button
|
||||||
|
key={s}
|
||||||
|
onClick={() => toggleSymptom(s)}
|
||||||
|
className={`px-2.5 py-1 rounded-full text-[10px] font-bold transition-all border whitespace-nowrap ${activeSymptoms.includes(s)
|
||||||
|
? 'bg-canina-blue border-canina-blue text-white shadow-sm shadow-canina-blue/30'
|
||||||
|
: 'bg-medical-gray-50 border-medical-gray-200 text-medical-gray-500 hover:border-canina-blue/40 hover:text-canina-blue'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Promo Card */}
|
{/* Promo Card */}
|
||||||
<div className="bg-medical-gray-900 rounded-[2rem] p-6 text-white relative overflow-hidden group">
|
<div className="bg-medical-gray-900 rounded-[2rem] p-6 text-white relative overflow-hidden group">
|
||||||
<div className="absolute -top-10 -left-10 w-32 h-32 bg-canina-blue rounded-full blur-3xl opacity-20 group-hover:opacity-40 transition-opacity" />
|
<div className="absolute -top-10 -left-10 w-32 h-32 bg-canina-blue rounded-full blur-3xl opacity-20 group-hover:opacity-40 transition-opacity" />
|
||||||
<h4 className="text-lg font-black italic mb-4 relative z-10 underline decoration-canina-blue underline-offset-8">مشاوره رایگان</h4>
|
<h4 className="text-lg font-black italic mb-4 relative z-10 underline decoration-canina-blue underline-offset-8">مشاوره رایگان</h4>
|
||||||
<p className="text-xs text-white/60 leading-relaxed mb-6 relative z-10">اگر نمیدانید کدام ترکیب برای پت شما مناسب است، همین حالا با دامپزشکان ما تماس بگیرید.</p>
|
<p className="text-xs text-white/60 leading-relaxed mb-6 relative z-10">اگر نمیدانید کدام ترکیب برای پت شما مناسب است، همین حالا با دامپزشکان ما تماس بگیرید.</p>
|
||||||
<a href="tel:0218888" className="w-full bg-white text-medical-gray-900 py-3 rounded-xl text-xs font-black hover:scale-105 transition-transform relative z-10 flex items-center justify-center">شماره تماس: ۰۲۱-۸۸۸۸</a>
|
<a href="tel:0218888" className="w-full bg-white text-medical-gray-900 py-3 rounded-xl text-xs font-black hover:scale-105 transition-transform relative z-10 flex items-center justify-center">شماره تماس: ۰۲۱-۸۸۸۸</a>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<main className="flex-1 relative">
|
<main className="flex-1 relative">
|
||||||
<div className="flex flex-col md:flex-row items-center justify-between mb-8 gap-4">
|
<div className="flex flex-col md:flex-row items-center justify-between mb-8 gap-4">
|
||||||
<h2 className="text-3xl font-black text-medical-gray-900">کاتولوگ دارویی <span className="text-canina-blue">Canina</span></h2>
|
<h2 className="text-3xl font-black text-medical-gray-900">کاتولوگ دارویی <span className="text-canina-blue">Canina</span></h2>
|
||||||
<div className="relative w-full md:w-80">
|
<div className="relative w-full md:w-80">
|
||||||
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-medical-gray-400" />
|
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-medical-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="جستجوی محصول..."
|
placeholder="جستجوی محصول..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="w-full bg-white border border-medical-gray-200 rounded-full py-3 pr-11 pl-4 text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
|
className="w-full bg-white border border-medical-gray-200 rounded-full py-3 pr-11 pl-4 text-sm focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{isUpdating ? (
|
{isUpdating ? (
|
||||||
<div key="skeleton" className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
|
<div key="skeleton" className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
|
||||||
{Array.from({ length: 8 }).map((_, i) => (
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
<ProductCardSkeleton key={i} />
|
<ProductCardSkeleton key={i} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : filteredProducts.length > 0 ? (
|
) : filteredProducts.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<div key="grid" className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
|
<div key="grid" className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
|
||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
{filteredProducts.map((p: Product) => (
|
{filteredProducts.map((p: Product) => (
|
||||||
<ArchiveProductCard key={p.id} product={p} onClick={(p) => router.push(`/shop/${p.slug || p.id}`)} />
|
<ArchiveProductCard key={p.id} product={p} onClick={(p) => router.push(`/shop/${p.slug || p.id}`)} />
|
||||||
))}
|
))}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
|
||||||
{page < meta.lastPage && (
|
|
||||||
<div className="flex justify-center mt-10">
|
|
||||||
<button
|
|
||||||
onClick={loadMore}
|
|
||||||
className="bg-canina-blue text-white px-8 py-3 rounded-xl font-black text-sm hover:scale-105 transition-transform shadow-lg shadow-canina-blue/20"
|
|
||||||
>
|
|
||||||
بارگذاری محصولات بیشتر
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{page < meta.lastPage && (
|
||||||
</>
|
<div className="flex justify-center mt-10">
|
||||||
) : (
|
<button
|
||||||
<div key="empty" className="bg-white rounded-[3rem] p-20 text-center border-2 border-dashed border-medical-gray-200 flex flex-col items-center">
|
onClick={loadMore}
|
||||||
<div className="w-20 h-20 bg-medical-gray-50 rounded-full flex items-center justify-center text-medical-gray-300 mb-6">
|
|
||||||
<Search className="w-10 h-10" />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-2xl font-black text-medical-gray-900 mb-2">محصولی یافت نشد!</h3>
|
|
||||||
<p className="text-medical-gray-400 max-w-sm mb-8 font-bold">با فیلترهای فعلی محصولی مطابق با نیاز شما پیدا نکردیم. لطفاً فیلترها را گستردهتر کنید یا دکمه ریست را بزنید.</p>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
router.push('/shop');
|
|
||||||
setSelectedCategory("all");
|
|
||||||
setSelectedPet("all");
|
|
||||||
setActiveSymptoms([]);
|
|
||||||
setSearchQuery("");
|
|
||||||
}}
|
|
||||||
className="bg-canina-blue text-white px-8 py-3 rounded-xl font-black text-sm hover:scale-105 transition-transform shadow-lg shadow-canina-blue/20"
|
className="bg-canina-blue text-white px-8 py-3 rounded-xl font-black text-sm hover:scale-105 transition-transform shadow-lg shadow-canina-blue/20"
|
||||||
>
|
>
|
||||||
پاک کردن همه فیلترها و نمایش همه
|
بارگذاری محصولات بیشتر
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div key="empty" className="bg-white rounded-[3rem] p-20 text-center border-2 border-dashed border-medical-gray-200 flex flex-col items-center">
|
||||||
|
<div className="w-20 h-20 bg-medical-gray-50 rounded-full flex items-center justify-center text-medical-gray-300 mb-6">
|
||||||
|
<Search className="w-10 h-10" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-black text-medical-gray-900 mb-2">محصولی یافت نشد!</h3>
|
||||||
|
<p className="text-medical-gray-400 max-w-sm mb-8 font-bold">با فیلترهای فعلی محصولی مطابق با نیاز شما پیدا نکردیم. لطفاً فیلترها را گستردهتر کنید یا دکمه ریست را بزنید.</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/shop');
|
||||||
|
setSelectedCategory("all");
|
||||||
|
setSelectedPet("all");
|
||||||
|
setActiveSymptoms([]);
|
||||||
|
setSearchQuery("");
|
||||||
|
}}
|
||||||
|
className="bg-canina-blue text-white px-8 py-3 rounded-xl font-black text-sm hover:scale-105 transition-transform shadow-lg shadow-canina-blue/20"
|
||||||
|
>
|
||||||
|
پاک کردن همه فیلترها و نمایش همه
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user