Files
thermcool/components/awaria/AwariaClient.tsx
T
MichalC c9e4283789 feat: filtr jednostek multisplit, ostatnio oglądane, sklep pompy ciepła, czyszczenie komentarzy, checkbox zgody
- Filtr 'Ilość jednostek wewn.' w sklepie (1–9 jednostek, checkboxy, liczniki)
- Komponent RecentlyViewedProducts + Zustand store (recently-viewed-store.ts)
- Pompy ciepła dodane do sklepu i nawigacji (Header + MobileMenu)
- Logotypy marek zaktualizowane (Daikin, Panasonic + odświeżone istniejące)
- Checkbox zgody RODO w /zamowienie powiększony (w-5 h-5)
- Usunięcie ~65 zbędnych komentarzy WHAT z 12 plików (build: 0 errors)
- Plik analyze_products.ts (pomocniczy skrypt diagnostyczny)
2026-08-02 20:31:55 +02:00

399 lines
18 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Wrench, Zap, Check, AlertTriangle, Phone, CheckCircle2 } from 'lucide-react';
import { companyDetails } from '@/content/company';
import { submitAwariaForm } from '@/app/zgloszenie-awarii/actions';
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
// Kody błędów popularnych marek
const ERROR_CODES: Record<string, Record<string, string>> = {
'Gree': {
'E1': 'Błąd czujnika temperatury powietrza wewnętrznego',
'E2': 'Błąd czujnika temperatury wymiennika wewnętrznego',
'E3': 'Błąd czujnika temperatury zewnętrznej',
'E4': 'Wysokie ciśnienie — możliwy problem ze sprężarką lub skraplaczem',
'E5': 'Niskie ciśnienie — możliwy brak czynnika chłodniczego',
'E6': 'Błąd komunikacji między jednostkami',
'F1': 'Awaria czujnika temperatury — wymagana wymiana czujnika',
'H6': 'Blokada silnika wentylatora wewnętrznego',
},
'LG': {
'CH01': 'Błąd czujnika temperatury powietrza wewnętrznego',
'CH02': 'Błąd czujnika wymiennika wewnętrznego',
'CH03': 'Błąd czujnika wymiennika zewnętrznego',
'CH05': 'Błąd komunikacji — sprawdź okablowanie',
'CH07': 'Awaria wentylatora zewnętrznego',
'CH10': 'Błąd czujnika prądu sprężarki',
'CH38': 'Błąd modułu WiFi',
'C1': 'Problem z drenażem — sprawdź odpływ skroplin',
},
'Daikin': {
'A1': 'Awaria PCB — płyta główna jednostki wewnętrznej',
'A5': 'Przegrzanie — wymagane czyszczenie lub sprawdzenie czynnika',
'C4': 'Błąd czujnika wymiennika wewnętrznego',
'E1': 'Awaria PCB jednostki zewnętrznej',
'E3': 'Wyłącznik wysokiego ciśnienia',
'E4': 'Wyłącznik niskiego ciśnienia',
'F3': 'Niskie ciśnienie oleju sprężarki',
'U4': 'Błąd komunikacji wewnętrzna-zewnętrzna',
},
'Samsung': {
'E1': 'Błąd czujnika temperatury powietrza',
'E2': 'Błąd czujnika wymiennika ciepła',
'E3': 'Błąd silnika wentylatora',
'E4': 'Wyłącznik wysokiego ciśnienia',
'E6': 'Błąd komunikacji',
'F1': 'Awaria czujnika — wymagana diagnostyka',
'C4': 'Błąd czujnika temperatury zewnętrznej',
},
'Haier': {
'E1': 'Błąd czujnika temperatury wewnętrznej',
'E2': 'Błąd czujnika wymiennika wewnętrznego',
'E3': 'Błąd czujnika temperatury zewnętrznej',
'E4': 'Wyłącznik wysokiego ciśnienia',
'E5': 'Wyłącznik niskiego ciśnienia — sprawdź poziom czynnika',
'E6': 'Błąd komunikacji',
'F1': 'Awaria czujnika — wymiana',
},
'Rotenso': {
'E1': 'Błąd czujnika temperatury wewnętrznej',
'E2': 'Błąd wymiennika wewnętrznego',
'E3': 'Błąd czujnika zewnętrznego',
'E5': 'Niskie ciśnienie — sprawdź czynnik chłodniczy',
'E6': 'Błąd komunikacji między jednostkami',
'P1': 'Ochrona przed zamrożeniem',
'P4': 'Wyłącznik wysokiego ciśnienia',
},
'Mitsubishi Electric': {
'E1': 'Błąd czujnika wewnętrznego',
'E2': 'Błąd czujnika wymiennika',
'E4': 'Błąd czujnika zewnętrznego',
'E6': 'Problem z wentylatorem zewnętrznym',
'P1': 'Błąd czujnika powietrza zewnętrznego',
'P4': 'Wyłącznik wysokiego ciśnienia',
'P8': 'Przegrzanie sprężarki',
'U1': 'Błąd napięcia zasilania',
},
'Toshiba': {
'E01': 'Błąd czujnika temperatury wewnętrznej',
'E02': 'Błąd czujnika wymiennika wewnętrznego',
'E03': 'Błąd czujnika zewnętrznego',
'E04': 'Wyłącznik wysokiego ciśnienia',
'E07': 'Błąd komunikacji',
'E08': 'Awaria wentylatora zewnętrznego',
},
};
const BRANDS = ['Gree', 'Rotenso', 'Haier', 'LG', 'Samsung', 'Daikin', 'Mitsubishi Electric', 'Toshiba', 'Aux', 'Fuji Electric', 'Inna marka'];
const PROBLEM_TYPES = [
'Nie chłodzi / nie grzeje',
'Wyświetla kod błędu',
'Głośna praca / wibracje',
'Wyciek wody',
'Brzydki zapach',
'Nie włącza się',
'Pilot nie działa',
'Zamarza wymiennik',
'Inne',
];
export function AwariaClient() {
const [step, setStep] = useState<'form' | 'success'>('form');
const [isUrgent, setIsUrgent] = useState(false);
const [brand, setBrand] = useState('');
const [model, setModel] = useState('');
const [errorCode, setErrorCode] = useState('');
const [problemType, setProblemType] = useState('');
const [description, setDescription] = useState('');
const [lastService, setLastService] = useState('');
const [warranty, setWarranty] = useState('');
const [name, setName] = useState('');
const [phone, setPhone] = useState('');
const [city, setCity] = useState('');
const [consent, setConsent] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const errorHint = brand && errorCode && ERROR_CODES[brand]?.[errorCode.toUpperCase()];
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!consent) {
alert('Proszę zaznaczyć zgodę na przetwarzanie danych.');
return;
}
setIsSubmitting(true);
const recaptchaToken = await getRecaptchaToken('submit');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const formDataObj: any = {
isUrgent,
brand,
model,
errorCode,
problemType,
description,
warranty,
lastService,
name,
phone,
city,
preferredDate: '',
preferredTime: '',
};
if (recaptchaToken) formDataObj.recaptchaToken = recaptchaToken;
await submitAwariaForm(formDataObj);
setIsSubmitting(false);
setStep('success');
};
if (step === 'success') {
return (
<main className="bg-slate-50 min-h-screen flex items-center justify-center py-16">
<div className="max-w-md mx-auto px-4 w-full">
<div className="bg-green-50 text-green-800 p-8 rounded-2xl border border-green-200 flex flex-col items-center text-center">
<CheckCircle2 className="w-16 h-16 text-green-500 mb-4" />
<h3 className="text-2xl font-bold font-heading mb-2">Zgłoszenie przyjęte!</h3>
<p className="text-green-700 mb-2">
{isUrgent
? 'Twoje zgłoszenie oznaczono jako PILNE — skontaktujemy się z Tobą w ciągu 2 godzin.'
: 'Skontaktujemy się z Tobą w ciągu 24 godzin, aby ustalić termin wizyty serwisanta.'}
</p>
<p className="text-sm text-green-700/80 mb-6">
Jeśli sprawa jest pilna — zadzwoń bezpośrednio: {companyDetails.phone}
</p>
<button
onClick={() => setStep('form')}
className="mt-2 px-6 py-2 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition-colors"
>
Wyślij kolejne zgłoszenie
</button>
</div>
</div>
</main>
);
}
return (
<main className="bg-slate-50 min-h-screen py-12">
<div className="max-w-2xl mx-auto px-4 sm:px-6">
<div className="text-center mb-8">
<div className="size-14 bg-red-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Wrench className="size-7 text-red-600" />
</div>
<h1 className="text-3xl font-extrabold text-primary font-heading mb-2">
Zgłoś awarię / serwis
</h1>
<p className="text-text-muted">Wypełnij formularz — oddzwonimy i ustalimy termin wizyty</p>
</div>
<div
onClick={() => setIsUrgent(!isUrgent)}
className={`flex items-center gap-4 p-4 rounded-2xl border-2 cursor-pointer mb-6 transition-all ${
isUrgent ? 'border-red-400 bg-red-50' : 'border-border bg-white hover:border-red-300'
}`}
>
<div className={`size-10 rounded-xl flex items-center justify-center shrink-0 ${isUrgent ? 'bg-red-500' : 'bg-slate-100'}`}>
<AlertTriangle className={`size-5 ${isUrgent ? 'text-white' : 'text-text-muted'}`} />
</div>
<div className="flex-1">
<p className={`font-bold text-sm ${isUrgent ? 'text-red-700' : 'text-primary'}`}>
{isUrgent ? '🚨 ZGŁOSZENIE PILNE — kontakt w ciągu 2 godzin' : 'Oznacz jako PILNE'}
</p>
<p className="text-xs text-text-muted">Np. upał, brak chłodzenia, awaria krytyczna</p>
</div>
<div className={`size-6 rounded-full border-2 flex items-center justify-center shrink-0 ${isUrgent ? 'border-red-500 bg-red-500' : 'border-slate-300'}`}>
{isUrgent && <Check className="size-3.5 text-white" />}
</div>
</div>
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
<div className="bg-white rounded-2xl border border-border p-6">
<h2 className="text-base font-bold text-primary font-heading mb-4 flex items-center gap-2">
<Zap className="size-4 text-accent" /> Urządzenie
</h2>
<div className="flex flex-col gap-4">
<div>
<label className="text-xs font-medium text-primary mb-1 block">Marka *</label>
<select
required value={brand} onChange={e => { setBrand(e.target.value); setErrorCode(''); }}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
>
<option value="">Wybierz markę...</option>
{BRANDS.map(b => <option key={b} value={b}>{b}</option>)}
</select>
</div>
<div>
<label className="text-xs font-medium text-primary mb-1 block">Model (opcjonalnie)</label>
<input
value={model} onChange={e => setModel(e.target.value)}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
placeholder="Np. Gree Pular, LG Artcool 3,5 kW..."
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="text-xs font-medium text-primary mb-1 block">Na gwarancji?</label>
<select
value={warranty} onChange={e => setWarranty(e.target.value)}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
>
<option value="">Nie wiem</option>
<option value="tak">Tak</option>
<option value="nie">Nie</option>
</select>
</div>
<div>
<label className="text-xs font-medium text-primary mb-1 block">Ostatni serwis</label>
<select
value={lastService} onChange={e => setLastService(e.target.value)}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
>
<option value="">Nie pamiętam</option>
<option value="<1rok">Mniej niż rok temu</option>
<option value="1-2lata">1-2 lata temu</option>
<option value="2-5lat">2-5 lat temu</option>
<option value=">5lat">Ponad 5 lat temu</option>
<option value="nigdy">Nigdy nie był serwisowany</option>
</select>
</div>
</div>
</div>
</div>
<div className="bg-white rounded-2xl border border-border p-6">
<h2 className="text-base font-bold text-primary font-heading mb-4 flex items-center gap-2">
<AlertTriangle className="size-4 text-accent" /> Opis problemu
</h2>
<div className="flex flex-col gap-4">
<div>
<label className="text-xs font-medium text-primary mb-2 block">Rodzaj problemu *</label>
<div className="flex flex-wrap gap-2">
{PROBLEM_TYPES.map(p => (
<button
key={p} type="button"
onClick={() => setProblemType(p)}
className={`px-3 py-1.5 rounded-xl text-xs font-semibold border-2 transition-all ${
problemType === p ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'
}`}
>
{p}
</button>
))}
</div>
</div>
<div>
<label className="text-xs font-medium text-primary mb-1 block">
Kod błędu z wyświetlacza (opcjonalnie)
</label>
<input
value={errorCode}
onChange={e => setErrorCode(e.target.value)}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
placeholder="Np. E1, F3, CH05..."
/>
{errorHint && (
<div className="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-xl text-xs text-blue-700">
<span className="font-bold">Kod {errorCode.toUpperCase()}:</span> {errorHint}
</div>
)}
{errorCode && !errorHint && brand && ERROR_CODES[brand] && (
<p className="mt-1 text-xs text-text-muted">Kodu nie rozpoznano — serwisant zdiagnozuje na miejscu.</p>
)}
</div>
<div>
<label className="text-xs font-medium text-primary mb-1 block">Szczegółowy opis</label>
<textarea
value={description} onChange={e => setDescription(e.target.value)}
rows={3}
className="w-full rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none resize-none"
placeholder="Opisz kiedy się pojawił problem, jak często występuje, czy jest jakiś charakterystyczny dźwięk..."
/>
</div>
</div>
</div>
<div className="bg-white rounded-2xl border border-border p-6">
<h2 className="text-base font-bold text-primary font-heading mb-4 flex items-center gap-2">
<Phone className="size-4 text-accent" /> Dane kontaktowe
</h2>
<div className="flex flex-col gap-4">
<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
required value={name} onChange={e => setName(e.target.value)}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
placeholder="Jan Kowalski"
/>
</div>
<div>
<label className="text-xs font-medium text-primary mb-1 block">Telefon *</label>
<input
required type="tel" value={phone} onChange={e => setPhone(e.target.value)}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
placeholder="+48 600 000 000"
/>
</div>
</div>
<div>
<label className="text-xs font-medium text-primary mb-1 block">Miasto *</label>
<select
required value={city} onChange={e => setCity(e.target.value)}
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
>
<option value="">Wybierz miasto...</option>
{['Wrocław', 'Legnica', 'Wałbrzych', 'Jelenia Góra', 'Lubin', 'Głogów', 'Świdnica', 'Dzierżoniów', 'Oleśnica', 'Oława', 'Trzebnica', 'Środa Śląska', 'Siechnice', 'Inne'].map(c => (
<option key={c} value={c}>{c}</option>
))}
</select>
</div>
</div>
</div>
<div
className="flex items-start gap-3 cursor-pointer"
onClick={() => setConsent(!consent)}
>
<div className={`size-5 rounded border-2 shrink-0 mt-0.5 flex items-center justify-center transition-colors ${
consent ? 'bg-accent border-accent' : 'bg-white border-slate-300'
}`}>
{consent && <Check className="size-3 text-white" />}
</div>
<span className="text-xs text-text-muted leading-relaxed">
Wyrażam zgodę na przetwarzanie danych osobowych podanych w formularzu
w celu obsługi zgłoszenia serwisowego przez firmę ThermCool. *
</span>
</div>
<button
type="submit"
disabled={isSubmitting || !brand || !problemType || !name || !phone || !city || !consent}
className={`flex items-center justify-center gap-2 py-4 px-6 rounded-2xl font-bold text-base transition-all ${
isUrgent
? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-accent hover:bg-accent/90 text-white'
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
{isSubmitting ? 'Wysyłanie...' : isUrgent ? '🚨 Wyślij PILNE zgłoszenie' : 'Wyślij zgłoszenie serwisowe'}
</button>
<p className="text-xs text-text-muted text-center">
{isUrgent ? 'Odpowiadamy na pilne zgłoszenia w ciągu 2 godzin w godzinach pracy.' : 'Skontaktujemy się w ciągu 24 godzin.'}
{' '}Możesz też zadzwonić bezpośrednio: <a href={`tel:${companyDetails.phoneRaw}`} className="text-accent font-semibold">{companyDetails.phone}</a>
</p>
</form>
</div>
</main>
);
}