348 lines
20 KiB
TypeScript
348 lines
20 KiB
TypeScript
"use client";
|
||
import { useState, useEffect, useMemo } from "react";
|
||
import { Product, IngredientInfo, PRODUCTS, INGREDIENTS_WIKI } from "../lib/data/products";
|
||
import { motion } from "motion/react";
|
||
import { FlaskConical, CheckCircle2, ChevronRight, ChevronLeft, Beaker, Search } from "lucide-react";
|
||
import { productService } from "../lib/services/productService";
|
||
|
||
import Link from "next/link";
|
||
import Image from "next/image";
|
||
import { useRouter, useSearchParams } from 'next/navigation';
|
||
|
||
import api from "../lib/services/api";
|
||
import { Ingredient } from "../lib/types";
|
||
|
||
// Keywords map for connecting ingredients to products
|
||
const INGREDIENT_MATCH_KEYWORDS: Record<string, string[]> = {
|
||
"green-mussel": ["صدف", "mussel", "gag", "perna", "canhydrox", "velox", "flexan", "gelenkenergie"],
|
||
"hydroxyapatite": ["هیدروکسی", "آپاتیت", "hydroxyapatite", "canhydrox"],
|
||
"collagen": ["کلاژن", "collagen", "flexan"],
|
||
"colostrum": ["آغوز", "colostrum", "immun-booster", "ایمون بوستر"],
|
||
"silver": ["نقره", "میکروسیلور", "silver", "zahngel", "ژل دندان", "دندان"],
|
||
"peat-extract": ["پیت", "moor", "moortranke", "هومیک", "عصاره پیت", "گوارش"],
|
||
"salmon-oil": ["سالمون", "ماهی", "salmon", "lachs", "امگا", "marine", "welpenmilch", "katzenmilch"],
|
||
"black-cumin": ["سیاه دانه", "سیاهدانه", "زیره", "cumin", "schwarz", "kummel"],
|
||
"eggshell": ["پوسته", "تخم مرغ", "تخممرغ", "eggshell", "eierschalen"],
|
||
"biotin": ["بیوتین", "biotin", "vitamin h", "ویتامین h"],
|
||
"taurin": ["تورین", "taurin", "taurine", "katzenmilch"],
|
||
"seaweed": ["جلبک", "seaweed", "seealgen", "seelgen", "ascophyllum", "canhydrox"],
|
||
"l-carnitine": ["کارنیتین", "carnitine", "herz-vital", "هرز ویتال", "قلب"],
|
||
"hawthorn": ["زالزالک", "hawthorn", "crataegus", "herz-vital", "هرز ویتال", "قلب"],
|
||
"brewer-yeast": ["مخمر", "yeast", "hefe", "b-complex", "canhydrox", "mineral-tabs", "vitamin-tabs", "taurin", "hefe-tabletten"],
|
||
"bovine-blood": ["خون", "rinderblut", "blood", "آهن", "هموگلوبین", "سرم"],
|
||
"bovine-fat": ["چربی", "rinderfett", "fat", "کالری", "گوشت گاو", "energy-gel", "immun-booster", "katzenmilch", "welpenmilch"],
|
||
"willow-bark": ["بید", "willow", "salix", "arthro", "آرترو"],
|
||
"ginger": ["زنجبیل", "ginger", "zingiber", "arthro", "آرترو"],
|
||
"hyaluronic": ["هیالورونات", "hyaluron", "augenpflege", "چشم"],
|
||
"calendula-sea-buckthorn": ["کالاندولا", "سنجد", "خولان", "pfotenpflege", "پنجه", "همیشهبهار", "پوست"],
|
||
"dodecanoic": ["دودکانوئیک", "مارگوسا", "insect", "کک", "کنه", "حشرات", "ضد انگل", "پپت-پروتکت", "pet-protect", "اسپری"],
|
||
"calcium-carbonate": ["کربنات کلسیم", "کلسیم", "calcium-carbonat", "mineral-tabs", "welpenkalk", "gag", "vitamin-tabs", "ballaststoff"],
|
||
"vitamin-c": ["ویتامین c", "ویتامین سی", "ascorbic", "vitamin-c", "zahngel", "canhydrox"],
|
||
"vitamin-e": ["ویتامین e", "ویتامین ای", "tocopherol", "vitamin-e", "canhydrox", "immun-booster", "energy-gel", "gag", "arthro"],
|
||
"vitamin-d3": ["ویتامین d3", "ویتامین دی", "cholecalciferol", "d3", "immun-booster", "mineral-tabs", "vitamin-tabs", "welpenmilch"],
|
||
"dextrose": ["دکستروز", "dextrose", "گلوکز", "biotin-tabs", "herz-vital", "energy-gel"],
|
||
"marine-oils": ["گردو", "آرگان", "گل رز", "marine-ol", "olmischung", "روغن دریایی", "روغن"],
|
||
"apple-fiber": ["سیب", "پکتین", "فیبر", "ballaststoff", "fiber", "یونجه", "هویج", "نخود"],
|
||
"zinc": ["روی", "zinc", "کلات", "arthro", "seealgen", "biotin", "mineral-tabs"]
|
||
};
|
||
|
||
// Formatter to prevent duplicate English names like "آغوز (Colostrum) (Colostrum)"
|
||
function formatIngredientDisplayName(item: { nameFa?: string; scientificName?: string; nameEn?: string; name?: string; slug?: string }): string {
|
||
const rawName = item.nameFa || item.name || '';
|
||
const cleanFa = rawName.replace(/\s*\([^)]*\)\s*/g, ' ').replace(/\s+/g, ' ').trim();
|
||
const sub = (item.scientificName || item.nameEn || '').replace(/\s*\([^)]*\)\s*/g, ' ').replace(/\s+/g, ' ').trim();
|
||
|
||
if (!cleanFa) return sub || rawName || item.slug || '';
|
||
if (!sub || cleanFa.toLowerCase() === sub.toLowerCase() || /[\u0600-\u06FF]/.test(sub)) {
|
||
return cleanFa;
|
||
}
|
||
|
||
return `${cleanFa} (${sub})`;
|
||
}
|
||
|
||
export default function IngredientWiki() {
|
||
const router = useRouter();
|
||
const searchParams = useSearchParams();
|
||
const termParam = searchParams.get('term');
|
||
const [ingredientsWiki, setIngredientsWiki] = useState<IngredientInfo[]>(INGREDIENTS_WIKI);
|
||
const [products, setProducts] = useState<Product[]>(PRODUCTS);
|
||
const [searchQuery, setSearchQuery] = useState("");
|
||
|
||
useEffect(() => {
|
||
productService.getProducts({ limit: 999 })
|
||
.then(res => {
|
||
if (res?.data && res.data.length > 0) {
|
||
setProducts(res.data);
|
||
}
|
||
})
|
||
.catch(err => {
|
||
console.warn('[IngredientWiki] Using static fallback products:', err);
|
||
});
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
api.get('/ingredients')
|
||
.then(res => {
|
||
const rawList = Array.isArray(res.data) ? res.data : (res.data?.data || []);
|
||
if (rawList.length > 0) {
|
||
const apiList: IngredientInfo[] = rawList.map((item: Ingredient) => ({
|
||
id: item.slug || item.id,
|
||
name: formatIngredientDisplayName(item),
|
||
description: item.description || '',
|
||
benefits: Array.isArray(item.benefits) ? item.benefits : []
|
||
}));
|
||
setIngredientsWiki(apiList);
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.warn('[IngredientWiki] Using static INGREDIENTS_WIKI fallback:', err);
|
||
});
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (termParam && ingredientsWiki.length > 0) {
|
||
const el = document.getElementById(`wiki-${termParam}`);
|
||
if (el) {
|
||
setTimeout(() => {
|
||
const yOffset = -120;
|
||
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||
}, 200);
|
||
}
|
||
}
|
||
}, [termParam, ingredientsWiki]);
|
||
|
||
// Product matcher for each ingredient
|
||
const getProductsForIngredient = useMemo(() => {
|
||
return (ingId: string, ingName: string) => {
|
||
const cleanSlug = (ingId || '').replace(/-/g, ' ').toLowerCase();
|
||
const cleanFa = (ingName || '').replace(/\s*\([^)]*\)\s*/g, ' ').trim().toLowerCase();
|
||
const keywords = INGREDIENT_MATCH_KEYWORDS[ingId] || [];
|
||
|
||
return products.filter(p => {
|
||
const pText = [
|
||
p.name || '',
|
||
p.nameFa || '',
|
||
p.nameEn || '',
|
||
p.scientificTagline || '',
|
||
p.slug || '',
|
||
p.id || '',
|
||
p.description || '',
|
||
p.shortDescription || '',
|
||
p.category || '',
|
||
p.categorySlug || '',
|
||
...(p.main_ingredients || []),
|
||
...(p.benefitsList || []),
|
||
...(p.keyBenefits?.map(k => `${k.title} ${k.description}`) || []),
|
||
(p as any).ingredients || ''
|
||
].join(' ').toLowerCase();
|
||
|
||
if (cleanFa && pText.includes(cleanFa)) return true;
|
||
if (cleanSlug && pText.includes(cleanSlug)) return true;
|
||
|
||
return keywords.some(kw => pText.includes(kw.toLowerCase()));
|
||
});
|
||
};
|
||
}, [products]);
|
||
|
||
return (
|
||
<div className="bg-white py-12 px-4 overflow-hidden font-vazir" dir="rtl">
|
||
<div className="max-w-7xl mx-auto">
|
||
|
||
{/* Standard Breadcrumb Navigation & Integrated Back Button */}
|
||
<div className="flex items-center justify-between mb-8 pb-4 border-b border-medical-gray-200/60 font-vazir" dir="rtl">
|
||
<div className="flex items-center gap-2 text-xs font-bold text-medical-gray-400 whitespace-nowrap">
|
||
<span className="cursor-pointer hover:text-canina-blue transition-colors" onClick={() => router.push('/')}>خانه</span>
|
||
<ChevronLeft className="w-3 h-3 text-medical-gray-300" />
|
||
<span className="text-canina-blue font-black">دانشنامه مواد نایاب و ارگانیک</span>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => router.push('/')}
|
||
className="px-3.5 py-1.5 bg-white border border-medical-gray-200 rounded-lg text-medical-gray-600 shadow-xs flex items-center gap-1.5 font-bold text-xs hover:border-canina-blue hover:text-canina-blue transition-all whitespace-nowrap cursor-pointer"
|
||
>
|
||
<span>بازگشت</span>
|
||
<ChevronLeft className="w-3.5 h-3.5" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex flex-col items-center text-center mb-20">
|
||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-canina-blue/5 border border-canina-blue/10 rounded-full text-canina-blue text-xs font-black uppercase tracking-widest mb-6">
|
||
<FlaskConical className="w-4 h-4" />
|
||
علم در خدمت کیفیت
|
||
</div>
|
||
<h2 className="text-4xl lg:text-6xl font-black text-medical-gray-900 leading-tight md:max-w-4xl">
|
||
دانشنامه مواد <span className="text-canina-blue italic">نایاب و ارگانیک</span> کنینا
|
||
</h2>
|
||
<p className="mt-6 text-lg text-medical-gray-500 max-w-2xl leading-relaxed">
|
||
تمامی ترکیبات فعال و مکملهای به کار رفته در محصولات دارویی و مراقبتی Canina Pharma آلمان، دارای بالاترین گرید بیولوژیک و فارماکوپه اروپا میباشند.
|
||
</p>
|
||
<div className="relative w-full max-w-md mt-8">
|
||
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-medical-gray-400" />
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی ماده مؤثره (مثلاً: صدف لبسبز، آغوز، بیوتین)..."
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
className="w-full bg-medical-gray-50 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>
|
||
|
||
{/* Sugar-Free & Gluten-Free Medical Standards Banner */}
|
||
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||
<div className="px-4 py-2 bg-emerald-50 border border-emerald-200 text-emerald-800 rounded-2xl text-xs font-black flex items-center gap-1.5 shadow-xs">
|
||
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
|
||
<span>۱۰۰٪ فاقد قند افزوده (Sugar-Free)</span>
|
||
</div>
|
||
<div className="px-4 py-2 bg-blue-50 border border-blue-200 text-blue-800 rounded-2xl text-xs font-black flex items-center gap-1.5 shadow-xs">
|
||
<CheckCircle2 className="w-4 h-4 text-blue-600" />
|
||
<span>مطابق استانداردهای دارویی و بالینی آلمان</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{(() => {
|
||
const filtered = ingredientsWiki.filter(ing => {
|
||
if (!searchQuery.trim()) return true;
|
||
const q = searchQuery.trim().toLowerCase();
|
||
return ing.name.toLowerCase().includes(q) || ing.description.toLowerCase().includes(q);
|
||
});
|
||
|
||
if (filtered.length === 0) {
|
||
return (
|
||
<div className="text-center py-20">
|
||
<Search className="w-12 h-12 text-medical-gray-300 mx-auto mb-4" />
|
||
<h3 className="text-xl font-black text-medical-gray-900 mb-2">مادهای یافت نشد</h3>
|
||
<p className="text-medical-gray-400 font-bold">با عبارت جستجو شده ماده مؤثرهای مطابقت ندارد.</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-24">
|
||
{filtered.map((ing, idx) => {
|
||
const hasIngredient = getProductsForIngredient(ing.id, ing.name);
|
||
const isTargeted = termParam === ing.id;
|
||
|
||
return (
|
||
<div
|
||
key={ing.id}
|
||
id={`wiki-${ing.id}`}
|
||
className={`flex flex-col lg:flex-row items-stretch gap-12 p-8 sm:p-12 rounded-[3.5rem] transition-all duration-500 border border-medical-gray-100 shadow-sm hover:shadow-xl ${isTargeted ? 'bg-canina-blue/5 border-2 border-canina-blue ring-4 ring-canina-blue/10 shadow-2xl' : 'bg-white'} ${idx % 2 === 1 ? 'lg:flex-row-reverse' : ''}`}
|
||
>
|
||
{/* Content */}
|
||
<div className="lg:w-1/2 flex flex-col justify-between space-y-6">
|
||
<motion.div
|
||
initial={{ opacity: 0, x: idx % 2 === 1 ? 40 : -40 }}
|
||
whileInView={{ opacity: 1, x: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.6 }}
|
||
>
|
||
<div className="flex items-center gap-4 mb-6">
|
||
<div className="w-14 h-14 rounded-3xl bg-medical-gray-900 flex items-center justify-center text-white shadow-xl shrink-0">
|
||
<Beaker className="w-6 h-6 text-canina-gold" />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-2xl sm:text-3xl font-black text-medical-gray-900 leading-tight font-vazir">{ing.name}</h3>
|
||
<span className="text-[12px] text-canina-blue font-bold tracking-wider font-vazir inline-block mt-1 bg-canina-blue/5 px-2.5 py-0.5 rounded-lg border border-canina-blue/10">کد ماده مؤثره: <span className="font-mono">{ing.id}</span></span>
|
||
</div>
|
||
</div>
|
||
|
||
<p className="text-base text-medical-gray-600 leading-relaxed mb-6 font-vazir">
|
||
{ing.description}
|
||
</p>
|
||
|
||
<div className="grid grid-cols-1 gap-3 mb-6">
|
||
{ing.benefits.map((benefit, bIdx) => (
|
||
<div key={bIdx} className="flex items-start gap-3 p-3.5 bg-medical-gray-50/80 rounded-2xl border border-medical-gray-100">
|
||
<CheckCircle2 className="w-5 h-5 text-canina-blue shrink-0 mt-0.5" />
|
||
<span className="text-xs sm:text-sm font-bold text-medical-gray-700 leading-relaxed font-vazir">{benefit}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
|
||
{/* Products Linking */}
|
||
<div className="lg:w-1/2 relative flex">
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.95 }}
|
||
whileInView={{ opacity: 1, scale: 1 }}
|
||
viewport={{ once: true }}
|
||
className="bg-medical-gray-50/80 rounded-[3rem] p-6 sm:p-8 border border-medical-gray-200/60 relative overflow-hidden flex-1 flex flex-col justify-between"
|
||
>
|
||
<div>
|
||
<div className="flex items-center justify-between mb-6 pb-4 border-b border-medical-gray-200/60">
|
||
<div className="flex items-center gap-2">
|
||
<span className="w-2.5 h-2.5 rounded-full bg-canina-blue animate-pulse" />
|
||
<h4 className="text-xs sm:text-sm font-black text-medical-gray-900 font-vazir">موجود در محصولات کاتالوگ</h4>
|
||
</div>
|
||
<Link
|
||
href={`/shop?search=${encodeURIComponent(ing.name.replace(/\s*\([^)]*\)\s*/g, '').trim())}`}
|
||
className="text-xs font-black text-canina-blue bg-canina-blue/10 hover:bg-canina-blue hover:text-white px-3 py-1 rounded-full font-vazir transition-all"
|
||
>
|
||
{hasIngredient.length} محصول
|
||
</Link>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 relative z-10">
|
||
{hasIngredient.length > 0 ? hasIngredient.slice(0, 4).map(p => (
|
||
<Link
|
||
key={p.id}
|
||
href={`/shop/${p.slug || p.id}`}
|
||
className="bg-white rounded-[2rem] p-4 shadow-sm border border-medical-gray-200 hover:border-canina-blue hover:shadow-lg hover:scale-[1.02] transition-all cursor-pointer group flex flex-col justify-between"
|
||
>
|
||
<div className="aspect-square bg-medical-gray-50 rounded-2xl p-3 mb-3 flex items-center justify-center relative overflow-hidden">
|
||
<Image
|
||
src={p.image || '/assets/images/canina-product-placeholder.png'}
|
||
alt={p.name}
|
||
width={96}
|
||
height={96}
|
||
className="max-h-20 w-auto object-contain group-hover:scale-110 transition-transform duration-300"
|
||
unoptimized
|
||
onError={(e: any) => { e.currentTarget.src = '/assets/images/canina-product-placeholder.png'; }}
|
||
/>
|
||
</div>
|
||
<h4 className="text-xs font-black text-center text-medical-gray-900 group-hover:text-canina-blue transition-colors px-1 leading-snug font-vazir line-clamp-2">{p.name}</h4>
|
||
</Link>
|
||
)) : (
|
||
<div className="col-span-full py-8 text-center">
|
||
<p className="text-medical-gray-400 font-bold italic mb-3 font-vazir">محصولی با این ماده مؤثره یافت نشد.</p>
|
||
<button
|
||
onClick={() => router.push('/shop')}
|
||
className="text-canina-blue font-black text-sm hover:underline cursor-pointer font-vazir"
|
||
>
|
||
مشاهده همه محصولات
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{hasIngredient.length > 4 && (
|
||
<div className="mt-4 pt-3 border-t border-medical-gray-200/50 text-center">
|
||
<Link
|
||
href={`/shop?search=${encodeURIComponent(ing.name.replace(/\s*\([^)]*\)\s*/g, '').trim())}`}
|
||
className="text-xs font-black text-canina-blue hover:text-medical-gray-900 transition-colors font-vazir inline-flex items-center gap-1.5"
|
||
>
|
||
<span>+ و {hasIngredient.length - 4} محصول تخصصی دیگر در کاتالوگ</span>
|
||
<span className="underline">مشاهده همه</span>
|
||
</Link>
|
||
</div>
|
||
)}
|
||
|
||
{/* Decorative backdrop */}
|
||
<div className="absolute -bottom-20 -left-20 w-80 h-80 bg-canina-blue rounded-full blur-[100px] opacity-10 pointer-events-none" />
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
);
|
||
})()}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|