969 lines
50 KiB
TypeScript
969 lines
50 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect, useMemo } from 'react';
|
||
import Link from 'next/link';
|
||
import Image from 'next/image';
|
||
import { ArrowRight, ArrowLeft, Check, Thermometer, Home, Zap, ShoppingCart, Phone, RotateCcw } from 'lucide-react';
|
||
import { Product } from '@/content/products';
|
||
import { companyDetails } from '@/content/company';
|
||
import { ProductCard } from '@/components/shop/ProductCard';
|
||
|
||
interface Props {
|
||
products: Product[];
|
||
}
|
||
|
||
// Hook do zapisywania stanu w sessionStorage
|
||
function useSessionState<T>(key: string, initialValue: T) {
|
||
const [state, setState] = useState<T>(initialValue);
|
||
|
||
useEffect(() => {
|
||
try {
|
||
const item = window.sessionStorage.getItem(key);
|
||
if (item) {
|
||
setState(JSON.parse(item));
|
||
}
|
||
} catch (error) {}
|
||
}, [key]);
|
||
|
||
const setValue = (value: T | ((val: T) => T)) => {
|
||
try {
|
||
const valueToStore = value instanceof Function ? value(state) : value;
|
||
setState(valueToStore);
|
||
if (typeof window !== 'undefined') {
|
||
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
||
}
|
||
} catch (error) {}
|
||
};
|
||
|
||
return [state, setValue] as const;
|
||
}
|
||
|
||
// Oblicz wymaganą moc chłodniczą w kW
|
||
function calculatePower(m2: number, floor: string, sun: string, building: string): number {
|
||
let power = m2 * 0.1; // baza: 1 kW na 10 m²
|
||
|
||
// Korekta typu budynku i piętra
|
||
if (building === 'apartment') {
|
||
if (floor === 'top') power *= 1.2; // ostatnie piętro - więcej ciepła z dachu
|
||
if (floor === 'ground') power *= 0.95;
|
||
} else if (building === 'house') {
|
||
if (floor === 'top') power *= 1.1; // poddasze - dodatkowe nagrzewanie
|
||
}
|
||
|
||
// Korekta nasłonecznienia
|
||
if (sun === 'south') power *= 1.15; // południe - duże nasłonecznienie
|
||
if (sun === 'west') power *= 1.1;
|
||
if (sun === 'north') power *= 0.9;
|
||
|
||
return Math.round(power * 10) / 10;
|
||
}
|
||
|
||
// Szacunkowy koszt montażu
|
||
function estimateInstall(pipes: string, rooms: number, installMethod: string, outdoorUnitLocation: string, hasSocket: string): { min: number; max: number } {
|
||
const base = rooms === 1 ? 1400 : rooms === 2 ? 2400 : rooms === 3 ? 3200 : 3800;
|
||
// Przybliżona liczba dodatkowych metrów (powyżej 3m)
|
||
const extraMeters = pipes === 'short' ? 0 : pipes === 'medium' ? 3 : pipes === 'long' ? 7 : 15;
|
||
const methodPrice = installMethod === 'bruzda' ? 90 : 80; // zł za metr
|
||
// Zostawmy bazowe koszty rur miedzianych (np. miedź, kable) i dodajmy koszt bruzdy/korytka
|
||
const pipeBaseCost = pipes === 'short' ? 0 : pipes === 'medium' ? 200 : pipes === 'long' ? 500 : 1000;
|
||
// Elewacja: 400 zł (pompka) + ok. 600 zł (1h podnośnika jako bufor)
|
||
const facadeCost = outdoorUnitLocation === 'facade' ? 1000 : 0;
|
||
// Brak gniazdka: dodatkowy koszt ok 200 zł za dociągnięcie zasilania
|
||
const socketCost = hasSocket !== 'yes' ? 200 : 0;
|
||
const pipeExtra = pipeBaseCost + (extraMeters * methodPrice) + facadeCost + socketCost;
|
||
return { min: base + pipeExtra, max: base + pipeExtra + 600 };
|
||
}
|
||
|
||
const STEPS = ['Typ', 'Pomieszczenie', 'Instalacja', 'Budżet', 'Dobór'] as const;
|
||
|
||
export function KalkulatorClient({ products }: Props) {
|
||
const [showWidthWarning, setShowWidthWarning] = useState(false);
|
||
const [maxWidth, setMaxWidth] = useState(80);
|
||
const [deviceType, setDeviceType] = useSessionState<'klimatyzacja' | 'pompa'>('calc_deviceType', 'klimatyzacja');
|
||
const [pumpFormSent, setPumpFormSent] = useState(false);
|
||
const [pumpForm, setPumpForm] = useState({ name: '', phone: '', city: '', message: '' });
|
||
const [step, setStep] = useSessionState('calc_step', 0);
|
||
const [building, setBuilding] = useSessionState('calc_building', 'apartment');
|
||
const [pomieszczenia, setPomieszczenia] = useSessionState<{ nazwa: string; m2: number }[]>('calc_pomieszczenia', [
|
||
{ nazwa: 'Pokój 1', m2: 20 }
|
||
]);
|
||
const [floor, setFloor] = useSessionState('calc_floor', 'middle'); // ground | middle | top
|
||
const [sun, setSun] = useSessionState('calc_sun', 'south'); // north | east | south | west
|
||
|
||
const addRoom = () => {
|
||
if (pomieszczenia.length < 5) {
|
||
setPomieszczenia(prev => [...prev, { nazwa: `Pokój ${prev.length + 1}`, m2: 15 }]);
|
||
}
|
||
};
|
||
|
||
const removeRoom = (i: number) => {
|
||
if (pomieszczenia.length > 1) {
|
||
setPomieszczenia(prev => prev.filter((_, idx) => idx !== i));
|
||
}
|
||
};
|
||
|
||
const updateRoom = (i: number, m2: number) => {
|
||
setPomieszczenia(prev => prev.map((r, idx) => idx === i ? { ...r, m2 } : r));
|
||
};
|
||
const [pipes, setPipes] = useSessionState('calc_pipes', 'medium'); // short(<3m) | medium(3-6m) | long(6-10m) | verylong(>10m)
|
||
const [installMethod, setInstallMethod] = useSessionState<'korytko' | 'bruzda'>('calc_installMethod', 'korytko');
|
||
const [outdoorUnitLocation, setOutdoorUnitLocation] = useSessionState<'balcony' | 'facade' | 'roof'>('calc_outdoorLocation', 'balcony');
|
||
const [hasSocket, setHasSocket] = useSessionState<'yes' | 'no' | 'dont_know'>('calc_hasSocket', 'yes');
|
||
const [budget, setBudget] = useSessionState('calc_budget', 8000);
|
||
const [preferredBrand, setPreferredBrand] = useSessionState('calc_brand', '');
|
||
const [energyClass, setEnergyClass] = useSessionState('calc_energy', '');
|
||
const [maxNoise, setMaxNoise] = useSessionState<number | null>('calc_maxNoise', null);
|
||
const [maxNoiseOut, setMaxNoiseOut] = useSessionState<number | null>('calc_maxNoiseOut', null);
|
||
const [page, setPage] = useSessionState('calc_page', 1);
|
||
|
||
const totalM2 = pomieszczenia.reduce((sum, p) => sum + p.m2, 0);
|
||
const rooms = pomieszczenia.length;
|
||
const requiredPower = useMemo(() =>
|
||
pomieszczenia.reduce((sum, p) => sum + calculatePower(p.m2, floor, sun, building), 0),
|
||
[pomieszczenia, floor, sun, building]
|
||
);
|
||
const requiredPowerRounded = Math.round(requiredPower * 10) / 10;
|
||
const installCost = useMemo(() => estimateInstall(pipes, rooms, installMethod, outdoorUnitLocation, hasSocket), [pipes, rooms, installMethod, outdoorUnitLocation, hasSocket]);
|
||
|
||
const category = rooms === 1 ? 'scienne' : 'multisplit';
|
||
|
||
const results = useMemo(() => {
|
||
if (step < 4 || deviceType === 'pompa') return [];
|
||
|
||
const maxPrice = budget * 0.75;
|
||
const powerMin = requiredPower * 0.6;
|
||
const powerMax = requiredPower * 1.8;
|
||
|
||
const filtered = products
|
||
.filter(p => {
|
||
if (p.category !== category) return false;
|
||
|
||
if (preferredBrand && p.brand !== preferredBrand) return false;
|
||
|
||
if (energyClass) {
|
||
const pClass = p.specs?.['Klasa energetyczna'];
|
||
if (!pClass || !pClass.includes(energyClass)) return false;
|
||
}
|
||
|
||
if (maxNoise !== null) {
|
||
const noiseStr = p.specs?.['Hałas wewnątrz (dB)'];
|
||
if (!noiseStr) return false;
|
||
// Hałas jest zapisany np. "19–38" - bierzemy dolną granicę
|
||
const minNoise = parseInt(noiseStr.split('–')[0].trim());
|
||
if (isNaN(minNoise) || minNoise > maxNoise) return false;
|
||
}
|
||
|
||
if (maxNoiseOut !== null) {
|
||
const noiseOutStr = p.specs?.['Hałas zewnątrz (dB)'];
|
||
// Jeśli produktu nie ma tego parametru, odrzucamy, jeśli nałożono ścisły filtr
|
||
if (!noiseOutStr) return false;
|
||
const minNoiseOut = parseInt(noiseOutStr.split('–')[0].trim());
|
||
if (isNaN(minNoiseOut) || minNoiseOut > maxNoiseOut) return false;
|
||
}
|
||
|
||
if (category === 'multisplit') {
|
||
if (p.price !== null && p.price > maxPrice) return false;
|
||
} else {
|
||
if (p.price === null || p.price > maxPrice) return false;
|
||
}
|
||
|
||
const moc = parseFloat(String(p.specs?.['Moc chłodnicza'] || '0').replace(',', '.').split('–')[0]);
|
||
return moc >= powerMin && moc <= powerMax;
|
||
})
|
||
.sort((a, b) => {
|
||
// Sortuj po tym jak blisko jest moc do wymaganej
|
||
const mocA = parseFloat(String(a.specs?.['Moc chłodnicza'] || '0').replace(',', '.').split('–')[0]);
|
||
const mocB = parseFloat(String(b.specs?.['Moc chłodnicza'] || '0').replace(',', '.').split('–')[0]);
|
||
return Math.abs(mocA - requiredPower) - Math.abs(mocB - requiredPower);
|
||
});
|
||
|
||
return filtered.length > 0
|
||
? filtered
|
||
: products
|
||
.filter(p => p.category === category && p.price !== null)
|
||
.sort((a, b) => (a.price || 0) - (b.price || 0));
|
||
}, [step, deviceType, products, requiredPower, category, budget, preferredBrand, energyClass, maxNoise, maxNoiseOut]);
|
||
|
||
const paginatedResults = useMemo(() => results.slice((page - 1) * 9, page * 9), [results, page]);
|
||
const totalPages = Math.ceil(results.length / 9);
|
||
|
||
const getPageNumbers = () => {
|
||
const pages = [];
|
||
if (totalPages <= 7) {
|
||
for (let i = 1; i <= totalPages; i++) pages.push(i);
|
||
} else {
|
||
if (page <= 4) {
|
||
pages.push(1, 2, 3, 4, 5, '...', totalPages);
|
||
} else if (page >= totalPages - 3) {
|
||
pages.push(1, '...', totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages);
|
||
} else {
|
||
pages.push(1, '...', page - 1, page, page + 1, '...', totalPages);
|
||
}
|
||
}
|
||
return pages;
|
||
};
|
||
|
||
const handleFinish = () => {
|
||
setStep(4);
|
||
setPage(1);
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const handleReset = () => {
|
||
setStep(0);
|
||
setPage(1);
|
||
Object.keys(window.sessionStorage).forEach(key => {
|
||
if (key.startsWith('calc_')) window.sessionStorage.removeItem(key);
|
||
});
|
||
setDeviceType('klimatyzacja');
|
||
setBuilding('apartment');
|
||
setPomieszczenia([{ nazwa: 'Pokój 1', m2: 20 }]);
|
||
setFloor('middle');
|
||
setSun('south');
|
||
setPipes('medium');
|
||
setInstallMethod('korytko');
|
||
setOutdoorUnitLocation('balcony');
|
||
setHasSocket('yes');
|
||
setBudget(8000);
|
||
setPreferredBrand('');
|
||
setEnergyClass('');
|
||
setMaxNoise(null);
|
||
setMaxNoiseOut(null);
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const handleNext = () => {
|
||
setStep(s => s + 1);
|
||
setPage(1);
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const handlePumpSubmit = async () => {
|
||
if (!pumpForm.name || !pumpForm.phone || !pumpForm.city) return;
|
||
// Na razie pokazujemy success — podepniemy Resend później
|
||
setPumpFormSent(true);
|
||
};
|
||
|
||
return (
|
||
<main className="bg-slate-50 min-h-screen py-12">
|
||
<div className={`mx-auto px-4 sm:px-6 transition-all duration-500 ${step === 4 && deviceType !== 'pompa' ? 'max-w-5xl' : 'max-w-2xl'}`}>
|
||
{/* Header */}
|
||
<div className="text-center mb-8">
|
||
<h1 className="text-3xl font-extrabold text-primary font-heading mb-2">
|
||
Dobierz urządzenie
|
||
</h1>
|
||
<p className="text-text-muted">Odpowiedz na {STEPS.length} krótkich pytań — pokażemy Ci najlepsze opcje z naszej oferty</p>
|
||
</div>
|
||
|
||
{/* Pasek postępu */}
|
||
<div className="flex items-center mb-8">
|
||
{STEPS.map((s, i) => (
|
||
<div key={s} className="flex items-start flex-1 last:flex-none">
|
||
<div className="flex flex-col items-center gap-1">
|
||
<div className={`size-7 rounded-full flex items-center justify-center text-xs font-bold shrink-0 transition-all ${
|
||
i < step ? 'bg-green-500 text-white' : i === step ? 'bg-accent text-white' : 'bg-slate-200 text-text-muted'
|
||
}`}>
|
||
{i < step ? <Check className="size-3.5" /> : i + 1}
|
||
</div>
|
||
<span className={`text-xs font-medium hidden sm:block whitespace-nowrap ${i === step ? 'text-accent' : 'text-text-muted'}`}>{s}</span>
|
||
</div>
|
||
{i < STEPS.length - 1 && (
|
||
<div className={`flex-1 h-0.5 mx-2 mt-[13px] ${i < step ? 'bg-green-500' : 'bg-slate-200'}`} />
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Karty kroków */}
|
||
<div className="bg-white rounded-2xl border border-border p-6 md:p-8">
|
||
|
||
{/* Krok 0: Typ urządzenia */}
|
||
{step === 0 && (
|
||
<div className="flex flex-col gap-4">
|
||
<h2 className="text-lg font-bold text-primary font-heading mb-2">Co chcesz zainstalować?</h2>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
<button onClick={() => setDeviceType('klimatyzacja')}
|
||
className={`p-5 rounded-xl border-2 text-left transition-all ${deviceType === 'klimatyzacja' ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||
<span className="text-3xl mb-2 block">❄️</span>
|
||
<p className="font-bold text-primary">Klimatyzacja</p>
|
||
<p className="text-xs text-text-muted mt-1">Chłodzenie latem, opcjonalne grzanie zimą</p>
|
||
</button>
|
||
<button onClick={() => setDeviceType('pompa')}
|
||
className={`p-5 rounded-xl border-2 text-left transition-all ${deviceType === 'pompa' ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||
<span className="text-3xl mb-2 block">🔥</span>
|
||
<p className="font-bold text-primary">Pompa ciepła</p>
|
||
<p className="text-xs text-text-muted mt-1">Głównie ogrzewanie domu z niskim kosztem eksploatacji</p>
|
||
</button>
|
||
</div>
|
||
{deviceType === 'pompa' && !pumpFormSent && (
|
||
<div className="mt-4 p-5 bg-white border border-border rounded-2xl flex flex-col gap-4">
|
||
<p className="text-sm font-bold text-primary">Wypełnij formularz — oddzwonimy w celu ustalenia terminu bezpłatnej i niezobowiązującej wizji lokalnej</p>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
<div>
|
||
<label className="text-xs font-medium text-primary mb-1 block">Imię i nazwisko *</label>
|
||
<input
|
||
name="pump_name" required
|
||
className="w-full border border-border rounded-xl px-3 py-2.5 text-sm focus:border-accent outline-none"
|
||
placeholder="Jan Kowalski"
|
||
value={pumpForm.name}
|
||
onChange={e => setPumpForm(f => ({...f, name: e.target.value}))}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="text-xs font-medium text-primary mb-1 block">Telefon *</label>
|
||
<input
|
||
name="pump_phone" required type="tel"
|
||
className="w-full border border-border rounded-xl px-3 py-2.5 text-sm focus:border-accent outline-none"
|
||
placeholder="+48 600 000 000"
|
||
value={pumpForm.phone}
|
||
onChange={e => setPumpForm(f => ({...f, phone: e.target.value}))}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="text-xs font-medium text-primary mb-1 block">Miasto *</label>
|
||
<input
|
||
name="pump_city" required
|
||
className="w-full border border-border rounded-xl px-3 py-2.5 text-sm focus:border-accent outline-none"
|
||
placeholder="Wrocław"
|
||
value={pumpForm.city}
|
||
onChange={e => setPumpForm(f => ({...f, city: e.target.value}))}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="text-xs font-medium text-primary mb-1 block">Krótki opis (opcjonalnie)</label>
|
||
<textarea
|
||
name="pump_message" rows={3}
|
||
className="w-full border border-border rounded-xl px-3 py-2.5 text-sm focus:border-accent outline-none resize-none"
|
||
placeholder="Np. dom 150m², nowy budynek, chcę wymienić ogrzewanie gazowe..."
|
||
value={pumpForm.message}
|
||
onChange={e => setPumpForm(f => ({...f, message: e.target.value}))}
|
||
/>
|
||
</div>
|
||
<button
|
||
onClick={handlePumpSubmit}
|
||
disabled={!pumpForm.name || !pumpForm.phone || !pumpForm.city}
|
||
className="flex items-center justify-center gap-2 py-3 px-6 bg-accent text-white rounded-xl font-bold text-sm hover:bg-accent/90 transition-colors disabled:opacity-50"
|
||
>
|
||
Wyślij zapytanie o pompę ciepła
|
||
</button>
|
||
<p className="text-xs text-text-muted text-center mt-2">
|
||
Wizja lokalna jest bezpłatna i niezobowiązująca. Oddzwaniamy w ciągu 24 godzin.
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{deviceType === 'pompa' && pumpFormSent && (
|
||
<div className="mt-4 p-5 bg-green-50 border border-green-200 rounded-2xl text-center">
|
||
<p className="text-green-700 font-bold mb-1">✅ Zapytanie wysłane!</p>
|
||
<p className="text-sm text-green-600">Skontaktujemy się z Tobą w ciągu 24 godzin z bezpłatną wyceną.</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Krok 1: Pomieszczenie */}
|
||
{step === 1 && (
|
||
<div className="flex flex-col gap-6">
|
||
<div className="flex items-center gap-3 mb-2">
|
||
<div className="size-10 rounded-xl bg-blue-50 flex items-center justify-center">
|
||
<Home className="size-5 text-blue-600" />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-lg font-bold text-primary font-heading">Pomieszczenie</h2>
|
||
<p className="text-xs text-text-muted">Metraż i warunki</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Lista pomieszczeń */}
|
||
<div className="flex flex-col gap-3">
|
||
{pomieszczenia.map((p, i) => (
|
||
<div key={i} className="bg-slate-50 rounded-xl border border-border p-4">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<span className="text-sm font-semibold text-primary">{p.nazwa}</span>
|
||
{pomieszczenia.length > 1 && (
|
||
<button
|
||
onClick={() => removeRoom(i)}
|
||
className="text-xs text-red-400 hover:text-red-600 transition-colors"
|
||
>
|
||
Usuń
|
||
</button>
|
||
)}
|
||
</div>
|
||
<div className="flex flex-col gap-1.5">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs text-text-muted">Powierzchnia</span>
|
||
<span className="text-xs font-bold text-primary bg-accent/10 px-2 py-0.5 rounded-md">{p.m2} m²</span>
|
||
</div>
|
||
<input
|
||
type="range" min={5} max={100} step={5} value={p.m2}
|
||
onChange={e => updateRoom(i, Number(e.target.value))}
|
||
className="range range-xs range-accent w-full"
|
||
/>
|
||
<div className="flex justify-between text-xs text-text-muted">
|
||
<span>5 m²</span>
|
||
<span>100 m²</span>
|
||
</div>
|
||
{(() => {
|
||
const mocPokoju = Math.round(calculatePower(p.m2, floor, sun, building) * 10) / 10;
|
||
const dostepneMoce = [2.5, 3.5, 5.0, 7.0];
|
||
const zalecana = dostepneMoce.reduce((prev, curr) =>
|
||
Math.abs(curr - mocPokoju) < Math.abs(prev - mocPokoju) ? curr : prev
|
||
);
|
||
return (
|
||
<div className="mt-2 flex items-center justify-between bg-white border border-border rounded-lg px-3 py-2">
|
||
<span className="text-xs text-text-muted">Zalecana moc jednostki</span>
|
||
<span className="text-sm font-bold text-accent">{zalecana} kW</span>
|
||
</div>
|
||
);
|
||
})()}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{pomieszczenia.length < 5 && (
|
||
<button
|
||
onClick={addRoom}
|
||
className="flex items-center gap-2 text-sm text-accent font-semibold hover:text-accent/80 transition-colors border border-dashed border-accent/40 rounded-xl px-4 py-3 w-full justify-center"
|
||
>
|
||
+ Dodaj kolejne pomieszczenie
|
||
</button>
|
||
)}
|
||
|
||
{/* Ograniczenie szerokości */}
|
||
<div className="bg-slate-50 border border-border rounded-xl p-4">
|
||
<div className="flex items-start justify-between gap-3 mb-2">
|
||
<div>
|
||
<p className="text-sm font-semibold text-primary">Ograniczenie szerokości jednostki?</p>
|
||
<p className="text-xs text-text-muted mt-0.5">Np. wąska ściana, wnęka, szafa</p>
|
||
</div>
|
||
<button
|
||
onClick={() => setShowWidthWarning(prev => !prev)}
|
||
className={`shrink-0 text-xs font-bold px-3 py-1.5 rounded-lg transition-colors ${
|
||
showWidthWarning ? 'bg-amber-100 text-amber-700' : 'bg-slate-200 text-slate-600 hover:bg-slate-300'
|
||
}`}
|
||
>
|
||
{showWidthWarning ? 'Tak, mam ograniczenie' : 'Nie dotyczy'}
|
||
</button>
|
||
</div>
|
||
{showWidthWarning && (
|
||
<div className="mt-3">
|
||
<div className="flex items-center justify-between mb-1.5">
|
||
<span className="text-xs text-text-muted">Maksymalna szerokość jednostki</span>
|
||
<span className="text-xs font-bold text-amber-700 bg-amber-100 px-2 py-0.5 rounded-md">{maxWidth} cm</span>
|
||
</div>
|
||
<div className="relative h-5 flex items-center">
|
||
<div className="absolute w-full h-1.5 bg-slate-200 rounded-full" />
|
||
<div
|
||
className="absolute h-1.5 bg-amber-400 rounded-full"
|
||
style={{ width: `${((maxWidth - 40) / 80) * 100}%` }}
|
||
/>
|
||
<input
|
||
type="range" min={40} max={120} step={5} value={maxWidth}
|
||
onChange={e => setMaxWidth(Number(e.target.value))}
|
||
className="absolute w-full h-1.5 opacity-0 cursor-pointer z-10"
|
||
/>
|
||
<div
|
||
className="absolute size-4 bg-white border-2 border-amber-400 rounded-full shadow-md pointer-events-none"
|
||
style={{ left: `calc(${((maxWidth - 40) / 80) * 100}% - 8px)` }}
|
||
/>
|
||
</div>
|
||
<div className="flex justify-between text-xs text-text-muted mt-1">
|
||
<span>40 cm</span>
|
||
<span>120 cm</span>
|
||
</div>
|
||
<div className="mt-3 flex items-start gap-2 p-3 bg-amber-50 border border-amber-200 rounded-xl">
|
||
<span className="text-amber-500 text-base shrink-0">⚠️</span>
|
||
<p className="text-xs text-amber-800 leading-relaxed">
|
||
Standardowe jednostki split mają <strong>70–110 cm szerokości</strong>.
|
||
{maxWidth < 70 && ' Przy tak małej przestrzeni wybór jest bardzo ograniczony — '}
|
||
{maxWidth >= 70 && maxWidth < 85 && ' Przy tej szerokości dostępne są kompaktowe modele — '}
|
||
{maxWidth >= 85 && ' Większość modeli powinna się zmieścić — '}
|
||
skonsultuj wymiary konkretnego modelu przed zakupem.
|
||
<a href="/kontakt" className="ml-1 font-bold text-amber-700 underline">Zapytaj nas →</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="p-3 bg-accent/10 rounded-xl">
|
||
<p className="text-sm text-accent font-semibold">
|
||
Łączna wymagana moc: ok. <strong>{requiredPowerRounded} kW</strong>
|
||
<span className="text-xs font-normal text-text-muted ml-2">
|
||
({pomieszczenia.length === 1 ? 'klimatyzator split' : `multisplit ${pomieszczenia.length}+1`}, łącznie {totalM2} m²)
|
||
</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">Typ budynku</label>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
{[
|
||
{ value: 'apartment', label: '🏢 Mieszkanie w bloku', sub: 'Kamienica, wieżowiec, blok' },
|
||
{ value: 'house', label: '🏠 Dom jednorodzinny', sub: 'Wolnostojący, szeregowiec, bliźniak' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setBuilding(o.value)}
|
||
className={`p-4 rounded-xl border-2 text-left transition-all ${building === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||
<p className="text-xs text-text-muted">{o.sub}</p>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{building === 'house' && (
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">Typ przestrzeni</label>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
{[
|
||
{ value: 'ground', label: '🏠 Parter' },
|
||
{ value: 'top', label: '🏠 Piętro / poddasze' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setFloor(o.value)}
|
||
className={`py-2.5 px-3 rounded-xl text-sm font-semibold border-2 transition-all ${floor === o.value ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'}`}>
|
||
{o.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{building === 'apartment' && (
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">Piętro</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{[
|
||
{ value: 'ground', label: 'Parter' },
|
||
{ value: 'low', label: '1-3 piętro' },
|
||
{ value: 'middle', label: '4-8 piętro' },
|
||
{ value: 'high', label: '9+ piętro' },
|
||
{ value: 'top', label: 'Ostatnie' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setFloor(o.value)}
|
||
className={`py-2 px-4 rounded-xl text-sm font-semibold border-2 transition-all ${floor === o.value ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'}`}>
|
||
{o.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">Ekspozycja okien</label>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
{[
|
||
{ value: 'north', label: '🧭 Północ (chłodna)' },
|
||
{ value: 'east', label: '🧭 Wschód' },
|
||
{ value: 'west', label: '🧭 Zachód' },
|
||
{ value: 'south', label: '🧭 Południe (mocne słońce)' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setSun(o.value)}
|
||
className={`py-2.5 px-3 rounded-xl text-sm font-semibold border-2 transition-all ${sun === o.value ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'}`}>
|
||
{o.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Krok 2: Instalacja */}
|
||
{step === 2 && (
|
||
<div className="flex flex-col gap-6">
|
||
<div className="flex items-center gap-3 mb-2">
|
||
<div className="size-10 rounded-xl bg-orange-50 flex items-center justify-center">
|
||
<Zap className="size-5 text-orange-600" />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-lg font-bold text-primary font-heading">Długość instalacji</h2>
|
||
<p className="text-xs text-text-muted">Rury i kable między jednostkami</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-3">
|
||
{[
|
||
{ value: 'short', label: 'Do 3 metrów', sub: 'Jednostka zewnętrzna tuż obok (balkon, taras)' },
|
||
{ value: 'medium', label: '3–6 metrów', sub: 'Typowa instalacja w bloku lub domu' },
|
||
{ value: 'long', label: '6–10 metrów', sub: 'Dłuższa trasa, np. przez kilka pomieszczeń' },
|
||
{ value: 'verylong', label: 'Powyżej 10 metrów', sub: 'Bardzo długa trasa, wymaga dodatkowych materiałów' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setPipes(o.value)}
|
||
className={`p-4 rounded-xl border-2 text-left transition-all ${pipes === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||
<p className="text-xs text-text-muted">{o.sub}</p>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">Sposób prowadzenia instalacji (powyżej 3m)</label>
|
||
<div className="flex flex-col gap-3">
|
||
{[
|
||
{ value: 'korytko', label: 'Korytko PCV', sub: 'Rury schowane w białym korytku (80 zł/mb)' },
|
||
{ value: 'bruzda', label: 'Bruzda w ścianie', sub: 'Rury ukryte w wykutej bruzdzie (90 zł/mb)' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setInstallMethod(o.value as 'korytko' | 'bruzda')}
|
||
className={`p-4 rounded-xl border-2 text-left transition-all ${installMethod === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||
<p className="text-xs text-text-muted">{o.sub}</p>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">Miejsce montażu jednostki zewnętrznej (agregatu)</label>
|
||
<div className="flex flex-col gap-3">
|
||
{[
|
||
{ value: 'balcony', label: 'Zwykły balkon / taras / posadzka', sub: 'Standardowy montaż, najtańszy i najwygodniejszy w serwisowaniu' },
|
||
{ value: 'facade', label: 'Elewacja / balkon francuski', sub: 'Wymaga użycia podnośnika koszowego (ok. 600 zł/h) i pompki skroplin (+ ok. 400 zł)' },
|
||
{ value: 'roof', label: 'Dach budynku', sub: 'Wymaga odpowiedniego stelaża lub konstrukcji dachowej' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setOutdoorUnitLocation(o.value as any)}
|
||
className={`p-4 rounded-xl border-2 text-left transition-all ${outdoorUnitLocation === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||
<p className="text-xs text-text-muted">{o.sub}</p>
|
||
</button>
|
||
))}
|
||
</div>
|
||
{building === 'apartment' && outdoorUnitLocation !== 'balcony' && (
|
||
<div className="mt-3 p-3 bg-amber-50 rounded-xl text-xs text-amber-800 border border-amber-200">
|
||
⚠️ <strong>Pamiętaj:</strong> Montaż agregatu na elewacji lub dachu w bloku wielorodzinnym wymaga uzyskania pisemnej zgody spółdzielni, wspólnoty mieszkaniowej lub zarządcy budynku.
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">Zasilanie (gniazdko lub prąd w miejscu montażu agregatu)</label>
|
||
<div className="flex flex-col gap-3">
|
||
{[
|
||
{ value: 'yes', label: 'Tak', sub: 'Mam gniazdko na balkonie lub wyprowadzony kabel' },
|
||
{ value: 'no', label: 'Nie', sub: 'Instalator musi dociągnąć prąd z wewnątrz (+ ok. 200 zł)' },
|
||
{ value: 'dont_know', label: 'Nie wiem / Do ustalenia', sub: 'Do ustalenia w trakcie wizji lokalnej' },
|
||
].map(o => (
|
||
<button key={o.value} onClick={() => setHasSocket(o.value as any)}
|
||
className={`p-4 rounded-xl border-2 text-left transition-all ${hasSocket === o.value ? 'border-accent bg-accent/5' : 'border-border hover:border-accent'}`}>
|
||
<p className="text-sm font-bold text-primary">{o.label}</p>
|
||
<p className="text-xs text-text-muted">{o.sub}</p>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-3 bg-orange-50 rounded-xl text-xs text-orange-700">
|
||
Szacowany koszt montażu dla Twojej konfiguracji:
|
||
<strong className="ml-1">{installCost.min.toLocaleString('pl-PL')}–{installCost.max.toLocaleString('pl-PL')} zł</strong>
|
||
</div>
|
||
|
||
<div className="mt-3 p-4 bg-blue-50 border border-blue-200 rounded-xl">
|
||
<p className="text-sm font-semibold text-blue-900 mb-1">📐 Chcesz dokładną wycenę?</p>
|
||
<p className="text-xs text-blue-800 leading-relaxed">
|
||
Skontaktuj się z nami — wycena jest <strong>bezpłatna i niezobowiązująca</strong>.
|
||
Aby przyspieszyć wycenę, wyślij nam zdjęcia:
|
||
</p>
|
||
<ul className="mt-2 flex flex-col gap-1">
|
||
{[
|
||
'Miejsca gdzie ma wisieć jednostka wewnętrzna',
|
||
'Ściany przez które przejdzie instalacja',
|
||
'Miejsca gdzie stanie jednostka zewnętrzna',
|
||
'Skrzynki elektrycznej (bezpieczniki)',
|
||
].map((item, i) => (
|
||
<li key={i} className="flex items-start gap-2 text-xs text-blue-800">
|
||
<span className="text-blue-500 shrink-0">→</span>
|
||
{item}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
<a href="/kontakt" className="mt-3 inline-flex items-center gap-1.5 text-xs font-bold text-white bg-accent px-3 py-1.5 rounded-lg hover:bg-accent/90 transition-colors">
|
||
Wyślij zdjęcia i zapytaj o wycenę →
|
||
</a>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Krok 3: Budżet */}
|
||
{step === 3 && (
|
||
<div className="flex flex-col gap-6">
|
||
<div className="flex items-center gap-3 mb-2">
|
||
<div className="size-10 rounded-xl bg-green-50 flex items-center justify-center">
|
||
<ShoppingCart className="size-5 text-green-600" />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-lg font-bold text-primary font-heading">Całkowity budżet</h2>
|
||
<p className="text-xs text-text-muted">Urządzenie + montaż łącznie</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-1.5">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs text-text-muted">Budżet</span>
|
||
<span className="text-xs font-bold text-primary bg-accent/10 px-2 py-0.5 rounded-md">{budget.toLocaleString('pl-PL')} zł</span>
|
||
</div>
|
||
<input
|
||
type="range" min={3000} max={25000} step={500} value={budget}
|
||
onChange={e => setBudget(Number(e.target.value))}
|
||
className="range range-xs range-accent w-full"
|
||
/>
|
||
<div className="flex justify-between text-xs text-text-muted">
|
||
<span>3 000 zł</span>
|
||
<span>25 000 zł</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div className="bg-slate-50 rounded-xl p-3">
|
||
<p className="text-xs text-text-muted mb-1">Na urządzenie zostaje</p>
|
||
<p className="text-base font-bold text-primary font-heading">
|
||
ok. {(budget * 0.75).toLocaleString('pl-PL')} zł
|
||
</p>
|
||
</div>
|
||
<div className="bg-orange-50 rounded-xl p-3">
|
||
<p className="text-xs text-orange-700 mb-1">Szac. koszt montażu</p>
|
||
<p className="text-base font-bold text-orange-700 font-heading">
|
||
{installCost.min.toLocaleString('pl-PL')}–{installCost.max.toLocaleString('pl-PL')} zł
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-3 bg-blue-50 rounded-xl text-xs text-blue-700">
|
||
💡 Podsumowanie: szukamy klimatyzatora <strong>{category === 'scienne' ? 'split ściennego' : 'multisplit'}</strong> o mocy ok. <strong>{requiredPowerRounded} kW</strong> w cenie do <strong>{(budget * 0.75).toLocaleString('pl-PL')} zł</strong>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">
|
||
Preferowana marka (opcjonalnie)
|
||
</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{['Bez preferencji', 'Gree', 'Rotenso', 'Daikin', 'Haier', 'LG', 'Samsung', 'Mitsubishi Electric', 'Toshiba', 'Fuji Electric'].map(b => (
|
||
<button
|
||
key={b}
|
||
onClick={() => setPreferredBrand(b === 'Bez preferencji' ? '' : b)}
|
||
className={`px-3 py-1.5 rounded-xl text-xs font-semibold border-2 transition-all ${
|
||
(b === 'Bez preferencji' && preferredBrand === '') || preferredBrand === b
|
||
? 'border-accent bg-accent text-white'
|
||
: 'border-border bg-white text-primary hover:border-accent'
|
||
}`}
|
||
>
|
||
{b}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">
|
||
Klasa energetyczna (opcjonalnie)
|
||
</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{['Bez preferencji', 'A+++', 'A++', 'A+'].map(c => (
|
||
<button
|
||
key={c}
|
||
onClick={() => setEnergyClass(c === 'Bez preferencji' ? '' : c)}
|
||
className={`px-3 py-1.5 rounded-xl text-xs font-semibold border-2 transition-all ${
|
||
(c === 'Bez preferencji' && energyClass === '') || energyClass === c
|
||
? 'border-accent bg-accent text-white'
|
||
: 'border-border bg-white text-primary hover:border-accent'
|
||
}`}
|
||
>
|
||
{c}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">
|
||
Maksymalna głośność wewnątrz (opcjonalnie)
|
||
</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{[
|
||
{ label: 'Bez znaczenia', val: null },
|
||
{ label: 'Bardzo ciche (do 20 dB)', val: 20 },
|
||
{ label: 'Ciche (do 25 dB)', val: 25 },
|
||
{ label: 'Standardowe (do 30 dB)', val: 30 }
|
||
].map(n => (
|
||
<button
|
||
key={n.label}
|
||
onClick={() => setMaxNoise(n.val)}
|
||
className={`px-3 py-1.5 rounded-xl text-xs font-semibold border-2 transition-all ${
|
||
maxNoise === n.val
|
||
? 'border-accent bg-accent text-white'
|
||
: 'border-border bg-white text-primary hover:border-accent'
|
||
}`}
|
||
>
|
||
{n.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="text-sm font-semibold text-primary mb-3 block">
|
||
Maksymalna głośność zewnątrz (opcjonalnie)
|
||
</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{[
|
||
{ label: 'Bez znaczenia', val: null },
|
||
{ label: 'Bardzo ciche (do 45 dB)', val: 45 },
|
||
{ label: 'Ciche (do 50 dB)', val: 50 },
|
||
{ label: 'Standardowe (do 55 dB)', val: 55 }
|
||
].map(n => (
|
||
<button
|
||
key={n.label}
|
||
onClick={() => setMaxNoiseOut(n.val)}
|
||
className={`px-3 py-1.5 rounded-xl text-xs font-semibold border-2 transition-all ${
|
||
maxNoiseOut === n.val
|
||
? 'border-accent bg-accent text-white'
|
||
: 'border-border bg-white text-primary hover:border-accent'
|
||
}`}
|
||
>
|
||
{n.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Krok 4: Wyniki */}
|
||
{step === 4 && deviceType === 'pompa' && (
|
||
<div className="flex flex-col gap-6 text-center">
|
||
<div className="bg-white rounded-2xl p-8">
|
||
<h2 className="text-2xl font-extrabold text-primary font-heading mb-4">
|
||
Wycena pompy ciepła
|
||
</h2>
|
||
<p className="text-text-muted mb-8 text-sm">
|
||
Każda instalacja pompy ciepła wymaga indywidualnego doboru urządzenia do specyfiki budynku (izolacja, instalacja grzewcza).
|
||
Skontaktuj się z nami, a przygotujemy dla Ciebie <strong>bezpłatną, spersonalizowaną ofertę</strong>.
|
||
</p>
|
||
<a href={`tel:${companyDetails.phoneRaw}`}>
|
||
<button className="flex items-center gap-2 px-6 py-3 bg-accent text-white rounded-xl font-bold mx-auto hover:bg-accent/90 transition-colors">
|
||
<Phone className="size-4" /> {companyDetails.phone}
|
||
</button>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{step === 4 && deviceType !== 'pompa' && (
|
||
<div className="flex flex-col gap-6">
|
||
{/* Podsumowanie */}
|
||
<div className="bg-slate-50 rounded-2xl border border-border p-5">
|
||
<h2 className="text-lg font-bold text-primary font-heading mb-4">
|
||
Dopasowane klimatyzatory
|
||
</h2>
|
||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||
{[
|
||
{ label: 'Wymagana moc', value: `${requiredPowerRounded} kW` },
|
||
{ label: 'Powierzchnia', value: `${totalM2} m²` },
|
||
{ label: 'Typ instalacji', value: category === 'scienne' ? 'Split (1 pokój)' : `Multisplit (${rooms} pokoje)` },
|
||
{ label: 'Szac. montaż', value: `${installCost.min.toLocaleString('pl-PL')}–${installCost.max.toLocaleString('pl-PL')} zł` },
|
||
...(energyClass ? [{ label: 'Klasa energetyczna', value: energyClass }] : []),
|
||
...(maxNoise ? [{ label: 'Max. hałas wewn.', value: `do ${maxNoise} dB` }] : []),
|
||
...(maxNoiseOut ? [{ label: 'Max. hałas zewn.', value: `do ${maxNoiseOut} dB` }] : []),
|
||
].map((s, i) => (
|
||
<div key={i} className="bg-white rounded-xl p-3 border border-border">
|
||
<p className="text-xs text-text-muted mb-1">{s.label}</p>
|
||
<p className="text-sm font-bold text-primary font-heading">{s.value}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{results.length > 0 ? (
|
||
<div className="flex flex-col gap-8">
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-5">
|
||
{paginatedResults.map(product => (
|
||
<ProductCard key={product.slug} product={product} />
|
||
))}
|
||
</div>
|
||
{totalPages > 1 && (
|
||
<div className="flex flex-wrap items-center justify-center gap-2">
|
||
{getPageNumbers().map((p, i) => (
|
||
p === '...' ? (
|
||
<span key={i} className="px-2 text-text-muted font-bold">...</span>
|
||
) : (
|
||
<button
|
||
key={i}
|
||
onClick={() => {
|
||
setPage(p as number);
|
||
// Drobne przewinięcie na początek wyników dopasowania po zmianie strony
|
||
window.scrollTo({ top: 150, behavior: 'smooth' });
|
||
}}
|
||
className={`size-10 flex items-center justify-center rounded-xl font-bold text-sm transition-colors ${
|
||
page === p ? 'bg-accent text-white' : 'bg-white border border-border text-primary hover:border-accent'
|
||
}`}
|
||
>
|
||
{p}
|
||
</button>
|
||
)
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<div className="bg-slate-50 rounded-2xl border border-border p-8 text-center">
|
||
<p className="text-text-muted mb-4 text-sm">
|
||
Nie znaleziono produktów dla podanych kryteriów. Spróbuj zwiększyć budżet lub zadzwoń do nas — dobierzemy ofertę indywidualnie.
|
||
</p>
|
||
<a href={`tel:${companyDetails.phoneRaw}`}>
|
||
<button className="flex items-center gap-2 px-5 py-2.5 bg-accent text-white rounded-xl font-bold mx-auto hover:bg-accent/90 transition-colors text-sm">
|
||
<Phone className="size-4" /> {companyDetails.phone}
|
||
</button>
|
||
</a>
|
||
</div>
|
||
)}
|
||
|
||
<div className="p-4 bg-amber-50 border border-amber-200 rounded-xl mt-2">
|
||
<p className="text-xs font-bold text-amber-800 mb-1">⚠️ Ceny mają charakter orientacyjny</p>
|
||
<p className="text-xs text-amber-700 mb-3">
|
||
Podane ceny urządzeń są cenami brutto (23% VAT). Koszt montażu jest szacunkowy. Zachęcamy do darmowej wyceny.
|
||
</p>
|
||
<div className="flex flex-col sm:flex-row gap-2">
|
||
<a href={`tel:${companyDetails.phoneRaw}`}>
|
||
<button className="flex items-center gap-2 px-4 py-2 bg-accent text-white rounded-xl text-xs font-bold hover:bg-accent/90 transition-colors">
|
||
<Phone className="size-3.5" /> Bezpłatna wycena
|
||
</button>
|
||
</a>
|
||
<Link href="/kontakt" className="flex items-center gap-2 px-4 py-2 bg-amber-100 text-amber-800 rounded-xl text-xs font-bold hover:bg-amber-200 transition-colors border border-amber-300">
|
||
|
||
Prześlij zdjęcia
|
||
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Nawigacja */}
|
||
<div className="flex justify-between mt-8 pt-6 border-t border-border">
|
||
{step > 0 ? (
|
||
<button onClick={() => setStep(s => s - 1)}
|
||
className="flex items-center gap-2 px-4 py-2.5 border border-border rounded-xl text-sm font-semibold text-primary hover:border-accent transition-colors">
|
||
<ArrowLeft className="size-4" /> Wstecz
|
||
</button>
|
||
) : <div />}
|
||
|
||
{step < STEPS.length - 1 ? (
|
||
!(step === 0 && deviceType === 'pompa') && (
|
||
<button onClick={handleNext}
|
||
className="flex items-center gap-2 px-6 py-2.5 bg-accent text-white rounded-xl text-sm font-bold hover:bg-accent/90 transition-colors">
|
||
Dalej <ArrowRight className="size-4" />
|
||
</button>
|
||
)
|
||
) : (
|
||
<Link href="/sklep" className="flex items-center gap-2 px-6 py-2.5 bg-accent text-white rounded-xl text-sm font-bold hover:bg-accent/90 transition-colors">
|
||
|
||
Przejdź do sklepu <ArrowRight className="size-4" />
|
||
|
||
</Link>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|