Files
Coop-NL/src/components/editor/ConfigPanel.tsx
T
Jakub_HostacnyandClaude Opus 5.5 9c2fb60fd6 feat: uprava produktovej fotky - zoom, posun a orezanie
- posuvniky Velkost / Vodorovne / Zvisle pri produkte aj vernostnom produkte
- upravena fotka sa vykresli ako pozadie bunky (background-size/position,
  funguje aj v Gmaile), co presahuje ramik sa oreze
- pri cenovke vedla obrazka je fotka pozadim celeho riadku a da sa
  zasunut pod cenovku; Outlook (mso) dostane povodny obrazok
- neupravene produkty sa vykresluju presne ako doteraz

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

2061 lines
98 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 } 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';
const PRODUCT_BG_PRESETS = [
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
{ label: 'Žltá', sub: 'Domáce', color: '#fef0c7', border: '#e6d49a' },
{ label: 'Hnedá', sub: 'Gril', color: '#dcb085', border: '#c49a6c' },
{ label: 'Sivá', sub: 'Neutrálna', color: '#e6e1d9', border: '#d5cfc5' },
{ label: 'Zelená', sub: 'Bio', color: '#e8f5e9', border: '#c8e6c9' },
{ label: 'Biela', sub: 'Čistá', color: '#ffffff', border: '#e5e7eb' },
];
interface ConfigPanelProps {
module: NewsletterModule | null;
onUpdateModule: (updated: NewsletterModule) => void;
onApplyBadgeToAll?: (badge: BadgeTemplate, badgeLayout?: 'below' | 'side') => void;
onSetGlobalValidity?: (validity: string) => void; // nastaví platnosť vo všetkých produktových sekciách
onOpenBadgeManager?: (productId: string) => void;
onOpenImport?: () => void;
availableBadges?: BadgeTemplate[];
}
// Pomer strán obrázka (výška / šírka); pri chybe načítania 1
const loadImageAspect = (url: string): Promise<number> =>
new Promise((resolve) => {
if (!url) return resolve(1);
const img = new window.Image();
img.onload = () => resolve(img.naturalWidth ? img.naturalHeight / img.naturalWidth : 1);
img.onerror = () => resolve(1);
img.src = url;
});
// Úprava produktovej fotky: zoom a posun (čo presahuje rámik, sa oreže)
const ProductImageAdjust: React.FC<{
product: ProductItem;
onChange: (patch: Partial<ProductItem>) => void;
}> = ({ product, onChange }) => {
const scale = product.imageScale ?? 1;
const offsetX = product.imageOffsetX || 0;
const offsetY = product.imageOffsetY || 0;
const isAdjusted = scale !== 1 || offsetX !== 0 || offsetY !== 0;
const update = async (patch: Partial<ProductItem>) => {
// Pomer strán fotky potrebujeme pre presný výpočet pozadia; zistíme ho pri prvej úprave
if (!product.imageAspect && product.imageUrl) {
patch = { ...patch, imageAspect: await loadImageAspect(product.imageUrl) };
}
onChange(patch);
};
const slider = (label: string, value: number, display: string, min: number, max: number, step: number, key: keyof ProductItem) => (
<div className="grid grid-cols-[64px_1fr_44px] items-center gap-2">
<span className="text-[10px] text-gray-600 font-semibold">{label}</span>
<input
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={(e) => update({ [key]: Number(e.target.value) } as Partial<ProductItem>)}
className="w-full h-1 accent-red-600 cursor-pointer"
/>
<span className="text-[10px] font-mono text-gray-700 text-right">{display}</span>
</div>
);
return (
<div className="p-2.5 rounded-xl border border-gray-200 bg-gray-50 space-y-1.5">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold text-gray-700 uppercase tracking-wider">Úprava fotky</span>
{isAdjusted && (
<button
type="button"
onClick={() => onChange({ imageScale: undefined, imageOffsetX: undefined, imageOffsetY: undefined })}
className="text-[10px] font-bold text-gray-500 hover:text-red-600"
>
Obnoviť
</button>
)}
</div>
{slider('Veľkosť', scale, `${Math.round(scale * 100)} %`, 0.5, 2.5, 0.05, 'imageScale')}
{slider('Vodorovne', offsetX, `${offsetX} px`, -150, 150, 1, 'imageOffsetX')}
{slider('Zvisle', offsetY, `${offsetY} px`, -120, 120, 1, 'imageOffsetY')}
<p className="text-[10px] text-gray-500">Čo presahuje rámik, sa oreže. Pri polohe cenovky „vedľa obrázka“ sa dá produkt zasunúť pod cenovku.</p>
</div>
);
};
// 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>
);
};
// Nahratie celej cenovky ako PNG pre výnimočný produkt (prekryje šablónu)
const CustomBadgePngInput: React.FC<{ value?: string; onChange: (url: string) => void }> = ({ value, onChange }) => (
<div className="p-2.5 rounded-xl border border-dashed border-amber-300 bg-amber-50/60 space-y-1.5">
<ImageUploadInput
label="Vlastná PNG cenovka (výnimka)"
value={value || ''}
onChange={onChange}
placeholder="Nahrajte celú cenovku ako PNG"
/>
{value ? (
<button
type="button"
onClick={() => onChange('')}
className="w-full py-1.5 text-[11px] font-bold text-amber-800 bg-white hover:bg-amber-100 border border-amber-300 rounded-lg transition-colors"
>
Odstrániť PNG a vrátiť generovanú cenovku
</button>
) : (
<p className="text-[10px] text-amber-800/80">Nahraná PNG nahradí generovanú cenovku len pri tomto produkte.</p>
)}
</div>
);
export const ConfigPanel: React.FC<ConfigPanelProps> = ({
module,
onUpdateModule,
onApplyBadgeToAll,
onSetGlobalValidity,
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 updateData = (newData: any) => {
onUpdateModule({
...module,
data: { ...module.data, ...newData }
});
};
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>
)}
{/* 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>
<input
type="text"
value={module.data.title || ''}
onChange={(e) => updateData({ title: 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">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) =>
onSetGlobalValidity
? onSetGlobalValidity(e.target.value)
: updateData({ 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>
</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>
{/* Zoznam produktov */}
<div className="space-y-4 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: '1kg = 4,99 €',
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) => (
<div
key={p.id}
className="p-3 bg-gray-50 rounded-xl border border-gray-200 space-y-3 relative group"
>
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-gray-900 truncate max-w-[180px]">
{p.title || `Produkt ${pIdx + 1}`}
</span>
<button
onClick={() => {
const updated = (module.data.products || []).filter((_: any, i: number) => i !== pIdx);
updateData({ products: updated });
}}
className="text-gray-400 hover:text-red-600 p-1"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div>
<input
type="text"
value={p.title}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].title = e.target.value;
updateData({ products });
}}
placeholder="Názov produktu"
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-medium"
/>
</div>
<ImageUploadInput
label="Obrázok produktu"
value={p.imageUrl || ''}
onChange={(url) => {
const products = [...module.data.products];
products[pIdx].imageUrl = url;
products[pIdx].imageAspect = undefined; // nová fotka = nový pomer strán
updateData({ products });
}}
altValue={p.imageAlt}
onAltChange={(alt) => {
const products = [...module.data.products];
products[pIdx].imageAlt = alt;
updateData({ products });
}}
altPlaceholder={pickAlt(p.title, module.data.sectionTitle, 'Produkt')}
placeholder="https://...produkt.png alebo nahrajte súbor"
/>
<ProductImageAdjust
product={p}
onChange={(patch) => {
const products = [...module.data.products];
products[pIdx] = { ...products[pIdx], ...patch };
updateData({ products });
}}
/>
<div>
<label className="text-[10px] text-gray-500 font-semibold block mb-0.5">Platnosť akcie pre tento produkt (override)</label>
<input
type="text"
value={p.validityOverride || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].validityOverride = e.target.value;
updateData({ products });
}}
placeholder={`Predvolená: ${module.data.globalValidity || '2. 5. – 15. 5. 2026'}`}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white placeholder:text-gray-400"
/>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[10px] text-gray-500">Cena (€)</label>
<input
type="text"
value={p.priceMain}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].priceMain = e.target.value;
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[10px] text-gray-500">Centy</label>
<input
type="text"
value={p.priceSub}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].priceSub = e.target.value;
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[10px] text-gray-500">Zľava %</label>
<input
type="text"
value={p.discountText}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].discountText = e.target.value;
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded p-1 bg-white"
/>
</div>
<div>
<label className="text-[10px] text-gray-500">Merná cena</label>
<input
type="text"
value={p.unitPrice}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].unitPrice = e.target.value;
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded p-1 bg-white text-[10px]"
/>
</div>
</div>
{/* Cena s kartou (voliteľné) – premenné {cena_s_kartou} a {zlava_s_kartou} v cenovke */}
<div className="grid grid-cols-3 gap-2 p-2 rounded-lg bg-amber-50/60 border border-amber-100">
<div>
<label className="text-[10px] text-gray-500">S kartou (€)</label>
<input
type="text"
value={p.cardPriceMain || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardPriceMain = e.target.value;
updateData({ products });
}}
placeholder="—"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[10px] text-gray-500">Centy</label>
<input
type="text"
value={p.cardPriceSub || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardPriceSub = e.target.value;
updateData({ products });
}}
placeholder="—"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[10px] text-gray-500">Zľava s kartou</label>
<input
type="text"
value={p.cardDiscount || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardDiscount = e.target.value;
updateData({ products });
}}
placeholder="—"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white"
/>
</div>
</div>
{/* Predajne */}
<div>
<label className="text-[10px] text-gray-500 block mb-1">Dostupnosť (Piktogramy)</label>
<div className="flex gap-2 text-[10px]">
<label className="flex items-center gap-1 cursor-pointer">
<input
type="checkbox"
checked={p.stores?.potraviny}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].stores.potraviny = e.target.checked;
updateData({ products });
}}
/>
<span>Potraviny</span>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input
type="checkbox"
checked={p.stores?.supermarket}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].stores.supermarket = e.target.checked;
updateData({ products });
}}
/>
<span>Supermarket</span>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input
type="checkbox"
checked={p.stores?.tempo}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].stores.tempo = e.target.checked;
updateData({ products });
}}
/>
<span>Tempo</span>
</label>
</div>
</div>
{/* Grafická cenovka a šablóna */}
<div className="pt-2 border-t border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Tag className="w-3.5 h-3.5 text-red-600" />
<span className="text-xs font-bold text-gray-800">Cenovka</span>
</div>
<button
type="button"
onClick={() => onOpenBadgeManager && onOpenBadgeManager(p.id)}
className="text-[11px] font-bold text-red-600 hover:text-red-700 bg-red-50 hover:bg-red-100 border border-red-200 px-2.5 py-1 rounded-lg transition-colors"
>
Otvoriť Editor cenoviek
</button>
</div>
{/* Výber opakovane použiteľnej šablóny */}
<div>
<label className="text-[10px] text-gray-500 font-semibold block mb-0.5">
Šablóna cenovky
</label>
<select
value={p.badgeCustomTemplate?.id || (p.customBadgePngUrl ? 'custom_png' : 'default_coop')}
onChange={(e) => {
const val = e.target.value;
const products = [...module.data.products];
if (val === 'default_coop') {
products[pIdx].badgeCustomTemplate = undefined;
products[pIdx].customBadgePngUrl = undefined;
products[pIdx].badgeId = undefined;
} else {
const found = (availableBadges || []).find((b) => b.id === val);
if (found) {
products[pIdx].badgeCustomTemplate = found;
products[pIdx].badgeId = found.id;
products[pIdx].badgeWidth = found.width ?? products[pIdx].badgeWidth ?? 110;
products[pIdx].badgeAlign = found.align ?? products[pIdx].badgeAlign ?? 'center';
products[pIdx].badgePaddingTop = found.paddingTop ?? products[pIdx].badgePaddingTop ?? 4;
products[pIdx].badgePaddingBottom = found.paddingBottom ?? products[pIdx].badgePaddingBottom ?? 4;
products[pIdx].customBadgePngUrl = undefined;
}
}
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white font-medium"
>
<option value="default_coop">Predvolená COOP cenovka (červeno-biela)</option>
{(availableBadges || []).map((b) => (
<option key={b.id} value={b.id}>
{b.name}
</option>
))}
</select>
</div>
{/* Vlastná PNG cenovka (výnimka): prekryje šablónu, po odstránení sa vráti generovaná */}
<CustomBadgePngInput
value={p.customBadgePngUrl}
onChange={(url) => {
const products = [...module.data.products];
products[pIdx].customBadgePngUrl = url || undefined;
updateData({ products });
}}
/>
{/* Tlačidlá aplikácie cenovky */}
{p.badgeCustomTemplate && (
<div className="space-y-1.5">
<button
type="button"
onClick={() => {
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na všetky produkty v tejto sekcii?`)) {
// Produkty s vlastnou PNG cenovkou (výnimky) sa neprepisujú
const products = (module.data.products || []).map((prod: ProductItem) => prod.customBadgePngUrl ? prod : ({
...prod,
badgeCustomTemplate: p.badgeCustomTemplate,
badgeId: p.badgeCustomTemplate?.id,
badgeWidth: p.badgeWidth || p.badgeCustomTemplate?.width || 110,
badgeAlign: p.badgeAlign || p.badgeCustomTemplate?.align || 'center',
badgePaddingTop: p.badgePaddingTop ?? p.badgeCustomTemplate?.paddingTop ?? 4,
badgePaddingBottom: p.badgePaddingBottom ?? p.badgeCustomTemplate?.paddingBottom ?? 4,
badgeLayout: p.badgeLayout || 'below'
}));
updateData({ products });
}
}}
className="w-full py-1.5 text-[11px] font-bold text-slate-700 bg-slate-100 hover:bg-slate-200 border border-slate-300 rounded-lg transition-colors text-center"
>
Použiť pre všetky produkty v tejto sekcii
</button>
{onApplyBadgeToAll && (
<button
type="button"
onClick={() => {
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na VŠETKY produkty v celom newsletteri (vrátane vernostných zliav)?`)) {
onApplyBadgeToAll({
...p.badgeCustomTemplate!,
width: p.badgeWidth || p.badgeCustomTemplate?.width || 110,
align: p.badgeAlign || p.badgeCustomTemplate?.align || 'center',
paddingTop: p.badgePaddingTop ?? p.badgeCustomTemplate?.paddingTop ?? 4,
paddingBottom: p.badgePaddingBottom ?? p.badgeCustomTemplate?.paddingBottom ?? 4
}, p.badgeLayout || 'below');
}
}}
className="w-full py-1.5 text-[11px] font-bold text-red-700 bg-red-50 hover:bg-red-100 border border-red-200 rounded-lg transition-colors text-center flex items-center justify-center gap-1.5"
>
<Globe className="w-3 h-3 text-red-600" />
<span>Použiť v celom newsletteri</span>
</button>
)}
</div>
)}
{/* Poloha cenovky: pod / vedľa */}
<div>
<span className="text-[10px] text-gray-600 font-semibold block mb-1">Poloha cenovky</span>
<div className="grid grid-cols-2 gap-1 bg-gray-200 p-0.5 rounded-lg text-[10px] font-bold text-center">
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeLayout = 'below';
updateData({ products });
}}
className={`py-1.5 rounded transition-all flex items-center justify-center gap-1 ${
(p.badgeLayout || 'below') === 'below'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<span>↓ Pod obrázkom</span>
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeLayout = 'side';
updateData({ products });
}}
className={`py-1.5 rounded transition-all flex items-center justify-center gap-1 ${
p.badgeLayout === 'side'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<span>→ Vedľa obrázka</span>
</button>
</div>
</div>
{/* Rýchle ovládanie veľkosti (Width), zarovnania a paddingu */}
<div className="bg-gray-50 p-2.5 rounded-xl border border-gray-200 space-y-2">
{/* Šírka */}
<div>
<div className="flex justify-between items-center text-[10px] text-gray-600 mb-0.5">
<span className="font-semibold">Veľkosť cenovky (Šírka)</span>
<span className="font-bold text-gray-800 bg-white px-1.5 py-0.2 rounded border border-gray-200">
{p.badgeWidth || (p.badgeCustomTemplate ? 110 : 95)} px
</span>
</div>
<input
type="range"
min="70"
max="180"
step="2"
value={p.badgeWidth || (p.badgeCustomTemplate ? 110 : 95)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgeWidth = Number(e.target.value);
updateData({ products });
}}
className="w-full accent-red-600"
/>
</div>
{/* Zarovnanie */}
<div>
<span className="text-[10px] text-gray-600 font-semibold block mb-1">Zarovnanie</span>
<div className="grid grid-cols-3 gap-1 bg-gray-200 p-0.5 rounded-lg text-[10px] font-bold text-center">
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'left';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'left'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Vľavo
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'center';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'center'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Na stred
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'right';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'right'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Vpravo
</button>
</div>
</div>
{/* Padding zhora a zdola */}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[9px] text-gray-600 font-semibold block">Odsadenie zhora (px)</label>
<input
type="number"
min="0"
max="40"
value={p.badgePaddingTop ?? (p.badgeCustomTemplate?.paddingTop ?? 4)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgePaddingTop = Number(e.target.value);
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1 bg-white font-medium"
/>
</div>
<div>
<label className="text-[9px] text-gray-600 font-semibold block">Odsadenie zdola (px)</label>
<input
type="number"
min="0"
max="40"
value={p.badgePaddingBottom ?? (p.badgeCustomTemplate?.paddingBottom ?? 4)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgePaddingBottom = Number(e.target.value);
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1 bg-white font-medium"
/>
</div>
</div>
</div>
</div>
</div>
))}
</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) =>
onSetGlobalValidity
? onSetGlobalValidity(e.target.value)
: updateData({ 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>
</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>
{/* 2 Vernostné produkty */}
<div className="space-y-4 pt-2">
<span className="text-xs font-bold text-gray-800 uppercase tracking-wider block">
Vernostné produkty ({module.data.products?.length || 0})
</span>
{(module.data.products || []).map((p: any, pIdx: number) => (
<div key={p.id || pIdx} className="p-3 bg-gray-50 rounded-xl border border-gray-200 space-y-3">
<span className="text-xs font-bold text-gray-900 block truncate">
{p.title || `Vernostný produkt ${pIdx + 1}`}
</span>
<div>
<label className="text-[10px] text-gray-500 font-semibold block mb-0.5">Názov produktu</label>
<input
type="text"
value={p.title || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].title = e.target.value;
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-medium"
/>
</div>
<ImageUploadInput
label="Obrázok produktu"
value={p.imageUrl || ''}
onChange={(url) => {
const products = [...module.data.products];
products[pIdx].imageUrl = url;
products[pIdx].imageAspect = undefined; // nová fotka = nový pomer strán
updateData({ products });
}}
altValue={p.imageAlt}
onAltChange={(alt) => {
const products = [...module.data.products];
products[pIdx].imageAlt = alt;
updateData({ products });
}}
altPlaceholder={pickAlt(p.title, module.data.sectionTitle, 'Produkt')}
placeholder="https://...produkt.png alebo nahrajte súbor"
/>
<ProductImageAdjust
product={p}
onChange={(patch) => {
const products = [...module.data.products];
products[pIdx] = { ...products[pIdx], ...patch };
updateData({ products });
}}
/>
<div>
<label className="text-[10px] text-gray-500 font-semibold block mb-0.5">Platnosť akcie pre tento produkt (override)</label>
<input
type="text"
value={p.validityOverride || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].validityOverride = e.target.value;
updateData({ products });
}}
placeholder={`Predvolená: ${module.data.globalValidity || '2. 5. – 15. 5. 2026'}`}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white placeholder:text-gray-400"
/>
</div>
{/* Sekcia Bez karty */}
<div className="p-2.5 bg-red-50/40 rounded-lg border border-red-100 space-y-2">
<span className="text-[11px] font-bold text-red-700 block">Bez karty</span>
<div className="grid grid-cols-3 gap-1.5">
<div>
<label className="text-[9px] text-gray-500">Zľava</label>
<input
type="text"
value={p.noCardDiscount || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].noCardDiscount = e.target.value;
updateData({ products });
}}
placeholder="21%"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[9px] text-gray-500">Cena (€)</label>
<input
type="text"
value={p.noCardPriceMain || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].noCardPriceMain = e.target.value;
updateData({ products });
}}
placeholder="0"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[9px] text-gray-500">Centy</label>
<input
type="text"
value={p.noCardPriceSub || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].noCardPriceSub = e.target.value;
updateData({ products });
}}
placeholder="79"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
</div>
<div>
<label className="text-[9px] text-gray-500">Merná cena (bez karty)</label>
<input
type="text"
value={p.noCardUnitPrice || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].noCardUnitPrice = e.target.value;
updateData({ products });
}}
placeholder="1kg = 9,88 €"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white"
/>
</div>
</div>
{/* Sekcia S kartou */}
<div className="p-2.5 bg-amber-50/50 rounded-lg border border-amber-200 space-y-2">
<span className="text-[11px] font-bold text-amber-900 block">S kartou (Klub)</span>
<div className="grid grid-cols-3 gap-1.5">
<div>
<label className="text-[9px] text-gray-500">Zľava</label>
<input
type="text"
value={p.cardDiscount || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardDiscount = e.target.value;
updateData({ products });
}}
placeholder="38%"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[9px] text-gray-500">Cena (€)</label>
<input
type="text"
value={p.cardPriceMain || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardPriceMain = e.target.value;
updateData({ products });
}}
placeholder="0"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[9px] text-gray-500">Centy</label>
<input
type="text"
value={p.cardPriceSub || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardPriceSub = e.target.value;
updateData({ products });
}}
placeholder="62"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
</div>
<div>
<label className="text-[9px] text-gray-500">Merná cena (s kartou)</label>
<input
type="text"
value={p.cardUnitPrice || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardUnitPrice = e.target.value;
updateData({ products });
}}
placeholder="1kg = 7,75 €"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white"
/>
</div>
</div>
{/* Predajne */}
<div>
<label className="text-[10px] text-gray-500 block mb-1">Dostupnosť (Piktogramy)</label>
<div className="flex gap-2 text-[10px]">
<label className="flex items-center gap-1 cursor-pointer">
<input
type="checkbox"
checked={p.stores?.potraviny}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].stores.potraviny = e.target.checked;
updateData({ products });
}}
/>
<span>Potraviny</span>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input
type="checkbox"
checked={p.stores?.supermarket}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].stores.supermarket = e.target.checked;
updateData({ products });
}}
/>
<span>Supermarket</span>
</label>
<label className="flex items-center gap-1 cursor-pointer">
<input
type="checkbox"
checked={p.stores?.tempo}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].stores.tempo = e.target.checked;
updateData({ products });
}}
/>
<span>Tempo</span>
</label>
</div>
</div>
{/* Grafická cenovka a šablóna pre vernostné produkty */}
<div className="pt-2 border-t border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Tag className="w-3.5 h-3.5 text-red-600" />
<span className="text-xs font-bold text-gray-800">Cenovka</span>
</div>
<button
type="button"
onClick={() => onOpenBadgeManager && onOpenBadgeManager(p.id)}
className="text-[11px] font-bold text-red-600 hover:text-red-700 bg-red-50 hover:bg-red-100 border border-red-200 px-2.5 py-1 rounded-lg transition-colors"
>
Otvoriť Editor cenoviek
</button>
</div>
{/* Výber šablóny */}
<div>
<label className="text-[10px] text-gray-500 font-semibold block mb-0.5">
Šablóna cenovky
</label>
<select
value={p.badgeCustomTemplate?.id || 'default_loyalty'}
onChange={(e) => {
const val = e.target.value;
const products = [...module.data.products];
if (val === 'default_loyalty') {
products[pIdx].badgeCustomTemplate = undefined;
products[pIdx].badgeId = undefined;
products[pIdx].customBadgePngUrl = undefined;
} else {
const found = (availableBadges || []).find((b) => b.id === val);
if (found) {
products[pIdx].badgeCustomTemplate = found;
products[pIdx].badgeId = found.id;
products[pIdx].badgeWidth = found.width ?? products[pIdx].badgeWidth ?? 120;
products[pIdx].badgeAlign = found.align ?? products[pIdx].badgeAlign ?? 'center';
products[pIdx].badgePaddingTop = found.paddingTop ?? products[pIdx].badgePaddingTop ?? 4;
products[pIdx].badgePaddingBottom = found.paddingBottom ?? products[pIdx].badgePaddingBottom ?? 4;
products[pIdx].customBadgePngUrl = undefined;
}
}
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white font-medium"
>
<option value="default_loyalty">Predvolený Dual sticker (Bez karty / S kartou)</option>
{(availableBadges || []).map((b) => (
<option key={b.id} value={b.id}>
{b.name}
</option>
))}
</select>
</div>
{/* Vlastná PNG cenovka (výnimka): prekryje šablónu, po odstránení sa vráti generovaná */}
<CustomBadgePngInput
value={p.customBadgePngUrl}
onChange={(url) => {
const products = [...module.data.products];
products[pIdx].customBadgePngUrl = url || undefined;
updateData({ products });
}}
/>
{/* Tlačidlá aplikácie cenovky */}
{p.badgeCustomTemplate && (
<div className="space-y-1.5">
<button
type="button"
onClick={() => {
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na všetky vernostné produkty?`)) {
// Produkty s vlastnou PNG cenovkou (výnimky) sa neprepisujú
const products = (module.data.products || []).map((prod: any) => prod.customBadgePngUrl ? prod : ({
...prod,
badgeCustomTemplate: p.badgeCustomTemplate,
badgeId: p.badgeCustomTemplate?.id,
badgeWidth: p.badgeWidth || p.badgeCustomTemplate?.width || 120,
badgeAlign: p.badgeAlign || p.badgeCustomTemplate?.align || 'center',
badgePaddingTop: p.badgePaddingTop ?? p.badgeCustomTemplate?.paddingTop ?? 4,
badgePaddingBottom: p.badgePaddingBottom ?? p.badgeCustomTemplate?.paddingBottom ?? 4,
badgeLayout: p.badgeLayout || 'below'
}));
updateData({ products });
}
}}
className="w-full py-1.5 text-[11px] font-bold text-slate-700 bg-slate-100 hover:bg-slate-200 border border-slate-300 rounded-lg transition-colors text-center"
>
Použiť pre všetky produkty v tejto sekcii
</button>
{onApplyBadgeToAll && (
<button
type="button"
onClick={() => {
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na VŠETKY produkty v celom newsletteri?`)) {
onApplyBadgeToAll({
...p.badgeCustomTemplate!,
width: p.badgeWidth || p.badgeCustomTemplate?.width || 120,
align: p.badgeAlign || p.badgeCustomTemplate?.align || 'center',
paddingTop: p.badgePaddingTop ?? p.badgeCustomTemplate?.paddingTop ?? 4,
paddingBottom: p.badgePaddingBottom ?? p.badgeCustomTemplate?.paddingBottom ?? 4
}, p.badgeLayout || 'below');
}
}}
className="w-full py-1.5 text-[11px] font-bold text-red-700 bg-red-50 hover:bg-red-100 border border-red-200 rounded-lg transition-colors text-center flex items-center justify-center gap-1.5"
>
<Globe className="w-3 h-3 text-red-600" />
<span>Použiť v celom newsletteri</span>
</button>
)}
</div>
)}
{/* Poloha cenovky: pod / vedľa */}
{p.badgeCustomTemplate && (
<div>
<span className="text-[10px] text-gray-600 font-semibold block mb-1">Poloha cenovky</span>
<div className="grid grid-cols-2 gap-1 bg-gray-200 p-0.5 rounded-lg text-[10px] font-bold text-center">
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeLayout = 'below';
updateData({ products });
}}
className={`py-1.5 rounded transition-all flex items-center justify-center gap-1 ${
(p.badgeLayout || 'below') === 'below'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<span>↓ Pod obrázkom</span>
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeLayout = 'side';
updateData({ products });
}}
className={`py-1.5 rounded transition-all flex items-center justify-center gap-1 ${
p.badgeLayout === 'side'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<span>→ Vedľa obrázka</span>
</button>
</div>
</div>
)}
{/* Rýchle ovládanie veľkosti (Width), zarovnania a paddingu pre vernostnú cenovku */}
{(p.badgeCustomTemplate || p.customBadgePngUrl) && (
<div className="bg-gray-50 p-2.5 rounded-xl border border-gray-200 space-y-2">
{/* Šírka */}
<div>
<div className="flex justify-between items-center text-[10px] text-gray-600 mb-0.5">
<span className="font-semibold">Veľkosť cenovky (Šírka)</span>
<span className="font-bold text-gray-800 bg-white px-1.5 py-0.2 rounded border border-gray-200">
{p.badgeWidth || (p.badgeCustomTemplate ? 120 : 140)} px
</span>
</div>
<input
type="range"
min="80"
max="180"
step="2"
value={p.badgeWidth || (p.badgeCustomTemplate ? 120 : 140)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgeWidth = Number(e.target.value);
updateData({ products });
}}
className="w-full accent-red-600 cursor-pointer"
/>
</div>
{/* Zarovnanie */}
<div>
<span className="text-[10px] text-gray-600 font-semibold block mb-1">
Zarovnanie cenovky
</span>
<div className="grid grid-cols-3 gap-1 bg-gray-200/70 p-0.5 rounded-lg text-xs font-semibold text-center">
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'left';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'left'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Vľavo
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'center';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'center'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Na stred
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'right';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'right'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Vpravo
</button>
</div>
</div>
{/* Padding zhora a zdola */}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[9px] text-gray-600 font-semibold block">Odsadenie zhora (px)</label>
<input
type="number"
min="0"
max="40"
value={p.badgePaddingTop ?? (p.badgeCustomTemplate?.paddingTop ?? 4)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgePaddingTop = Number(e.target.value);
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1 bg-white font-medium"
/>
</div>
<div>
<label className="text-[9px] text-gray-600 font-semibold block">Odsadenie zdola (px)</label>
<input
type="number"
min="0"
max="40"
value={p.badgePaddingBottom ?? (p.badgeCustomTemplate?.paddingBottom ?? 4)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgePaddingBottom = Number(e.target.value);
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1 bg-white font-medium"
/>
</div>
</div>
</div>
)}
</div>
</div>
))}
</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>
<input
type="text"
value={module.data.item1?.title || ''}
onChange={(e) => updateData({
item1: { ...module.data.item1, title: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold"
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>
<input
type="text"
value={module.data.item2?.title || ''}
onChange={(e) => updateData({
item2: { ...module.data.item2, title: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold"
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>
<input
type="text"
value={module.data.item1?.title || ''}
onChange={(e) => updateData({
item1: { ...module.data.item1, title: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold"
/>
</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>
<input
type="text"
value={module.data.item2?.title || ''}
onChange={(e) => updateData({
item2: { ...module.data.item2, title: e.target.value }
})}
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold"
/>
</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>
);
};