Files
Coop-NL/src/components/editor/ConfigPanel.tsx
T
Jakub_HostacnyandClaude Opus 5.5 25771afc79 feat: produktove karty v style letaku a zjednoduseny panel produktu
Karta podla sablony coop_nws_009: fotka cez celu kartu, cenovka cez fotku
vpravo dole, stitky predajni nad cenovkou, pod tym texty.
- v editore sa vizual sklada zivo (HTML/CSS); pri exporte (ZIP/Brevo) sa ulozi
  ako JPG a do e-mailu ide ako jeden obrazok - funguje rovnako vo vsetkych
  klientoch; nezmenene karty sa znova nenahravaju
- /api/img: proxy obrazkov pre nahlad (inak by sa vizual nedal ulozit)
- novy ProductEditor pre bezne aj vernostne produkty: Texty, Fotka (zoom/posun),
  Cenovka (sablona, velkost, ceny), Dostupnost, Dalsie moznosti; zbalene po produktoch
- alt texty kariet s cenou a zlavou, mobilne styly textu, ochrana farieb
  v tmavom rezime (zelena karta, Apple Mail)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:06:48 +02:00

1096 lines
51 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import React from 'react';
import { NewsletterModule, ProductItem, BadgeTemplate, CardProduct } from '@/types/newsletter';
import { Plus, Trash2, Image as ImageIcon, Tag, Store, Globe, Palette, Check, Code } from 'lucide-react';
import { SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
import { ImageUploadInput } from './ImageUploadInput';
import { pickAlt } from '@/lib/altText';
import { renderGeneratedModuleHtml, CUSTOM_HTML_MODULE_TYPES } from '@/lib/emailRenderer';
import { badgeWidthFor } from '@/lib/productCard';
import { ProductEditor, PRODUCT_BG_PRESETS } from './ProductEditor';
interface ConfigPanelProps {
module: NewsletterModule | null;
onUpdateModule: (updated: NewsletterModule) => void;
onApplyBadgeToAll?: (badge: BadgeTemplate, badgeLayout?: 'below' | 'side') => void;
// Zmena nastavení spoločných pre všetky produktové sekcie a vernostné zľavy (platnosť akcie...)
onUpdateAllProductSections?: (patch: Record<string, unknown>) => void;
campaignValidity?: string; // globálna platnosť akcie (pre Lištu platnosti)
onOpenBadgeManager?: (productId: string) => void;
onOpenImport?: () => void;
availableBadges?: BadgeTemplate[];
}
// Jednoduchý HTML editor: vlastné HTML nahradí modul vygenerovaný z formulára
const CustomHtmlEditor: React.FC<{
module: NewsletterModule;
updateData: (patch: Record<string, unknown>) => void;
}> = ({ module, updateData }) => {
const enabled = !!module.data.useCustomHtml;
const toggle = () => {
if (enabled) {
// Neupravené HTML nedržíme, aby sa pri ďalšom zapnutí predvyplnilo aktuálne z formulára
const unchanged = (module.data.customHtml || '').trim() === renderGeneratedModuleHtml(module);
updateData({ useCustomHtml: false, ...(unchanged ? { customHtml: undefined } : {}) });
} else {
// Pri prvom zapnutí predvyplníme aktuálne vygenerované HTML
updateData({
useCustomHtml: true,
customHtml: module.data.customHtml || renderGeneratedModuleHtml(module)
});
}
};
const reloadFromForm = () => {
if (confirm('Prepísať vlastné HTML aktuálnym HTML vygenerovaným z formulára?')) {
updateData({ customHtml: renderGeneratedModuleHtml(module) });
}
};
return (
<div className="mb-5 p-3 rounded-xl border border-gray-200 bg-gray-50 space-y-2">
<label className="flex items-center justify-between gap-2 cursor-pointer">
<span className="text-xs font-bold text-gray-800 flex items-center gap-1.5">
<Code className="w-3.5 h-3.5 text-gray-600" /> Vlastné HTML
</span>
<input type="checkbox" checked={enabled} onChange={toggle} className="w-4 h-4 accent-red-600" />
</label>
{enabled ? (
<>
<textarea
value={module.data.customHtml || ''}
onChange={(e) => updateData({ customHtml: e.target.value })}
spellCheck={false}
className="w-full h-96 text-[11px] leading-snug font-mono border border-gray-300 rounded-lg p-2 bg-white whitespace-pre"
/>
<div className="flex items-center justify-between gap-2">
<p className="text-[10px] text-gray-500">Náhľad sa aktualizuje priebežne. Používajte tabuľky a inline štýly.</p>
<button
type="button"
onClick={reloadFromForm}
className="shrink-0 text-[10px] font-bold text-gray-700 bg-white hover:bg-gray-100 border border-gray-300 px-2 py-1 rounded-lg"
title="Nahradiť obsah HTML vygenerovaným z formulára"
>
Načítať z formulára
</button>
</div>
</>
) : (
<p className="text-[10px] text-gray-500">Zapnite pre úpravu HTML kódu. Formulár ostane uložený a po vypnutí sa vráti.</p>
)}
</div>
);
};
export const ConfigPanel: React.FC<ConfigPanelProps> = ({
module,
onUpdateModule,
onApplyBadgeToAll,
onUpdateAllProductSections,
campaignValidity = '',
onOpenBadgeManager,
onOpenImport,
availableBadges = []
}) => {
if (!module) {
return (
<div className="h-full flex items-center justify-center p-6 text-center text-gray-400">
<p className="text-sm">Vyberte modul v náhľade e-mailu pre úpravu jeho vlastností.</p>
</div>
);
}
const updateAllProductSections = (patch: Record<string, unknown>) =>
onUpdateAllProductSections ? onUpdateAllProductSections(patch) : updateData(patch);
const updateData = (newData: any) => {
onUpdateModule({
...module,
data: { ...module.data, ...newData }
});
};
// Zmena jedného produktu v sekcii
const updateProduct = (pIdx: number, patch: Record<string, unknown>) => {
const products = [...(module.data.products || [])];
products[pIdx] = { ...products[pIdx], ...patch };
updateData({ products });
};
// Cenovka produktu pre všetky produkty sekcie (produkty s vlastnou PNG cenovkou sa neprepisujú)
const applyBadgeToSection = (p: CardProduct) => {
const template = p.badgeCustomTemplate;
if (!template) return;
if (!confirm(`Použiť cenovku "${template.name}" pre všetky produkty v tejto sekcii?`)) return;
const products = (module.data.products || []).map((prod: CardProduct) =>
prod.customBadgePngUrl
? prod
: { ...prod, badgeCustomTemplate: template, badgeId: template.id, badgeWidth: p.badgeWidth }
);
updateData({ products });
};
// Cenovka produktu v celom newsletteri (všetky produktové sekcie aj vernostné zľavy)
const applyBadgeToAll = (p: CardProduct, variant: 'standard' | 'loyalty') => {
if (!p.badgeCustomTemplate || !onApplyBadgeToAll) return;
if (!confirm(`Použiť cenovku "${p.badgeCustomTemplate.name}" pre VŠETKY produkty v celom newsletteri?`)) return;
onApplyBadgeToAll({ ...p.badgeCustomTemplate, width: badgeWidthFor(p, variant) });
};
return (
<div className="h-full overflow-y-auto p-5 space-y-6 bg-white border-l border-gray-200">
<div className="border-b border-gray-100 pb-3 space-y-1.5">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-wider text-red-600 bg-red-50 px-2 py-0.5 rounded">
{module.type}
</span>
<span className="text-[10px] text-gray-400 font-medium">Názov modulu v editore</span>
</div>
<input
type="text"
value={module.title}
onChange={(e) => onUpdateModule({ ...module, title: e.target.value })}
placeholder="Názov modulu..."
className="w-full text-base font-bold text-gray-900 border border-gray-200 hover:border-gray-300 focus:border-red-500 rounded-lg px-2.5 py-1.5 bg-gray-50/50 hover:bg-white focus:bg-white focus:outline-none focus:ring-1 focus:ring-red-500 transition-all"
/>
</div>
{/* TOP ANNOUNCEMENT */}
{module.type === 'announcement' && (
<div className="space-y-4">
{/* Toggle zapnuté / vypnuté */}
<div className="bg-red-50/60 p-3.5 rounded-xl border border-red-200 flex items-center justify-between">
<div>
<span className="text-xs font-bold text-gray-900 block">Zobraziť v e-maile</span>
<span className="text-[11px] text-gray-500">Top oznamovacia lišta</span>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={module.data.enabled !== false}
onChange={(e) => updateData({ enabled: e.target.checked })}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-300 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-red-600"></div>
</label>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Text badge (napr. TIP)</label>
<input
type="text"
value={module.data.tipBadge || ''}
onChange={(e) => updateData({ tipBadge: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Hlavný text oznamu</label>
<input
type="text"
value={module.data.text || ''}
onChange={(e) => updateData({ text: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Text odkazu</label>
<input
type="text"
value={module.data.linkText || ''}
onChange={(e) => updateData({ linkText: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Cieľová URL</label>
<input
type="text"
value={module.data.linkUrl || ''}
onChange={(e) => updateData({ linkUrl: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
</div>
)}
{/* LIŠTA PLATNOSTI AKCIE */}
{module.type === 'validity_bar' && (
<div className="space-y-4">
<div className="bg-red-50/60 p-3.5 rounded-xl border border-red-200 flex items-center justify-between">
<div>
<span className="text-xs font-bold text-gray-900 block">Zobraziť v e-maile</span>
<span className="text-[11px] text-gray-500">Lišta platnosti akcie</span>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={module.data.enabled !== false}
onChange={(e) => updateData({ enabled: e.target.checked })}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-300 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-red-600"></div>
</label>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Text pred dátumom</label>
<input
type="text"
value={module.data.text || ''}
onChange={(e) => updateData({ text: e.target.value })}
placeholder="napr. Akcia platí"
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Globálna platnosť akcie</label>
<input
type="text"
value={campaignValidity}
onChange={(e) => updateAllProductSections({ globalValidity: e.target.value })}
placeholder="napr. 2. 5. – 15. 5. 2026"
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
<p className="text-[10px] text-gray-500 mt-1">Rovnaká platnosť ako v produktových sekciách – zmena sa prejaví všade.</p>
</div>
</div>
)}
{/* Vlastné HTML pre hlavičku a pätičku */}
{CUSTOM_HTML_MODULE_TYPES.includes(module.type) && (
<CustomHtmlEditor module={module} updateData={updateData} />
)}
{/* HEADER */}
{module.type === 'header' && !module.data.useCustomHtml && (() => {
const navLinks = module.data.navLinks || [
{ label: 'Letáky', url: 'https://www.coop.sk/sk/letaky', variant: 'outline' },
{ label: 'COOP Klub', url: 'https://www.coopklub.sk', variant: 'filled' }
];
const updateNavLink = (index: number, patch: Partial<typeof navLinks[0]>) => {
const updated = [...navLinks];
updated[index] = { ...updated[index], ...patch };
updateData({ navLinks: updated });
};
return (
<div className="space-y-5">
<ImageUploadInput
label="Logo COOP Jednota"
value={module.data.logoUrl || ''}
onChange={(url) => updateData({ logoUrl: url })}
altValue={module.data.logoAlt}
onAltChange={(alt) => updateData({ logoAlt: alt })}
altPlaceholder="COOP Jednota"
/>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Odkaz loga</label>
<input
type="text"
value={module.data.logoLink || ''}
onChange={(e) => updateData({ logoLink: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
placeholder="https://www.coop.sk"
/>
</div>
{/* Tlačidlá v hlavičke */}
<div className="pt-2 border-t border-gray-200 space-y-4">
<span className="text-xs font-bold text-gray-800 uppercase tracking-wider block">
Tlačidlá v hlavičke
</span>
{/* Tlačidlo 1 (Letáky) */}
<div className="bg-gray-50 p-3 rounded-xl border border-gray-200 space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-gray-700">Tlačidlo 1 (Letáky)</span>
<span className="text-[10px] bg-red-100 text-red-700 font-bold px-2 py-0.5 rounded">
{navLinks[0]?.variant === 'filled' ? 'Plné' : 'Obrys'}
</span>
</div>
<div>
<label className="block text-[11px] text-gray-500 mb-1">Text tlačidla</label>
<input
type="text"
value={navLinks[0]?.label || ''}
onChange={(e) => updateNavLink(0, { label: e.target.value })}
className="w-full text-xs font-bold border border-gray-300 rounded-lg p-2 bg-white"
placeholder="Letáky"
/>
</div>
<div>
<label className="block text-[11px] text-gray-500 mb-1">Cieľový odkaz (URL)</label>
<input
type="text"
value={navLinks[0]?.url || ''}
onChange={(e) => updateNavLink(0, { url: e.target.value })}
className="w-full text-xs border border-gray-300 rounded-lg p-2 bg-white"
placeholder="https://www.coop.sk/sk/letaky"
/>
</div>
<div>
<label className="block text-[11px] text-gray-500 mb-1">Štýl zobrazenia</label>
<select
value={navLinks[0]?.variant || 'outline'}
onChange={(e) => updateNavLink(0, { variant: e.target.value as any })}
className="w-full text-xs border border-gray-300 rounded-lg p-2 bg-white font-medium"
>
<option value="outline">Červený obrys (Biele pozadie, červený text)</option>
<option value="filled">Modré plné tlačidlo (Biele písmo)</option>
</select>
</div>
</div>
{/* Tlačidlo 2 (COOP Klub) */}
<div className="bg-gray-50 p-3 rounded-xl border border-gray-200 space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-gray-700">Tlačidlo 2 (COOP Klub)</span>
<span className="text-[10px] bg-blue-100 text-blue-700 font-bold px-2 py-0.5 rounded">
{navLinks[1]?.variant === 'filled' ? 'Plné modré' : 'Obrys'}
</span>
</div>
<div>
<label className="block text-[11px] text-gray-500 mb-1">Text tlačidla</label>
<input
type="text"
value={navLinks[1]?.label || ''}
onChange={(e) => updateNavLink(1, { label: e.target.value })}
className="w-full text-xs font-bold border border-gray-300 rounded-lg p-2 bg-white"
placeholder="COOP Klub"
/>
</div>
<div>
<label className="block text-[11px] text-gray-500 mb-1">Cieľový odkaz (URL)</label>
<input
type="text"
value={navLinks[1]?.url || ''}
onChange={(e) => updateNavLink(1, { url: e.target.value })}
className="w-full text-xs border border-gray-300 rounded-lg p-2 bg-white"
placeholder="https://www.coopklub.sk"
/>
</div>
<div>
<label className="block text-[11px] text-gray-500 mb-1">Štýl zobrazenia</label>
<select
value={navLinks[1]?.variant || 'filled'}
onChange={(e) => updateNavLink(1, { variant: e.target.value as any })}
className="w-full text-xs border border-gray-300 rounded-lg p-2 bg-white font-medium"
>
<option value="filled">Modré plné tlačidlo (Biele písmo)</option>
<option value="outline">Červený obrys (Biele pozadie, červený text)</option>
</select>
</div>
</div>
</div>
</div>
);
})()}
{/* BANNER */}
{module.type === 'banner' && (
<div className="space-y-4">
<ImageUploadInput
label="Obrázok bannera"
value={module.data.imageUrl || ''}
onChange={(url) => updateData({ imageUrl: url })}
altValue={module.data.altText}
onAltChange={(alt) => updateData({ altText: alt })}
altPlaceholder={pickAlt(module.data.title, module.title, 'COOP Jednota')}
/>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Odkaz bannera</label>
<input
type="text"
value={module.data.linkUrl || ''}
onChange={(e) => updateData({ linkUrl: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Nadpis pod bannerom</label>
<textarea
rows={2}
value={module.data.title || ''}
onChange={(e) => updateData({ title: e.target.value })}
title="Enter = nový riadok (alebo <br>)"
className="w-full text-sm border border-gray-300 rounded-lg p-2 resize-y"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Popis</label>
<textarea
value={module.data.description || ''}
onChange={(e) => updateData({ description: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2 h-20"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Text tlačidla</label>
<input
type="text"
value={module.data.buttonText || ''}
onChange={(e) => updateData({ buttonText: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Farba tlačidla</label>
<input
type="color"
value={module.data.buttonBgColor || '#e2001a'}
onChange={(e) => updateData({ buttonBgColor: e.target.value })}
className="w-full h-9 rounded-lg cursor-pointer border border-gray-300 p-1"
/>
</div>
</div>
</div>
)}
{/* PRODUCTS GRID */}
{module.type === 'products_grid' && (
<div className="space-y-5">
<div className="flex items-center justify-between">
<h3 className="text-xs font-bold text-gray-800 uppercase tracking-wider">
Nastavenia sekcie
</h3>
{onOpenImport && (
<button
onClick={onOpenImport}
className="text-xs font-bold text-red-600 hover:text-red-700 bg-red-50 hover:bg-red-100 px-2.5 py-1 rounded-lg transition-colors"
>
Hromadný import
</button>
)}
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Názov sekcie</label>
<input
type="text"
value={module.data.sectionTitle || ''}
onChange={(e) => updateData({ sectionTitle: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2 font-bold"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Globálna platnosť akcie</label>
<input
type="text"
value={module.data.globalValidity || ''}
onChange={(e) => updateAllProductSections({ globalValidity: e.target.value })}
placeholder="napr. 2. 5. – 15. 5. 2026"
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
<p className="text-[10px] text-gray-500 mt-1">Platí pre všetky produktové sekcie aj vernostné zľavy. Produkty s vlastnou platnosťou si ju ponechajú.</p>
<label className="mt-2 flex items-center gap-2 text-[11px] text-gray-700 cursor-pointer">
<input
type="checkbox"
checked={module.data.showCardValidity === true}
onChange={(e) => updateAllProductSections({ showCardValidity: e.target.checked })}
className="w-3.5 h-3.5 accent-red-600"
/>
Zobraziť platnosť aj na kartách produktov
</label>
</div>
{/* Rozloženie produktov (Počet produktov na riadok) */}
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">
Rozloženie produktov na riadok
</label>
<div className="grid grid-cols-2 gap-2 bg-gray-100 p-1 rounded-xl">
<button
type="button"
onClick={() => updateData({ columns: 2 })}
className={`py-1.5 px-2.5 rounded-lg text-xs font-bold transition-all flex items-center justify-center gap-1.5 ${
(module.data.columns || 2) === 2
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-500 hover:text-gray-900'
}`}
>
<span>2 produkty na riadok</span>
</button>
<button
type="button"
onClick={() => updateData({ columns: 1 })}
className={`py-1.5 px-2.5 rounded-lg text-xs font-bold transition-all flex items-center justify-center gap-1.5 ${
module.data.columns === 1
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-500 hover:text-gray-900'
}`}
>
<span>1 produkt na riadok</span>
</button>
</div>
</div>
{/* Farba pozadia skupiny produktov */}
<div className="bg-gray-50 p-3.5 rounded-xl border border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Palette className="w-3.5 h-3.5 text-gray-700" />
<label className="text-xs font-bold text-gray-800">Farba pozadia produktov</label>
</div>
<span className="text-[10px] font-mono font-bold px-1.5 py-0.5 bg-white border border-gray-200 rounded text-gray-700 uppercase">
{module.data.cardBgColor || '#feeaea'}
</span>
</div>
{/* Rýchle predvoľby */}
<div className="grid grid-cols-3 gap-1.5">
{PRODUCT_BG_PRESETS.map((preset) => {
const currentBg = (module.data.cardBgColor || '#feeaea').toLowerCase();
const isSelected = currentBg === preset.color.toLowerCase();
return (
<button
key={preset.color}
type="button"
onClick={() => {
const newColor = preset.color;
const newBorder = preset.border;
const updatedProducts = (module.data.products || []).map((p: ProductItem) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
cardBorderColor: newBorder,
products: updatedProducts,
});
}}
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg border text-[11px] font-medium transition-all text-left ${
isSelected
? 'border-red-600 bg-white shadow-xs ring-1 ring-red-500 font-bold'
: 'border-gray-200 bg-white hover:border-gray-300 text-gray-700'
}`}
>
<span
className="w-3.5 h-3.5 rounded-full border border-black/15 shrink-0 flex items-center justify-center"
style={{ backgroundColor: preset.color }}
>
{isSelected && <Check className="w-2.5 h-2.5 text-gray-800" />}
</span>
<span className="truncate">{preset.label}</span>
</button>
);
})}
</div>
{/* Vlastná farba (Color Picker + HEX input) */}
<div className="flex items-center gap-2 pt-2 border-t border-gray-200">
<span className="text-[10px] text-gray-500 font-semibold shrink-0">Vlastná:</span>
<div className="flex items-center gap-1.5 flex-1">
<input
type="color"
value={module.data.cardBgColor || '#feeaea'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: ProductItem) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
className="w-7 h-7 rounded border border-gray-300 cursor-pointer p-0.5 bg-white shrink-0"
/>
<input
type="text"
value={module.data.cardBgColor || '#feeaea'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: ProductItem) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
placeholder="#feeaea"
className="w-full text-xs font-mono border border-gray-300 rounded px-2 py-1 bg-white uppercase text-gray-800 font-bold"
/>
</div>
</div>
</div>
{/* Produkty */}
<div className="space-y-2 pt-2">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-gray-700">
Produkty ({module.data.products?.length || 0})
</span>
<button
onClick={() => {
const newP: ProductItem = {
id: `p-${Date.now()}`,
title: 'Nový produkt',
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_1.png`,
priceMain: '1',
priceSub: '99',
unitPrice: '',
discountText: '20%',
badgeText: 'zľava',
cardBgColor: module.data.cardBgColor || '#feeaea',
stores: { potraviny: true, supermarket: true, tempo: true }
};
updateData({ products: [...(module.data.products || []), newP] });
}}
className="text-xs font-bold text-white bg-gray-900 hover:bg-black px-2.5 py-1 rounded-lg flex items-center gap-1"
>
<Plus className="w-3.5 h-3.5" /> Pridať produkt
</button>
</div>
{(module.data.products || []).map((p: ProductItem, pIdx: number) => (
<ProductEditor
key={p.id}
product={p}
index={pIdx}
variant="standard"
defaultOpen={pIdx === 0}
availableBadges={availableBadges || []}
onChange={(patch) => updateProduct(pIdx, patch)}
onDelete={() => updateData({ products: (module.data.products || []).filter((_: ProductItem, i: number) => i !== pIdx) })}
onOpenBadgeEditor={onOpenBadgeManager ? () => onOpenBadgeManager(p.id) : undefined}
onApplyBadgeToSection={() => applyBadgeToSection(p)}
onApplyBadgeToAll={onApplyBadgeToAll ? () => applyBadgeToAll(p, 'standard') : undefined}
/>
))}
</div>
</div>
)}
{/* LOYALTY CLUB (VERNOSTNÉ ZĽAVY) */}
{module.type === 'loyalty_club' && (
<div className="space-y-5">
{/* Toggle zapnuté / vypnuté */}
<div className="bg-red-50/60 p-3.5 rounded-xl border border-red-200 flex items-center justify-between">
<div>
<span className="text-xs font-bold text-gray-900 block">Zobraziť v e-maile</span>
<span className="text-[11px] text-gray-500">Sekcia Vernostné zľavy</span>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={module.data.enabled !== false}
onChange={(e) => updateData({ enabled: e.target.checked })}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-300 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-red-600"></div>
</label>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Názov sekcie</label>
<input
type="text"
value={module.data.sectionTitle || ''}
onChange={(e) => updateData({ sectionTitle: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2 font-bold"
/>
</div>
<ImageUploadInput
label="Obrázok banneru Vernostných zliav"
value={module.data.headerImageUrl || ''}
onChange={(url) => updateData({ headerImageUrl: url })}
altValue={module.data.headerImageAlt}
onAltChange={(alt) => updateData({ headerImageAlt: alt })}
altPlaceholder={pickAlt(module.data.title, module.data.sectionTitle, 'Vernostné zľavy')}
/>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Odkaz banneru</label>
<input
type="text"
value={module.data.headerLink || ''}
onChange={(e) => updateData({ headerLink: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Globálna platnosť akcie</label>
<input
type="text"
value={module.data.globalValidity || ''}
onChange={(e) => updateAllProductSections({ globalValidity: e.target.value })}
placeholder="napr. 2. 5. – 15. 5. 2026"
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
<p className="text-[10px] text-gray-500 mt-1">Platí pre všetky produktové sekcie aj vernostné zľavy. Produkty s vlastnou platnosťou si ju ponechajú.</p>
<label className="mt-2 flex items-center gap-2 text-[11px] text-gray-700 cursor-pointer">
<input
type="checkbox"
checked={module.data.showCardValidity === true}
onChange={(e) => updateAllProductSections({ showCardValidity: e.target.checked })}
className="w-3.5 h-3.5 accent-red-600"
/>
Zobraziť platnosť aj na kartách produktov
</label>
</div>
{/* Farba pozadia vernostných kariet */}
<div className="bg-gray-50 p-3.5 rounded-xl border border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Palette className="w-3.5 h-3.5 text-gray-700" />
<label className="text-xs font-bold text-gray-800">Farba pozadia produktov</label>
</div>
<span className="text-[10px] font-mono font-bold px-1.5 py-0.5 bg-white border border-gray-200 rounded text-gray-700 uppercase">
{module.data.cardBgColor || '#e6e1d9'}
</span>
</div>
{/* Rýchle predvoľby */}
<div className="grid grid-cols-3 gap-1.5">
{PRODUCT_BG_PRESETS.map((preset) => {
const currentBg = (module.data.cardBgColor || '#e6e1d9').toLowerCase();
const isSelected = currentBg === preset.color.toLowerCase();
return (
<button
key={preset.color}
type="button"
onClick={() => {
const newColor = preset.color;
const updatedProducts = (module.data.products || []).map((p: any) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg border text-[11px] font-medium transition-all text-left ${
isSelected
? 'border-red-600 bg-white shadow-xs ring-1 ring-red-500 font-bold'
: 'border-gray-200 bg-white hover:border-gray-300 text-gray-700'
}`}
>
<span
className="w-3.5 h-3.5 rounded-full border border-black/15 shrink-0 flex items-center justify-center"
style={{ backgroundColor: preset.color }}
>
{isSelected && <Check className="w-2.5 h-2.5 text-gray-800" />}
</span>
<span className="truncate">{preset.label}</span>
</button>
);
})}
</div>
{/* Vlastná farba (Color Picker + HEX input) */}
<div className="flex items-center gap-2 pt-2 border-t border-gray-200">
<span className="text-[10px] text-gray-500 font-semibold shrink-0">Vlastná:</span>
<div className="flex items-center gap-1.5 flex-1">
<input
type="color"
value={module.data.cardBgColor || '#e6e1d9'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: any) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
className="w-7 h-7 rounded border border-gray-300 cursor-pointer p-0.5 bg-white shrink-0"
/>
<input
type="text"
value={module.data.cardBgColor || '#e6e1d9'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: any) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
placeholder="#e6e1d9"
className="w-full text-xs font-mono border border-gray-300 rounded px-2 py-1 bg-white uppercase text-gray-800 font-bold"
/>
</div>
</div>
</div>
{/* Vernostné produkty */}
<div className="space-y-2 pt-2">
<span className="text-xs font-bold text-gray-700 block">
Vernostné produkty ({module.data.products?.length || 0})
</span>
{(module.data.products || []).map((p: CardProduct, pIdx: number) => (
<ProductEditor
key={p.id || pIdx}
product={p}
index={pIdx}
variant="loyalty"
defaultOpen={pIdx === 0}
availableBadges={availableBadges || []}
onChange={(patch) => updateProduct(pIdx, patch)}
onOpenBadgeEditor={onOpenBadgeManager ? () => onOpenBadgeManager(p.id) : undefined}
onApplyBadgeToSection={() => applyBadgeToSection(p)}
onApplyBadgeToAll={onApplyBadgeToAll ? () => applyBadgeToAll(p, 'loyalty') : undefined}
/>
))}
</div>
</div>
)}
{/* ARTICLES ROW (LETÁKY ALEBO ČLÁNKY) */}
{module.type === 'articles_row' && (
<div className="space-y-5">
{module.data.item1?.btnText ? (
/* AK SÚ TO LETÁKY */
<div className="space-y-4">
<div className="p-3 bg-blue-50/50 rounded-xl border border-blue-100">
<span className="text-xs font-bold text-blue-900 block mb-1">
Leták 1 (Ľavý stĺpec)
</span>
<p className="text-[11px] text-gray-500 mb-3">Môžete upraviť nadpis letáku, cieľový odkaz a náhľadový obrázok.</p>
<div className="space-y-3">
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Nadpis letáku</label>
<textarea
rows={2}
value={module.data.item1?.title || ''}
onChange={(e) => updateData({
item1: { ...module.data.item1, title: e.target.value }
})}
title="Enter = nový riadok (alebo <br>)"
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold resize-y"
placeholder="Aktuálne letáky"
/>
</div>
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (kam to smeruje)</label>
<input
type="text"
value={module.data.item1?.linkUrl || ''}
onChange={(e) => updateData({
item1: { ...module.data.item1, linkUrl: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
placeholder="https://www.coop.sk/sk/letaky"
/>
</div>
<ImageUploadInput
label="Náhľadový obrázok letáku (PNG/JPG)"
value={module.data.item1?.imageUrl || ''}
onChange={(url) => updateData({
item1: { ...module.data.item1, imageUrl: url }
})}
altValue={module.data.item1?.altText}
onAltChange={(alt) => updateData({
item1: { ...module.data.item1, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item1?.title, module.data.title, module.title)}
/>
</div>
</div>
<div className="p-3 bg-amber-50/50 rounded-xl border border-amber-100">
<span className="text-xs font-bold text-amber-900 block mb-1">
Leták 2 (Pravý stĺpec)
</span>
<p className="text-[11px] text-gray-500 mb-3">Môžete upraviť nadpis letáku, cieľový odkaz a náhľadový obrázok.</p>
<div className="space-y-3">
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Nadpis letáku</label>
<textarea
rows={2}
value={module.data.item2?.title || ''}
onChange={(e) => updateData({
item2: { ...module.data.item2, title: e.target.value }
})}
title="Enter = nový riadok (alebo <br>)"
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold resize-y"
placeholder="Rozšírený leták"
/>
</div>
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (kam to smeruje)</label>
<input
type="text"
value={module.data.item2?.linkUrl || ''}
onChange={(e) => updateData({
item2: { ...module.data.item2, linkUrl: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
placeholder="https://www.coop.sk/sk/letaky"
/>
</div>
<ImageUploadInput
label="Náhľadový obrázok letáku (PNG/JPG)"
value={module.data.item2?.imageUrl || ''}
onChange={(url) => updateData({
item2: { ...module.data.item2, imageUrl: url }
})}
altValue={module.data.item2?.altText}
onAltChange={(alt) => updateData({
item2: { ...module.data.item2, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item2?.title, module.data.title, module.title)}
/>
</div>
</div>
</div>
) : (
/* AK SÚ TO ČLÁNKY (VEĽKONOČNÝ BARANČEK / VAJÍČKO AMULET) */
<div className="space-y-4">
<div className="p-3 bg-orange-50/50 rounded-xl border border-orange-100">
<span className="text-xs font-bold text-orange-900 block mb-1">
Článok 1 (Recepty)
</span>
<p className="text-[11px] text-gray-500 mb-3">Logá a gradienty sú na pevno. Môžete upraviť text, odkaz a obrázok v pozadí.</p>
<div className="space-y-3">
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Názov / Text článku</label>
<textarea
rows={2}
value={module.data.item1?.title || ''}
onChange={(e) => updateData({
item1: { ...module.data.item1, title: e.target.value }
})}
title="Enter = nový riadok (alebo <br>)"
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold resize-y"
/>
</div>
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (odkaz)</label>
<input
type="text"
value={module.data.item1?.linkUrl || ''}
onChange={(e) => updateData({
item1: { ...module.data.item1, linkUrl: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
/>
</div>
<ImageUploadInput
label="Obrázok v pozadí"
value={module.data.item1?.imageUrl || ''}
onChange={(url) => updateData({
item1: { ...module.data.item1, imageUrl: url }
})}
altValue={module.data.item1?.altText}
onAltChange={(alt) => updateData({
item1: { ...module.data.item1, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item1?.title, module.data.title, module.title)}
/>
</div>
</div>
<div className="p-3 bg-blue-50/50 rounded-xl border border-blue-100">
<span className="text-xs font-bold text-blue-900 block mb-1">
Článok 2 (Časopis Jednota)
</span>
<p className="text-[11px] text-gray-500 mb-3">Logá a gradienty sú na pevno. Môžete upraviť text, odkaz a obrázok v pozadí.</p>
<div className="space-y-3">
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Názov / Text článku</label>
<textarea
rows={2}
value={module.data.item2?.title || ''}
onChange={(e) => updateData({
item2: { ...module.data.item2, title: e.target.value }
})}
title="Enter = nový riadok (alebo <br>)"
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold resize-y"
/>
</div>
<div>
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (odkaz)</label>
<input
type="text"
value={module.data.item2?.linkUrl || ''}
onChange={(e) => updateData({
item2: { ...module.data.item2, linkUrl: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
/>
</div>
<ImageUploadInput
label="Obrázok v pozadí"
value={module.data.item2?.imageUrl || ''}
onChange={(url) => updateData({
item2: { ...module.data.item2, imageUrl: url }
})}
altValue={module.data.item2?.altText}
onAltChange={(alt) => updateData({
item2: { ...module.data.item2, altText: alt }
})}
altPlaceholder={pickAlt(module.data.item2?.title, module.data.title, module.title)}
/>
</div>
</div>
</div>
)}
</div>
)}
{/* FOOTER */}
{module.type === 'footer' && !module.data.useCustomHtml && (
<div className="space-y-4">
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Právny text (Disclaimer)</label>
<textarea
value={module.data.disclaimer || ''}
onChange={(e) => updateData({ disclaimer: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2 h-24"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Odkaz pre odhlásenie (Unsubscribe URL)</label>
<input
type="text"
value={module.data.unsubscribeUrl || ''}
onChange={(e) => updateData({ unsubscribeUrl: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">Google Play URL</label>
<input
type="text"
value={module.data.googlePlayUrl || ''}
onChange={(e) => updateData({ googlePlayUrl: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
<div>
<label className="block text-xs font-semibold text-gray-700 mb-1">App Store URL</label>
<input
type="text"
value={module.data.appStoreUrl || ''}
onChange={(e) => updateData({ appStoreUrl: e.target.value })}
className="w-full text-sm border border-gray-300 rounded-lg p-2"
/>
</div>
</div>
)}
</div>
);
};