canina/frontend/application/app/catalog/CatalogClient.tsx

237 lines
12 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, useEffect } from "react";
import SafeImage from "../../components/SafeImage";
import { Download, Printer, ShieldCheck, Sparkles, CheckCircle2, ChevronLeft, Search, Filter } from "lucide-react";
import Link from "next/link";
import { productService } from "../../lib/services/productService";
import { Product } from "../../lib/data/products";
import { toPersian } from "../../lib/utils";
export default function CatalogClient() {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState("");
const [selectedCategory, setSelectedCategory] = useState("all");
useEffect(() => {
productService.getProducts({ limit: 999 })
.then(res => {
setProducts(res.data || []);
})
.catch(err => console.error("Catalog fetch error:", err))
.finally(() => setLoading(false));
}, []);
const categories = Array.from(new Set(products.map(p => p.category).filter(Boolean)));
const filteredProducts = products.filter(p => {
const matchCategory = selectedCategory === "all" || p.category === selectedCategory;
const matchSearch = p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.artNo.toLowerCase().includes(searchQuery.toLowerCase()) ||
(p.main_ingredients && p.main_ingredients.some(i => i.toLowerCase().includes(searchQuery.toLowerCase())));
return matchCategory && matchSearch;
});
const handlePrint = () => {
window.print();
};
return (
<div className="min-h-screen bg-slate-50 font-vazir text-slate-800 py-12 px-4 sm:px-8 print:bg-white print:py-0 print:px-0" dir="rtl">
{/* Header Bar - Hidden on print */}
<div className="max-w-7xl mx-auto mb-10 print:hidden">
<div className="bg-gradient-to-r from-canina-blue via-blue-900 to-canina-blue text-white rounded-[2.5rem] p-8 md:p-12 shadow-2xl relative overflow-hidden">
<div className="absolute top-0 right-0 w-96 h-96 bg-white/5 rounded-full blur-3xl -mr-20 -mt-20 pointer-events-none" />
<div className="relative z-10 flex flex-col lg:flex-row lg:items-center justify-between gap-8">
<div className="max-w-2xl">
<div className="inline-flex items-center gap-2 px-3 py-1 bg-white/10 rounded-full text-xs font-black uppercase tracking-widest text-amber-300 mb-4 border border-white/10">
<Sparkles className="w-4 h-4" />
<span>کاتالوگ رسمی محصولات کانینا آلمان</span>
</div>
<h1 className="text-3xl md:text-5xl font-black font-lalezar leading-tight mb-4">
راهنمای جامع مکمل‌های درمانی و دارویی Canina Pharma
</h1>
<p className="text-white/80 text-sm md:text-base leading-relaxed">
معرفی کامل ترکیبات فعال، موارد مصرف بالینی و استانداردهای دارویی محصولات برند آلمانی کانینا جهت استفاده صاحبان پت‌ها و متخصصین دامپزشکی.
</p>
</div>
<div className="flex flex-wrap items-center gap-4">
<button
onClick={handlePrint}
className="bg-amber-400 hover:bg-amber-500 text-slate-900 font-black px-6 py-4 rounded-2xl shadow-xl hover:scale-105 transition-all flex items-center gap-3 text-sm cursor-pointer"
>
<Printer className="w-5 h-5" />
<span>چاپ / دانلود نسخه PDF کاتالوگ</span>
</button>
<Link
href="/shop"
className="bg-white/10 hover:bg-white/20 text-white font-bold px-6 py-4 rounded-2xl border border-white/20 transition-all flex items-center gap-2 text-sm"
>
<span>ورود به فروشگاه</span>
<ChevronLeft className="w-4 h-4" />
</Link>
</div>
</div>
</div>
</div>
{/* Print Cover Header - Visible only on Print */}
<div className="hidden print:block text-center py-8 border-b-2 border-canina-blue mb-8">
<h1 className="text-3xl font-black text-canina-blue font-lalezar mb-2">کاتالوگ رسمی مکمل‌های درمانی Canina Pharma Germany</h1>
<p className="text-sm text-gray-600 font-bold">واردکننده انحصاری مکمل‌های دارویی سگ و گربه در ایران — info@canina-iran.com | ۰۲۱-۸۸۸۸۴۴۴۴</p>
</div>
{/* Filter & Search Controls - Hidden on print */}
<div className="max-w-7xl mx-auto mb-10 print:hidden flex flex-col md:flex-row gap-4 justify-between items-center bg-white p-6 rounded-3xl border border-slate-200 shadow-sm">
<div className="relative w-full md:w-96">
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
placeholder="جستجوی نام محصول، کد کالا یا ماده موثره..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-slate-50 border border-slate-200 rounded-2xl py-3 pr-11 pl-4 text-sm font-bold focus:outline-none focus:ring-2 focus:ring-canina-blue/20"
/>
</div>
<div className="flex items-center gap-2 overflow-x-auto w-full md:w-auto pb-2 md:pb-0">
<button
onClick={() => setSelectedCategory("all")}
className={`px-4 py-2.5 rounded-xl text-xs font-black transition-all shrink-0 ${
selectedCategory === "all" ? "bg-canina-blue text-white shadow-md" : "bg-slate-100 text-slate-600 hover:bg-slate-200"
}`}
>
همه دسته‌ها ({products.length})
</button>
{categories.map(cat => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
className={`px-4 py-2.5 rounded-xl text-xs font-black transition-all shrink-0 ${
selectedCategory === cat ? "bg-canina-blue text-white shadow-md" : "bg-slate-100 text-slate-600 hover:bg-slate-200"
}`}
>
{cat}
</button>
))}
</div>
</div>
{/* Catalog Products Grid */}
<div className="max-w-7xl mx-auto">
{loading ? (
<div className="text-center py-20 text-slate-400 font-bold">در حال بارگذاری کاتالوگ محصولات...</div>
) : filteredProducts.length === 0 ? (
<div className="text-center py-20 bg-white rounded-3xl border border-slate-200">
<h3 className="text-xl font-black text-slate-700 mb-2">محصولی یافت نشد</h3>
<p className="text-sm text-slate-500">لطفاً عبارت جستجو را تغییر دهید.</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 print:grid-cols-2 print:gap-6">
{filteredProducts.map((product) => (
<div
key={product.id}
className="bg-white rounded-3xl border border-slate-200 overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 flex flex-col justify-between print:break-inside-avoid print:shadow-none print:border-slate-300"
>
<div>
{/* Top Image Banner */}
<div className="relative bg-slate-100 p-6 flex items-center justify-center border-b border-slate-100 aspect-video print:h-44">
<SafeImage
src={product.image}
alt={product.name}
className="w-full h-full"
imgClassName="object-contain max-h-40"
/>
<div className="absolute top-3 right-3 bg-canina-blue text-white px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-wider">
کد کالا: {product.artNo}
</div>
{product.suitableFor && (
<div className="absolute bottom-3 left-3 bg-white/90 backdrop-blur-sm text-slate-800 px-2.5 py-1 rounded-lg text-[11px] font-black shadow-sm">
مناسب: {product.suitableFor}
</div>
)}
</div>
{/* Body Content */}
<div className="p-6 space-y-4">
<div>
<span className="text-[10px] font-black text-canina-blue uppercase tracking-widest block mb-1">
{product.category}
</span>
<h3 className="text-xl font-black text-slate-900 leading-snug font-vazir">
{product.name}
</h3>
{product.scientificTagline && (
<p className="text-xs text-slate-500 font-bold mt-1 leading-relaxed">
{product.scientificTagline}
</p>
)}
</div>
<p className="text-xs text-slate-600 leading-relaxed line-clamp-4 print:line-clamp-none">
{product.shortDescription || product.description}
</p>
{/* Ingredients Badges */}
{product.main_ingredients && product.main_ingredients.length > 0 && (
<div className="pt-2 border-t border-slate-100">
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block mb-2">
مواد موثره و ترکیبات اصلی:
</span>
<div className="flex flex-wrap gap-1.5">
{product.main_ingredients.map((ing, idx) => (
<span
key={idx}
className="bg-blue-50 text-canina-blue border border-blue-100 rounded-lg px-2 py-1 text-[10px] font-bold"
>
{ing}
</span>
))}
</div>
</div>
)}
{/* Dosage Instructions */}
{(product.dosage_logic || (product as any).dosageLogic) && (
<div className="bg-amber-50/60 border border-amber-200/60 rounded-2xl p-3 text-xs font-bold text-amber-900 flex items-start gap-2">
<ShieldCheck className="w-4 h-4 text-amber-600 shrink-0 mt-0.5" />
<div>
<span className="font-black text-[11px] block text-amber-800">دستور مصرف بالینی:</span>
<span className="text-[11px] font-medium leading-relaxed">{product.dosage_logic || (product as any).dosageLogic}</span>
</div>
</div>
)}
</div>
</div>
{/* Card Footer - Hidden on print */}
<div className="p-4 bg-slate-50 border-t border-slate-100 flex items-center justify-between print:hidden">
<div className="flex items-center gap-1 text-[11px] font-bold text-slate-500">
<CheckCircle2 className="w-4 h-4 text-green-600" />
<span>گرید دارویی آلمان</span>
</div>
<Link
href={`/shop/${product.slug || product.id}`}
className="text-xs font-black text-canina-blue hover:underline flex items-center gap-1"
>
<span>جزئیات تخصصی</span>
<ChevronLeft className="w-3.5 h-3.5" />
</Link>
</div>
</div>
))}
</div>
)}
</div>
{/* Print Footer */}
<div className="hidden print:block text-center pt-8 border-t border-slate-300 mt-12 text-xs text-slate-500 font-bold">
صفحه کاتالوگ آنلاین محصولات Canina Pharma Germany — وبسایت رسمی: https://canina-iran.com
</div>
</div>
);
}