feat: Zaktualizuj sekcję hero, nawigację, kalkulator i formularz zgłoszenia awarii
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { AlertTriangle, Wrench, Phone, Check, Clock, Zap } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { companyDetails } from '@/content/company';
|
||||
import { submitAwariaForm } from '@/app/zgloszenie-awarii/actions';
|
||||
|
||||
// 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 [preferredDate, setPreferredDate] = useState('');
|
||||
const [preferredTime, setPreferredTime] = useState('');
|
||||
const [consent, setConsent] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Podpowiedź kodu błędu
|
||||
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);
|
||||
|
||||
await submitAwariaForm({
|
||||
isUrgent,
|
||||
brand,
|
||||
model,
|
||||
errorCode,
|
||||
problemType,
|
||||
description,
|
||||
warranty,
|
||||
lastService,
|
||||
name,
|
||||
phone,
|
||||
city,
|
||||
preferredDate,
|
||||
preferredTime,
|
||||
});
|
||||
|
||||
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 text-center">
|
||||
<div className="size-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<Check className="size-10 text-green-600" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-extrabold text-primary font-heading mb-3">
|
||||
Zgłoszenie przyjęte!
|
||||
</h1>
|
||||
<p className="text-text-muted 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-text-muted mb-8">
|
||||
Jeśli sprawa jest pilna — zadzwoń bezpośrednio:
|
||||
</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 mb-4">
|
||||
<Phone className="size-4" /> {companyDetails.phone}
|
||||
</button>
|
||||
</a>
|
||||
<Link href="/">
|
||||
<button className="text-sm text-text-muted hover:text-accent transition-colors">
|
||||
Wróć na stronę główną
|
||||
</button>
|
||||
</Link>
|
||||
</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">
|
||||
{/* Header */}
|
||||
<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>
|
||||
|
||||
{/* Pilne zgłoszenie toggle */}
|
||||
<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">
|
||||
|
||||
{/* Sekcja 1: Urządzenie */}
|
||||
<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 border border-border rounded-xl px-3 py-2.5 text-sm 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 border border-border rounded-xl px-3 py-2.5 text-sm focus:border-accent outline-none"
|
||||
placeholder="Np. Gree Pular, LG Artcool 3,5 kW..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid 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 border border-border rounded-xl px-3 py-2.5 text-sm 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 border border-border rounded-xl px-3 py-2.5 text-sm 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>
|
||||
|
||||
{/* Sekcja 2: Problem */}
|
||||
<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>
|
||||
|
||||
{/* Kod błędu z podpowiedzią */}
|
||||
<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 border border-border rounded-xl px-3 py-2.5 text-sm 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 border border-border rounded-xl px-3 py-2.5 text-sm 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>
|
||||
|
||||
{/* Sekcja 3: Termin */}
|
||||
<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">
|
||||
<Clock className="size-4 text-accent" /> Preferowany termin
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-xs font-medium text-primary mb-1 block">Data</label>
|
||||
<input
|
||||
type="date" value={preferredDate} onChange={e => setPreferredDate(e.target.value)}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
className="w-full border border-border rounded-xl px-3 py-2.5 text-sm focus:border-accent outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-medium text-primary mb-1 block">Godzina</label>
|
||||
<select
|
||||
value={preferredTime} onChange={e => setPreferredTime(e.target.value)}
|
||||
className="w-full border border-border rounded-xl px-3 py-2.5 text-sm focus:border-accent outline-none bg-white"
|
||||
>
|
||||
<option value="">Dowolna</option>
|
||||
<option value="8:00-10:00">8:00 – 10:00</option>
|
||||
<option value="10:00-12:00">10:00 – 12:00</option>
|
||||
<option value="12:00-14:00">12:00 – 14:00</option>
|
||||
<option value="14:00-16:00">14:00 – 16:00</option>
|
||||
<option value="16:00-18:00">16:00 – 18:00</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sekcja 4: Dane kontaktowe */}
|
||||
<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 border border-border rounded-xl px-3 py-2.5 text-sm 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 border border-border rounded-xl px-3 py-2.5 text-sm 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 border border-border rounded-xl px-3 py-2.5 text-sm 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>
|
||||
|
||||
{/* Zgoda RODO */}
|
||||
<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>
|
||||
|
||||
{/* Submit */}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user