453 lines
21 KiB
TypeScript
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>
|
|
);
|
|
}
|