'use client'; import React, { useState } from 'react'; import Papa from 'papaparse'; import { XMLParser } from 'fast-xml-parser'; import { BadgeTemplate, ProductItem } from '@/types/newsletter'; import { findBadgeByCode, badgeCode } from '@/lib/badgeUtils'; import { Upload, Check, AlertCircle, FileText, X } from 'lucide-react'; import { supabase } from '@/lib/supabase'; import { PodkladyProduct, podkladyToProductItem, readPodkladyZip, splitPrice } from '@/lib/podkladyZip'; import { CARD_BG_BY_BADGE_COLOR } from '@/lib/productCard'; import { shrinkImage, uploadFile } from '@/lib/upload'; interface ImportModalProps { isOpen: boolean; onClose: () => void; onImportProducts: (products: ProductItem[], options?: { replace?: boolean }) => void; availableBadges?: BadgeTemplate[]; // šablóny cenoviek – stĺpec s kódom určí, ktorú použiť } export const ProductImportModal: React.FC = ({ isOpen, onClose, onImportProducts, availableBadges = [] }) => { const [file, setFile] = useState(null); const [fileType, setFileType] = useState<'csv' | 'json' | 'xml'>('csv'); const [parsedRows, setParsedRows] = useState([]); const [columns, setColumns] = useState([]); // Mapovanie stĺpcov const [mapping, setMapping] = useState<{ title: string; priceMain: string; priceSub: string; unitPrice: string; discountText: string; imageUrl: string; validity: string; badgeCode: string; }>({ title: '', priceMain: '', priceSub: '', unitPrice: '', discountText: '', imageUrl: '', validity: '', badgeCode: '' }); const [presetName, setPresetName] = useState(''); const [savedPresets, setSavedPresets] = useState([]); // ZIP s podkladmi z Databázy produktov (tabuľka + fotky) – pevný formát, bez mapovania stĺpcov const [zip, setZip] = useState<{ products: (PodkladyProduct & { thumb: string })[]; info: string } | null>(null); const [zipProgress, setZipProgress] = useState(null); const [replaceExisting, setReplaceExisting] = useState(false); // vymazať doterajšie produkty sekcie if (!isOpen) return null; const handleFileUpload = (e: React.ChangeEvent) => { const uploaded = e.target.files?.[0]; if (!uploaded) return; setFile(uploaded); const ext = uploaded.name.split('.').pop()?.toLowerCase(); const reader = new FileReader(); setZip(null); if (ext === 'zip') { setColumns([]); setParsedRows([]); readPodkladyZip(uploaded, availableBadges) .then(({ products, info }) => { if (!products.length) throw new Error('V tabuľke sa nenašiel žiadny produkt.'); setZip({ products: products.map((p) => ({ ...p, thumb: p.photo ? URL.createObjectURL(p.photo) : '' })), info }); }) .catch((err: Error) => alert(`ZIP sa nepodarilo načítať: ${err.message}`)); return; } if (ext === 'csv') { setFileType('csv'); reader.onload = (event) => { const text = event.target?.result as string; Papa.parse(text, { header: true, skipEmptyLines: true, complete: (results) => { if (results.data.length > 0) { setParsedRows(results.data); const headers = Object.keys(results.data[0] as object); setColumns(headers); autoGuessMapping(headers); } } }); }; reader.readAsText(uploaded); } else if (ext === 'json') { setFileType('json'); reader.onload = (event) => { try { const json = JSON.parse(event.target?.result as string); const arrayData = Array.isArray(json) ? json : json.products || [json]; setParsedRows(arrayData); if (arrayData.length > 0) { const headers = Object.keys(arrayData[0]); setColumns(headers); autoGuessMapping(headers); } } catch (err) { alert('Chyba pri parsovaní JSON súboru.'); } }; reader.readAsText(uploaded); } else if (ext === 'xml') { setFileType('xml'); reader.onload = (event) => { try { const parser = new XMLParser({ ignoreAttributes: false }); const jsonObj = parser.parse(event.target?.result as string); // Hľadáme pole produktov const possibleArray = jsonObj.products?.product || jsonObj.items?.item || Object.values(jsonObj)[0]; const arrayData = Array.isArray(possibleArray) ? possibleArray : [possibleArray]; setParsedRows(arrayData); if (arrayData.length > 0) { const headers = Object.keys(arrayData[0]); setColumns(headers); autoGuessMapping(headers); } } catch (err) { alert('Chyba pri parsovaní XML súboru.'); } }; reader.readAsText(uploaded); } }; const autoGuessMapping = (headers: string[]) => { const newMapping = { ...mapping }; headers.forEach((h) => { const lower = h.toLowerCase(); if (lower.includes('nazov') || lower.includes('title') || lower.includes('name')) newMapping.title = h; if (lower.includes('hlavna') || lower.includes('euro') || lower.includes('price_main')) newMapping.priceMain = h; if (lower.includes('cent') || lower.includes('sub') || lower.includes('price_sub')) newMapping.priceSub = h; if (lower.includes('merna') || lower.includes('unit') || lower.includes('kg')) newMapping.unitPrice = h; if (lower.includes('zlava') || lower.includes('discount')) newMapping.discountText = h; if (lower.includes('obrazok') || lower.includes('image') || lower.includes('foto')) newMapping.imageUrl = h; if (lower.includes('platnost') || lower.includes('valid')) newMapping.validity = h; if (lower.includes('cenovk') || lower.includes('badge') || lower.includes('sablon') || lower.includes('template')) newMapping.badgeCode = h; }); setMapping(newMapping); }; // Import zo ZIP-u: fotky sa zmenšia (max 1000 px) a nahrajú, potom sa produkty pridajú do sekcie const applyZipImport = async () => { if (!zip) return; const items: ProductItem[] = []; try { for (let i = 0; i < zip.products.length; i++) { const p = zip.products[i]; setZipProgress(`Nahrávam fotky ${i + 1}/${zip.products.length}…`); let imageUrl = ''; if (p.photo) { const small = await shrinkImage(new File([p.photo], p.photoName || `${p.ean}.png`, { type: p.photo.type || 'image/png' })); imageUrl = await uploadFile(small, small.name); } items.push(podkladyToProductItem(p, imageUrl, i)); } } catch (err) { alert(`Import sa nepodaril: ${(err as Error).message}`); setZipProgress(null); return; } setZipProgress(null); zip.products.forEach((p) => p.thumb && URL.revokeObjectURL(p.thumb)); setZip(null); setFile(null); onImportProducts(items, { replace: replaceExisting }); onClose(); }; const applyImport = () => { if (zip) { applyZipImport(); return; } if (!mapping.title) { alert('Prosím vyberte stĺpec pre Názov produktu.'); return; } const importedProducts: ProductItem[] = parsedRows.map((row, idx) => { let pMain = String(row[mapping.priceMain] || '0').trim(); let pSub = String(row[mapping.priceSub] || '00').trim(); // Ak je cena v jednom stĺpci napr. "1.99" alebo "1,99" if (!mapping.priceSub && mapping.priceMain) { const fullPrice = String(row[mapping.priceMain] || '0').replace(',', '.'); const parts = fullPrice.split('.'); pMain = parts[0] || '0'; pSub = parts[1] || '00'; if (pSub.length === 1) pSub += '0'; } // Šablóna cenovky podľa kódu (alebo názvu) zo súboru const badge = mapping.badgeCode ? findBadgeByCode(availableBadges, row[mapping.badgeCode]) : undefined; return { ...(badge ? { badgeCustomTemplate: badge, badgeId: badge.id, badgeWidth: badge.width ?? 110 } : {}), id: `imported-${Date.now()}-${idx}`, title: String(row[mapping.title] || 'Nový produkt'), imageUrl: row[mapping.imageUrl] || 'https://dqexxfspkylqttrtxsww.supabase.co/storage/v1/object/public/newsletter-assets/produkt_1.png', priceMain: pMain, priceSub: pSub, unitPrice: row[mapping.unitPrice] || '', discountText: row[mapping.discountText] ? `${row[mapping.discountText]}%` : '', badgeText: 'zľava', cardBgColor: '#feeaea', stores: { potraviny: true, supermarket: true, tempo: true }, validityOverride: row[mapping.validity] || undefined }; }); // Kódy cenoviek, ku ktorým sa nenašla šablóna if (mapping.badgeCode) { const unknown = [ ...new Set( parsedRows .map((row) => String(row[mapping.badgeCode] || '').trim()) .filter((v) => v && !findBadgeByCode(availableBadges, v)) ) ]; if (unknown.length) { alert(`Pre tieto kódy cenoviek sa nenašla šablóna (produkty dostanú predvolenú cenovku):\n${unknown.join(', ')}`); } } onImportProducts(importedProducts); onClose(); }; return (
{/* Header */}

Hromadný import produktov

{/* Content */}
{/* File Upload Box */}
{/* ZIP s podkladmi: náhľad produktov */} {zip && (

Produkty v ZIP-e ({zip.products.length})

{zip.info && {zip.info}}
{zip.products.map((p) => { const price = p.price != null ? splitPrice(p.price) : null; const stores = [p.stores.potraviny && 'P', p.stores.supermarket && 'S', p.stores.tempo && 'T'].filter(Boolean).join(' '); return (
{p.thumb ? ( // eslint-disable-next-line @next/next/no-img-element ) : ( bez fotky )}

{p.title} {p.quantity && {p.quantity}}

EAN {p.ean} {p.unitPrice && ` · ${p.unitPrice}`} {p.deposit && ` · záloha ${p.deposit} €`} {p.appPrice != null && ` · v appke ${p.appPrice.toFixed(2).replace('.', ',')} €`} {stores && ` · ${stores}`}

{p.extras.length > 0 && (

štítok na fotke: {p.extras.join(' · ')}

)}

{p.badge && p.badgeMissing.length ? ( cenovka: {p.badge.name} – chýba {p.badgeMissing.join(' a ')} pre „{p.badgeLabel}“ ) : p.badge ? ( cenovka: {p.badge.name} ) : ( {p.badgeLabel ? `cenovka „${p.badgeLabel}“ nie je medzi šablónami – použije sa predvolená` : 'bez cenovky – použije sa predvolená'} )}

{price ? `${price.main},${price.sub} €` : '—'}

{p.discount &&

-{p.discount}

}
); })}

Fotky sa pri importe zmenšia na najviac 1000 px, pozadie karty sa nastaví podľa farby cenovky. Rozloženie karty (veľkosť a poloha cenovky, štítky predajní) sa prevezme z prvého produktu v sekcii.

)} {/* Mapping Table */} {columns.length > 0 && (

Vizuálny Data Mapper (Párovanie stĺpcov)

Kódy šablón:{' '} {availableBadges.length ? availableBadges.map(badgeCode).join(', ') : 'zatiaľ žiadne šablóny'} {' '}(kód vzniká z názvu šablóny; v súbore môže byť kód aj presný názov)

{/* Živý náhľad prvých 2 riadkov */} {parsedRows.length > 0 && (
Živý náhľad prvých položiek ({parsedRows.length} nájdených záznamov)
{parsedRows.slice(0, 2).map((row, idx) => (
{row[mapping.title] || 'Bez názvu'} {mapping.badgeCode && ( cenovka: {findBadgeByCode(availableBadges, row[mapping.badgeCode])?.name || `? (${row[mapping.badgeCode] || 'prázdne'})`} )} {row[mapping.priceMain] || '0'},{row[mapping.priceSub] || '00'} €
))}
)}
)}
{/* Footer */}
); };