From c9e4283789d8ef4b574211eb6c31840f2d08d86d Mon Sep 17 00:00:00 2001 From: MichalC <103170279+wSBMichalee@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:31:55 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20filtr=20jednostek=20multisplit,=20ostat?= =?UTF-8?q?nio=20ogl=C4=85dane,=20sklep=20pompy=20ciep=C5=82a,=20czyszczen?= =?UTF-8?q?ie=20komentarzy,=20checkbox=20zgody?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- analyze_products.ts | 10 +++ app/cennik-montazu/page.tsx | 4 +- app/globals.css | 14 ++- app/klimatyzacja-do-biura/page.tsx | 4 +- app/klimatyzacja-do-domu/page.tsx | 4 +- app/montaz-klimatyzacji/page.tsx | 2 +- app/serwis-daikin/page.tsx | 4 +- app/serwis-klimatyzacji/page.tsx | 46 +++++++--- app/serwis-lg/page.tsx | 2 +- app/serwis-mitsubishi/page.tsx | 4 +- app/serwis-samsung/page.tsx | 4 +- app/zamowienie/page.tsx | 2 +- components/CookieBanner.tsx | 7 -- components/awaria/AwariaClient.tsx | 9 -- components/forms/ContactForm.tsx | 49 +++++++++- components/layout/Header.tsx | 15 ++++ components/layout/MobileMenu.tsx | 31 ++++++- components/sections/ContactSection.tsx | 6 +- components/sections/HeroSection.tsx | 7 +- components/shop/CartItems.tsx | 5 -- components/shop/CartSummary.tsx | 5 -- components/shop/ProductCard.tsx | 8 -- components/shop/ProductGallery.tsx | 3 - components/shop/ProductPageClient.tsx | 30 ++----- components/shop/ProductsGrid.tsx | 99 ++++++++++++++++----- components/shop/RecentlyViewedProducts.tsx | 34 +++++++ content/products.ts | 13 ++- content/services.ts | 4 +- lib/recently-viewed-store.ts | 29 ++++++ public/brands/chv.webp | Bin 5438 -> 3330 bytes public/brands/daikin.webp | Bin 0 -> 4446 bytes public/brands/gree.webp | Bin 46458 -> 35328 bytes public/brands/haier.webp | Bin 18008 -> 32842 bytes public/brands/mitsubishi.webp | Bin 20012 -> 17804 bytes public/brands/panasonic.webp | Bin 0 -> 4364 bytes public/brands/samsung.webp | Bin 19682 -> 20138 bytes public/brands/toshiba.webp | Bin 15162 -> 12628 bytes 37 files changed, 331 insertions(+), 123 deletions(-) create mode 100644 analyze_products.ts create mode 100644 components/shop/RecentlyViewedProducts.tsx create mode 100644 lib/recently-viewed-store.ts create mode 100644 public/brands/daikin.webp create mode 100644 public/brands/panasonic.webp diff --git a/analyze_products.ts b/analyze_products.ts new file mode 100644 index 0000000..a84dbed --- /dev/null +++ b/analyze_products.ts @@ -0,0 +1,10 @@ +import { products } from './content/products'; + +const values = new Set(); +products.filter(p => p.category === 'multisplit').forEach(p => { + if (p.specs && p.specs['Liczba jednostek wewn.']) { + values.add(parseFloat(p.specs['Liczba jednostek wewn.'])); + } +}); +const sorted = Array.from(values).sort((a, b) => a - b); +console.log('Unique unit counts:', sorted); diff --git a/app/cennik-montazu/page.tsx b/app/cennik-montazu/page.tsx index 732acdd..e930449 100644 --- a/app/cennik-montazu/page.tsx +++ b/app/cennik-montazu/page.tsx @@ -250,11 +250,11 @@ export default function CennikMontazuPage() {

- - + Napisz do nas diff --git a/app/globals.css b/app/globals.css index 3eedda6..b309207 100644 --- a/app/globals.css +++ b/app/globals.css @@ -209,4 +209,16 @@ color: #ffffff !important; border-color: var(--accent) !important; } -} \ No newline at end of file +} +@layer utilities { + @keyframes marquee { + 0% { transform: translateX(0); } + 100% { transform: translateX(calc(-50% - 0.5rem)); } + } + .animate-marquee { + animation: marquee 25s linear infinite; + } + .animate-marquee:hover { + animation-play-state: paused; + } +} diff --git a/app/klimatyzacja-do-biura/page.tsx b/app/klimatyzacja-do-biura/page.tsx index f1d6b8d..13c2e1f 100644 --- a/app/klimatyzacja-do-biura/page.tsx +++ b/app/klimatyzacja-do-biura/page.tsx @@ -276,11 +276,11 @@ export default function KlimatyzacjaDoBiuraPage() {

- - + Napisz do nas diff --git a/app/klimatyzacja-do-domu/page.tsx b/app/klimatyzacja-do-domu/page.tsx index b1491a1..1118867 100644 --- a/app/klimatyzacja-do-domu/page.tsx +++ b/app/klimatyzacja-do-domu/page.tsx @@ -245,11 +245,11 @@ export default function KlimatyzacjaDoDomuPage() {

- - + Kalkulator doboru diff --git a/app/montaz-klimatyzacji/page.tsx b/app/montaz-klimatyzacji/page.tsx index 87cb664..edaf8fa 100644 --- a/app/montaz-klimatyzacji/page.tsx +++ b/app/montaz-klimatyzacji/page.tsx @@ -253,7 +253,7 @@ export default function MontazKlimatyzacjiPage() { Zadzwoń po bezpłatną wycenę — odpowiadamy szybko i przyjeżdżamy w całym Dolnym Śląsku.

- diff --git a/app/serwis-daikin/page.tsx b/app/serwis-daikin/page.tsx index 4cec75e..b2193df 100644 --- a/app/serwis-daikin/page.tsx +++ b/app/serwis-daikin/page.tsx @@ -196,11 +196,11 @@ export default function SerwisDaikinPage() {

- - + Zgłoś online diff --git a/app/serwis-klimatyzacji/page.tsx b/app/serwis-klimatyzacji/page.tsx index d776307..d2e50aa 100644 --- a/app/serwis-klimatyzacji/page.tsx +++ b/app/serwis-klimatyzacji/page.tsx @@ -83,7 +83,18 @@ const USLUGI = [ }, ]; -const MARKI = ['Daikin', 'Mitsubishi Electric', 'LG', 'Samsung', 'Gree', 'Rotenso', 'Haier', 'Aux', 'Fuji Electric', 'Toshiba', 'Fujitsu', 'Bosch']; +const BRANDS_WITH_LOGOS = [ + { name: 'Aux', src: '/brands/aux-brand.webp' }, + { name: 'CHV', src: '/brands/chv.webp' }, + { name: 'Fuji Electric', src: '/brands/fuji.webp' }, + { name: 'Gree', src: '/brands/gree.webp' }, + { name: 'Haier', src: '/brands/haier.webp' }, + { name: 'LG', src: '/brands/lg.webp' }, + { name: 'Mitsubishi Electric', src: '/brands/mitsubishi.webp' }, + { name: 'Rotenso', src: '/brands/rotenso.webp' }, + { name: 'Samsung', src: '/brands/samsung.webp' }, + { name: 'Toshiba', src: '/brands/toshiba.webp' }, +]; export default function SerwisKlimatyzacjiPage() { const jsonLd = { @@ -189,16 +200,27 @@ export default function SerwisKlimatyzacjiPage() { {/* Marki */} -
+

Serwisujemy wszystkie popularne marki

-
- {MARKI.map(m => ( - - {m} - - ))} + +
+
+ {/* Pierwszy zestaw */} + {BRANDS_WITH_LOGOS.map(m => ( +
+ {m.name} +
+ ))} + {/* Drugi zestaw (klon) do płynnego przejścia */} + {BRANDS_WITH_LOGOS.map(m => ( +
+ {m.name} +
+ ))} +
+

Nie widzisz swojej marki? Zadzwoń — prawdopodobnie też ją obsługujemy.

@@ -234,15 +256,13 @@ export default function SerwisKlimatyzacjiPage() {

- - - + Zgłoś online - - +
diff --git a/app/serwis-lg/page.tsx b/app/serwis-lg/page.tsx index bb10995..9fa3fa6 100644 --- a/app/serwis-lg/page.tsx +++ b/app/serwis-lg/page.tsx @@ -191,7 +191,7 @@ export default function SerwisLGPage() { Zadzwoń lub napisz — odpowiadamy szybko i przyjeżdżamy na Dolny Śląsk.

- diff --git a/app/serwis-mitsubishi/page.tsx b/app/serwis-mitsubishi/page.tsx index 832c81c..9853573 100644 --- a/app/serwis-mitsubishi/page.tsx +++ b/app/serwis-mitsubishi/page.tsx @@ -200,11 +200,11 @@ export default function SerwisMitsubishiPage() {

- - + Zgłoś online diff --git a/app/serwis-samsung/page.tsx b/app/serwis-samsung/page.tsx index 8703e40..13ecb3f 100644 --- a/app/serwis-samsung/page.tsx +++ b/app/serwis-samsung/page.tsx @@ -196,11 +196,11 @@ export default function SerwisSamsungPage() {

- - + Zgłoś online diff --git a/app/zamowienie/page.tsx b/app/zamowienie/page.tsx index 0dae233..f77a5de 100644 --- a/app/zamowienie/page.tsx +++ b/app/zamowienie/page.tsx @@ -122,7 +122,7 @@ Wróć do sklepu
diff --git a/components/CookieBanner.tsx b/components/CookieBanner.tsx index d854207..4707ae0 100644 --- a/components/CookieBanner.tsx +++ b/components/CookieBanner.tsx @@ -59,7 +59,6 @@ export default function CookieBanner() { return (
- {/* Główny pasek */}
🍪 @@ -77,10 +76,8 @@ export default function CookieBanner() {
- {/* Szczegóły */} {showDetails && (
- {/* Niezbędne */}

Niezbędne

@@ -95,7 +92,6 @@ export default function CookieBanner() {
- {/* Analityczne */}

Analityczne

@@ -113,7 +109,6 @@ export default function CookieBanner() {
- {/* Marketingowe */}

Marketingowe

@@ -131,7 +126,6 @@ export default function CookieBanner() {
- {/* Reklamowe */}

Reklamowe

@@ -151,7 +145,6 @@ export default function CookieBanner() {
)} - {/* Przyciski */}
+ {/* Warunkowe pola dla serwisu */} + {watch('subject') === 'serwis' && ( +
+
+ + +
+
+ + +
+
+ )} + {/* Wiadomość */}
diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index fc1ae6c..4cd4b33 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -211,6 +211,21 @@ export function Header() { > Pompy ciepła + - {/* Dark Overlay */}
- {/* Drawer Panel */}
- {/* Drawer Header */}
Menu. @@ -83,7 +80,6 @@ export function MobileMenu({ isOpen, onClose }: MobileMenuProps) {
- {/* Drawer Content */}
{/* 1. Klimatyzacja */} @@ -143,6 +139,33 @@ export function MobileMenu({ isOpen, onClose }: MobileMenuProps) {
+ {/* Sklep */} +
+ + Sklep + +
+
    + {[ + { label: "Klimatyzatory ścienne", href: "/sklep?category=scienne" }, + { label: "Klimatyzatory multisplit", href: "/sklep?category=multisplit" }, + { label: "Pompy ciepła", href: "/sklep?category=pompy-ciepla" }, + ].map((item, idx) => ( +
  • + + {item.label} + + +
  • + ))} +
+
+
+ {/* Nowa sekcja: Miasta */}
diff --git a/components/sections/ContactSection.tsx b/components/sections/ContactSection.tsx index 4102e1e..829946a 100644 --- a/components/sections/ContactSection.tsx +++ b/components/sections/ContactSection.tsx @@ -24,7 +24,7 @@ export function ContactSection() {
@@ -37,7 +37,7 @@ export function ContactSection() {
@@ -50,7 +50,7 @@ export function ContactSection() {

Nasze biuro

-
+
{companyDetails.name}
{companyDetails.address.street}
{companyDetails.address.zipCode} {companyDetails.address.city} diff --git a/components/sections/HeroSection.tsx b/components/sections/HeroSection.tsx index 1b3519e..c4f4145 100644 --- a/components/sections/HeroSection.tsx +++ b/components/sections/HeroSection.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import Image from "next/image"; import { ArrowRight, Star } from "lucide-react"; +import { buttonClasses } from "@/components/ui/Button"; export interface HeroSectionProps { eyebrow?: string; @@ -31,7 +32,7 @@ export function HeroSection({ features }: HeroSectionProps = {}) { return ( -
+
{/* TODO ZDJĘCIE HERO: realne zdjęcie firmy/montażu, format poziomy min. 1920×1080, plik public/hero.jpg */} {ctaText && ctaLink && ( - + {ctaText} )} {secondaryCtaText && secondaryCtaLink && ( - + {secondaryCtaText} )} diff --git a/components/shop/CartItems.tsx b/components/shop/CartItems.tsx index 5c2c6bc..8c232cc 100644 --- a/components/shop/CartItems.tsx +++ b/components/shop/CartItems.tsx @@ -33,7 +33,6 @@ export function CartItems() { return (
- {/* Usuń pozycję */} - {/* Obraz */}
- {/* Treść */}
@@ -65,9 +62,7 @@ export function CartItems() {
- {/* Opcja Montażu i Cena jednostkowa */}
- {/* Ilość i Cena */}
- {/* Treść */}

@@ -127,7 +123,6 @@ export function ProductCard({ product }: ProductCardProps) {

- {/* Kluczowe specs */}
{product.specs['Moc chłodnicza'] && ( @@ -149,7 +144,6 @@ export function ProductCard({ product }: ProductCardProps) { {/* Cena + CTA */}
{product.category === 'multisplit' ? ( - // Multisplit — zawsze formularz zapytania <>
@@ -165,7 +159,6 @@ export function ProductCard({ product }: ProductCardProps) { ) : product.price !== null ? ( - // Split z ceną — koszyk <>

@@ -191,7 +184,6 @@ export function ProductCard({ product }: ProductCardProps) { )} ) : ( - // Split bez ceny — zapytaj <>

diff --git a/components/shop/ProductGallery.tsx b/components/shop/ProductGallery.tsx index fe99e43..92bdb77 100644 --- a/components/shop/ProductGallery.tsx +++ b/components/shop/ProductGallery.tsx @@ -12,14 +12,12 @@ interface ProductGalleryProps { export function ProductGallery({ image, name, images }: ProductGalleryProps) { const [mainImage, setMainImage] = useState(image || '/images/hero/shop-section-image.webp'); - // Tworzymy tablicę miniatur, używamy placeholdera jako drugiej by pokazać funkcjonalność const gallery = images && images.length > 0 ? images : [image, '/images/hero/shop-section-image.webp']; return (
- {/* Główne zdjęcie */}
- {/* Miniatury */} {gallery.length > 1 && (
{gallery.map((img, idx) => ( diff --git a/components/shop/ProductPageClient.tsx b/components/shop/ProductPageClient.tsx index 7f5f18c..72f3346 100644 --- a/components/shop/ProductPageClient.tsx +++ b/components/shop/ProductPageClient.tsx @@ -11,6 +11,9 @@ import { Product } from '@/content/products'; import { useCartStore } from '@/lib/cart-store'; import { useCompareStore } from '@/lib/compare-store'; import { getProductVariants } from '@/content/product-variants'; +import { useRecentlyViewedStore } from '@/lib/recently-viewed-store'; +import { RecentlyViewedProducts } from './RecentlyViewedProducts'; +import { useEffect } from 'react'; const brandLogos: Record = { 'Gree': '/brands/gree.webp', @@ -34,7 +37,6 @@ interface Props { companyPhoneRaw: string; } -// Szacowany SEER na podstawie klasy energetycznej function estimateSEER(klasa: string): number { if (!klasa) return 5.0; const k = klasa.toUpperCase(); @@ -45,7 +47,6 @@ function estimateSEER(klasa: string): number { 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); @@ -81,6 +82,11 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa const { addItem: addCompare, removeItem: removeCompare, isAdded } = useCompareStore(); const inCompare = isAdded(currentProduct.slug); + const addRecentlyViewed = useRecentlyViewedStore((s) => s.addItem); + useEffect(() => { + addRecentlyViewed(currentProduct); + }, [currentProduct, addRecentlyViewed]); + const handleAddToCart = () => { if (!currentProduct.price) return; addToCart({ @@ -99,14 +105,12 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa else addCompare({ slug: currentProduct.slug, name: currentProduct.name, brand: currentProduct.brand, price: currentProduct.price, image: currentProduct.image, specs: currentProduct.specs }); }; - // Kalkulator const mocChlodnicza = parseMoc(currentProduct.specs['Moc chłodnicza'] || ''); const pobórMocy = parseMoc(currentProduct.specs['Pobór mocy chłodzenie (kW)'] || '') || (mocChlodnicza / estimateSEER(currentProduct.specs['Klasa energetyczna'] || '')); const cenaKwh = 0.75; const kosztGodzina = pobórMocy * cenaKwh; const kosztMiesiac = kosztGodzina * godziny * 30; - // Kluczowe parametry do ikon const params = [ currentProduct.specs['Klasa energetyczna'] && { icon: Star, label: 'Klasa energetyczna', value: currentProduct.specs['Klasa energetyczna'], color: 'text-green-600 bg-green-50' }, currentProduct.specs['Moc chłodnicza'] && { icon: Thermometer, label: 'Moc chłodnicza', value: `${currentProduct.specs['Moc chłodnicza']} kW`, color: 'text-blue-600 bg-blue-50' }, @@ -120,7 +124,6 @@ export function ProductPageClient({ product, seriesProducts, companyPhone, compa
- {/* Breadcrumb */}