Files
thermcool/components/shop/ProductPageClient.tsx
T

453 lines
21 KiB
TypeScript

'use client';
import { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import {
ArrowLeft, Phone, ShoppingCart, GitCompare, Check,
Thermometer, Volume2, Zap, Wind, Star, Info
} from 'lucide-react';
import { Product } from '@/content/products';
import { useCartStore } from '@/lib/cart-store';
import { useCompareStore } from '@/lib/compare-store';
import { getProductVariants } from '@/content/product-variants';
import { useRouter } from 'next/navigation';
const brandLogos: Record<string, string> = {
'Gree': '/brands/gree.webp',
'Rotenso': '/brands/rotenso.png',
'Toshiba': '/brands/toshiba.jpg',
'Daikin': '/brands/daikin.png',
'CHV': '/brands/chv.jpeg',
'Haier': '/brands/haier.jpg',
'Aux': '/brands/aux.png',
'LG': '/brands/lg.png',
'Samsung': '/brands/samsung.png',
'Mitsubishi Electric': '/brands/mitsubishi-electric.png',
'Fuji Electric': '/brands/fuji-electric.png',
};
interface Props {
product: Product;
companyPhone: string;
companyPhoneRaw: string;
}
// Szacowany SEER na podstawie klasy energetycznej
function estimateSEER(klasa: string): number {
if (!klasa) return 5.0;
const k = klasa.toUpperCase();
if (k.includes('A+++')) return 8.5;
if (k.includes('A++')) return 6.1;
if (k.includes('A+')) return 4.6;
if (k.includes('A')) return 3.6;
return 5.0;
}
// Parsuj moc — bierz wartość nominalną (środkową lub pierwszą)
function parseMoc(val: string): number {
if (!val) return 0;
const nums = val.replace(',', '.').match(/[\d.]+/g);
if (!nums) return 0;
if (nums.length === 1) return parseFloat(nums[0]);
// zakres: bierz środkową
const floats = nums.map(parseFloat);
return floats[Math.floor(floats.length / 2)];
}
const TABS = ['Opis', 'Specyfikacja', 'Montaż', 'Dostawa'] as const;
type Tab = typeof TABS[number];
export function ProductPageClient({ product, companyPhone, companyPhoneRaw }: Props) {
const [activeTab, setActiveTab] = useState<Tab>('Opis');
const [godziny, setGodziny] = useState(8);
const [added, setAdded] = useState(false);
const router = useRouter();
const variants = getProductVariants(product.slug);
const addToCart = useCartStore(s => s.addItem);
const { addItem: addCompare, removeItem: removeCompare, isAdded } = useCompareStore();
const inCompare = isAdded(product.slug);
const handleAddToCart = () => {
if (!product.price) return;
addToCart({ slug: product.slug, name: product.name, brand: product.brand, image: product.image, price: product.price });
setAdded(true);
setTimeout(() => setAdded(false), 1500);
};
const handleCompare = () => {
if (inCompare) removeCompare(product.slug);
else addCompare({ slug: product.slug, name: product.name, brand: product.brand, price: product.price, image: product.image, specs: product.specs });
};
// Kalkulator
const mocChlodnicza = parseMoc(product.specs['Moc chłodnicza'] || '');
const pobórMocy = parseMoc(product.specs['Pobór mocy chłodzenie (kW)'] || '') || (mocChlodnicza / estimateSEER(product.specs['Klasa energetyczna'] || ''));
const cenaKwh = 0.75;
const kosztGodzina = pobórMocy * cenaKwh;
const kosztMiesiac = kosztGodzina * godziny * 30;
// Kluczowe parametry do ikon
const params = [
product.specs['Klasa energetyczna'] && { icon: Star, label: 'Klasa energetyczna', value: product.specs['Klasa energetyczna'], color: 'text-green-600 bg-green-50' },
product.specs['Moc chłodnicza'] && { icon: Thermometer, label: 'Moc chłodnicza', value: `${product.specs['Moc chłodnicza']} kW`, color: 'text-blue-600 bg-blue-50' },
product.specs['Moc grzewcza (kW)'] && { icon: Thermometer, label: 'Moc grzewcza', value: `${product.specs['Moc grzewcza (kW)']} kW`, color: 'text-orange-600 bg-orange-50' },
product.specs['Hałas wewnątrz (dB)'] && { icon: Volume2, label: 'Hałas wewnątrz', value: `${product.specs['Hałas wewnątrz (dB)']} dB`, color: 'text-purple-600 bg-purple-50' },
product.specs['Czynnik chłodniczy'] && { icon: Wind, label: 'Czynnik', value: product.specs['Czynnik chłodniczy'], color: 'text-teal-600 bg-teal-50' },
product.specs['Grzanie do'] && { icon: Zap, label: 'Grzanie do', value: `-${product.specs['Grzanie do']}°C`, color: 'text-accent bg-accent-light' },
].filter(Boolean) as { icon: any; label: string; value: string; color: string }[];
return (
<main className="bg-slate-50 min-h-screen pb-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
{/* Breadcrumb */}
<nav className="flex items-center gap-1.5 text-xs text-text-muted mb-6">
<Link href="/" className="hover:text-accent transition-colors">Strona główna</Link>
<span>/</span>
<Link href="/sklep" className="hover:text-accent transition-colors">Sklep</Link>
<span>/</span>
<Link href={`/sklep?brand=${product.brand}`} className="hover:text-accent transition-colors">{product.brand}</Link>
<span>/</span>
<span className="text-primary font-medium truncate max-w-[200px]">{product.name}</span>
</nav>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 xl:gap-12">
{/* LEWA: Zdjęcie */}
<div>
<div className="relative aspect-square bg-white rounded-2xl border border-border overflow-hidden">
<Image
src={product.image}
alt={product.name}
fill
className="object-contain p-8"
priority
unoptimized={product.image.startsWith('http')}
/>
{/* Badges */}
{product.specs['Klasa energetyczna'] && (
<div className="absolute top-4 left-4 px-2.5 py-1 bg-green-500 text-white text-xs font-bold rounded-lg shadow">
{product.specs['Klasa energetyczna'].split('/')[0]}
</div>
)}
{product.category === 'multisplit' && (
<div className="absolute top-4 right-4 px-2.5 py-1 bg-accent text-white text-xs font-bold rounded-lg shadow">
MULTISPLIT
</div>
)}
</div>
</div>
{/* PRAWA: Info */}
<div className="flex flex-col gap-5">
{/* Nagłówek */}
<div>
<div className="mb-2">
{brandLogos[product.brand] ? (
<div className="bg-white rounded-lg p-2 inline-block border border-border">
<Image
src={brandLogos[product.brand]}
alt={product.brand}
width={120}
height={36}
className="object-contain object-left"
unoptimized
/>
</div>
) : (
<span className="text-xs font-bold text-accent uppercase tracking-widest">
{product.brand}
</span>
)}
</div>
<h1 className="text-2xl md:text-3xl font-extrabold text-primary font-heading leading-tight">
{product.name}
</h1>
<p className="text-xs text-text-muted mt-1.5 mb-4">SKU: {product.sku}</p>
</div>
{/* Warianty mocy */}
{variants && variants.moce.length > 0 && (
<div className="mb-4">
<p className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
Dostępne moce (kW):
</p>
<div className="flex flex-wrap gap-2">
{variants.moce.map((v) => (
<button
key={v.slug}
onClick={() => router.push(`/sklep/${v.slug}`)}
className={`px-4 py-2 rounded-xl text-sm font-bold border-2 transition-all ${
v.slug === product.slug
? 'border-accent bg-accent text-white shadow-sm'
: 'border-border bg-white text-primary hover:border-accent hover:text-accent'
}`}
>
{v.moc} kW
{v.slug !== product.slug && (
<span className="block text-[10px] font-normal opacity-70 mt-0.5">
{v.cena.toLocaleString('pl-PL')} zł
</span>
)}
{v.slug === product.slug && (
<span className="block text-[10px] font-normal opacity-80 mt-0.5">
{v.cena.toLocaleString('pl-PL')} zł
</span>
)}
</button>
))}
</div>
</div>
)}
{/* Warianty wykończenia */}
{variants && variants.wykończenia.length > 0 && (
<div className="mb-4">
<p className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
Wykończenie:
</p>
<div className="flex flex-wrap gap-2">
{variants.wykończenia.map((v) => (
<button
key={v.slug}
onClick={() => router.push(`/sklep/${v.slug}`)}
className={`px-4 py-2 rounded-xl text-sm font-bold border-2 transition-all ${
v.slug === product.slug
? 'border-accent bg-accent text-white shadow-sm'
: 'border-border bg-white text-primary hover:border-accent hover:text-accent'
}`}
>
{v.wykończenie}
</button>
))}
</div>
</div>
)}
{/* Ikony parametrów */}
{params.length > 0 && (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2.5">
{params.map((p, i) => {
const Icon = p.icon;
return (
<div key={i} className={`flex items-center gap-2 px-3 py-2 rounded-xl ${p.color} border border-current/10`}>
<Icon className="size-4 shrink-0" />
<div className="min-w-0">
<p className="text-[10px] font-medium opacity-70 leading-none mb-0.5">{p.label}</p>
<p className="text-xs font-bold truncate">{p.value}</p>
</div>
</div>
);
})}
</div>
)}
{/* Cena i CTA */}
<div className="bg-white rounded-2xl border border-border p-5">
{product.price !== null ? (
<>
<div className="flex items-end gap-3 mb-1">
<span className="text-3xl font-extrabold text-primary font-heading">
{product.price.toLocaleString('pl-PL')} zł
</span>
</div>
<p className="text-xs text-text-muted mb-1">cena urządzenia brutto (23% VAT)</p>
<p className="text-xs text-text-muted mb-4">Montaż wyceniamy indywidualnie — zadzwoń lub napisz</p>
<div className="flex flex-col sm:flex-row gap-3">
<button
onClick={handleAddToCart}
className={`flex-1 flex items-center justify-center gap-2 py-3 px-4 rounded-xl font-bold text-sm transition-all ${
added ? 'bg-green-500 text-white' : 'bg-accent text-white hover:bg-accent/90'
}`}
>
{added ? <><Check className="size-4" /> Dodano do koszyka</> : <><ShoppingCart className="size-4" /> Do koszyka</>}
</button>
<Link href="/kontakt" className="flex-1">
<button className="w-full flex items-center justify-center gap-2 py-3 px-4 rounded-xl font-bold text-sm border-2 border-primary text-primary hover:bg-primary hover:text-white transition-all">
Zapytaj o montaż
</button>
</Link>
</div>
<button
onClick={handleCompare}
className={`w-full mt-2 flex items-center justify-center gap-2 py-2 px-4 rounded-xl text-xs font-medium transition-colors ${
inCompare ? 'bg-accent/10 text-accent border border-accent' : 'bg-slate-50 text-text-muted border border-border hover:border-accent hover:text-accent'
}`}
>
<GitCompare className="size-3.5" />
{inCompare ? 'Usuń z porównania' : 'Dodaj do porównania'}
</button>
</>
) : (
<>
<p className="text-lg font-bold text-primary font-heading mb-1">Cena na zapytanie</p>
<p className="text-sm text-text-muted mb-4">Multisplit — cena zależy od konfiguracji jednostek wewnętrznych.</p>
<a href={`tel:${companyPhoneRaw}`} className="flex items-center justify-center gap-2 py-3 px-4 rounded-xl bg-accent text-white font-bold text-sm hover:bg-accent/90 transition-colors">
<Phone className="size-4" /> Zadzwoń: {companyPhone}
</a>
</>
)}
</div>
{/* Profesjonalna porada */}
<a
href={`tel:${companyPhoneRaw}`}
className="flex items-center justify-between px-5 py-3.5 bg-primary rounded-xl text-white hover:bg-primary/90 transition-colors"
>
<span className="text-sm font-semibold">Profesjonalna porada</span>
<span className="flex items-center gap-2 font-bold">
<Phone className="size-4" /> {companyPhone}
</span>
</a>
{/* Kalkulator kosztów energii */}
{mocChlodnicza > 0 && (
<div className="bg-white rounded-2xl border border-border p-5">
<div className="flex items-center gap-2 mb-4">
<Zap className="size-4 text-accent" />
<h3 className="text-sm font-bold text-primary font-heading">Kalkulator kosztów energii</h3>
<div className="group relative ml-auto">
<Info className="size-4 text-text-muted cursor-help" />
<div className="absolute right-0 bottom-6 w-56 bg-primary text-white text-xs rounded-lg p-2.5 hidden group-hover:block z-10">
Szacunek przy cenie prądu {cenaKwh} zł/kWh. Rzeczywiste koszty mogą się różnić.
</div>
</div>
</div>
<div className="flex items-center gap-4 mb-4">
<label className="text-xs text-text-muted shrink-0">Godziny użycia dziennie:</label>
<input
type="range"
min={1}
max={24}
value={godziny}
onChange={e => setGodziny(Number(e.target.value))}
className="flex-1 accent-accent"
/>
<span className="text-sm font-bold text-primary w-8 text-right">{godziny}h</span>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="bg-slate-50 rounded-xl p-3 text-center">
<p className="text-xs text-text-muted mb-1">Koszt / godzinę</p>
<p className="text-lg font-bold text-primary font-heading">
{kosztGodzina.toFixed(2)} zł
</p>
</div>
<div className="bg-accent-light rounded-xl p-3 text-center border border-accent/20">
<p className="text-xs text-accent mb-1">Koszt miesięczny</p>
<p className="text-lg font-bold text-accent font-heading">
{kosztMiesiac.toFixed(2)} zł
</p>
</div>
</div>
<p className="text-[10px] text-text-muted mt-2 text-center">
Szacunek przy {cenaKwh} zł/kWh · {godziny}h/dzień · 30 dni
{!product.specs['Pobór mocy chłodzenie (kW)'] && ' · pobór szacowany z klasy energetycznej'}
</p>
</div>
)}
</div>
</div>
{/* TABS */}
<div className="mt-10">
<div className="flex gap-1 border-b border-border mb-6 overflow-x-auto">
{TABS.map(tab => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-5 py-3 text-sm font-semibold whitespace-nowrap transition-colors border-b-2 -mb-px ${
activeTab === tab
? 'border-accent text-accent'
: 'border-transparent text-text-muted hover:text-primary'
}`}
>
{tab}
</button>
))}
</div>
{/* Tab: Opis */}
{activeTab === 'Opis' && (
<div className="max-w-3xl">
{product.shortDescription ? (
<p className="text-text-muted leading-relaxed text-base">{product.shortDescription}</p>
) : (
<p className="text-text-muted">Brak opisu produktu.</p>
)}
</div>
)}
{/* Tab: Specyfikacja */}
{activeTab === 'Specyfikacja' && (
<div className="max-w-2xl">
{Object.keys(product.specs).length > 0 ? (
<div className="rounded-xl border border-border overflow-hidden">
<table className="w-full text-sm">
<tbody>
{Object.entries(product.specs).map(([key, val], idx) => (
<tr key={key} className={idx % 2 === 0 ? 'bg-white' : 'bg-slate-50'}>
<td className="px-5 py-3 text-text-muted font-medium w-1/2 border-r border-border">{key}</td>
<td className="px-5 py-3 text-primary font-semibold">{val}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="text-text-muted">Brak specyfikacji.</p>
)}
</div>
)}
{/* Tab: Montaż */}
{activeTab === 'Montaż' && (
<div className="max-w-2xl space-y-4">
<div className="bg-white rounded-xl border border-border p-5">
<h3 className="font-bold text-primary font-heading mb-3">Montaż klimatyzacji</h3>
<p className="text-sm text-text-muted leading-relaxed mb-4">
Oferujemy profesjonalny montaż wszystkich klimatyzatorów z naszego sklepu na terenie Dolnego Śląska.
Montaż wykonujemy z uprawnieniami F-Gaz, estetycznie i terminowo.
</p>
<ul className="space-y-2 text-sm text-text-muted">
{['Bezpłatna wycena montażu przed realizacją', 'Uprawnienia F-Gaz — wymóg prawny', 'Próba szczelności i próżnia po montażu', 'Szkolenie z obsługi urządzenia', 'Gwarancja na wykonaną instalację'].map(item => (
<li key={item} className="flex items-center gap-2">
<Check className="size-4 text-green-500 shrink-0" />
{item}
</li>
))}
</ul>
<a href={`tel:${companyPhoneRaw}`} className="mt-4 inline-flex items-center gap-2 btn btn-primary text-white font-bold text-sm">
<Phone className="size-4" /> Zapytaj o montaż
</a>
</div>
</div>
)}
{/* Tab: Dostawa */}
{activeTab === 'Dostawa' && (
<div className="max-w-2xl">
<div className="bg-white rounded-xl border border-border p-5">
<h3 className="font-bold text-primary font-heading mb-3">Informacje o dostawie</h3>
<p className="text-sm text-text-muted leading-relaxed">
Szczegóły dostawy ustalamy indywidualnie po złożeniu zamówienia. Skontaktujemy się z Tobą w ciągu 24 godzin
od otrzymania zamówienia, aby potwierdzić termin dostawy i montażu.
</p>
<Link href="/dostawa" className="mt-3 inline-block text-sm text-accent hover:underline font-medium">
Sprawdź politykę dostawy →
</Link>
</div>
</div>
)}
</div>
</div>
</main>
);
}