Files
Coop-NL/src/components/editor/ProductEditor.tsx
T

626 lines
27 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';
// Editor jedného produktu (bežné aj vernostné produkty) – zbalený po produktoch, len to, čo treba:
// texty, fotka (zoom/posun), cenovka (šablóna, veľkosť, ceny), dostupnosť a ďalšie možnosti.
import React, { useEffect, useRef } from 'react';
import { BadgeTemplate, CardProduct, ProductLabel, ProductPhoto } from '@/types/newsletter';
import { ChevronDown, ChevronUp, Trash2, Pencil, Globe, Layers } from 'lucide-react';
import { ImageUploadInput } from './ImageUploadInput';
import { pickAlt } from '@/lib/altText';
import { DEFAULT_STORES_OFFSET_X, DEFAULT_STORES_OFFSET_Y, DEFAULT_STORES_SIZE, MAIN_PHOTO, badgeWidthFor, photoStack, productCardAlt } from '@/lib/productCard';
export 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: '#e3f0e4', border: '#c8e6c9' },
{ label: 'Biela', sub: 'Čistá', color: '#ffffff', border: '#e5e7eb' }
];
type Variant = 'standard' | 'loyalty';
// Textové polia cien produktu (bežné aj vernostné)
type TextKey =
| 'priceMain' | 'priceSub' | 'discountText'
| 'cardPriceMain' | 'cardPriceSub' | 'cardDiscount' | 'cardUnitPrice'
| 'noCardPriceMain' | 'noCardPriceSub' | 'noCardDiscount' | 'noCardUnitPrice';
type Patch = Record<string, unknown>;
interface ProductEditorProps {
product: CardProduct;
index: number;
variant: Variant;
availableBadges: BadgeTemplate[];
open: boolean; // harmonika: otvorený je vždy len jeden produkt
onToggle: () => void;
onChange: (patch: Patch) => void;
onDelete?: () => void;
onOpenBadgeEditor?: () => void;
onApplyBadgeToSection?: () => void;
onApplyBadgeToAll?: () => void;
onApplyStoresOffsetToAll?: (x: number, y: number, size: number) => void; // poloha a veľkosť štítkov pre všetky produkty
}
const inputCls = 'w-full text-xs border border-gray-300 rounded-lg px-2 py-1.5 bg-white';
const Field: React.FC<{ label: string; children: React.ReactNode; className?: string }> = ({ label, children, className }) => (
<label className={`block ${className || ''}`}>
<span className="text-[10px] text-gray-500 font-semibold block mb-0.5">{label}</span>
{children}
</label>
);
const TextInput: React.FC<{ value?: string; onChange: (v: string) => void; placeholder?: string; bold?: boolean }> = ({
value,
onChange,
placeholder,
bold
}) => (
<input
type="text"
value={value || ''}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className={`${inputCls} ${bold ? 'font-bold' : ''}`}
/>
);
const Slider: React.FC<{
label: string;
value: number;
display: string;
min: number;
max: number;
step: number;
onChange: (v: number) => void;
}> = ({ label, value, display, min, max, step, onChange }) => (
<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) => onChange(Number(e.target.value))}
className="w-full h-1 accent-red-600 cursor-pointer"
/>
<span className="text-[10px] font-mono text-gray-700 text-right">{display}</span>
</div>
);
const Block: React.FC<{ title: string; action?: React.ReactNode; children: React.ReactNode }> = ({ title, action, children }) => (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold text-gray-700 uppercase tracking-wider">{title}</span>
{action}
</div>
{children}
</div>
);
const ResetLink: React.FC<{ onClick: () => void }> = ({ onClick }) => (
<button type="button" onClick={onClick} className="text-[10px] font-bold text-gray-400 hover:text-red-600">
Obnoviť
</button>
);
// Poradie fotiek ako vrstvy: navrchu zoznamu je fotka najviac vpredu
const PhotoOrderList: React.FC<{ product: CardProduct; onChange: (order: string[]) => void }> = ({ product, onChange }) => {
const stack = photoStack(product); // odzadu dopredu
const extras = product.extraPhotos || [];
const move = (id: string, dir: 1 | -1) => {
const i = stack.indexOf(id);
const j = i + dir;
if (j < 0 || j >= stack.length) return;
const next = [...stack];
[next[i], next[j]] = [next[j], next[i]];
onChange(next);
};
const front = [...stack].reverse();
return (
<div className="rounded-lg border border-gray-200 bg-white p-2 space-y-1">
<span className="text-[10px] font-bold text-gray-600">Poradie fotiek (hore = vpredu)</span>
{front.map((id, k) => {
const isMain = id === MAIN_PHOTO;
const extraIdx = extras.findIndex((f) => f.id === id);
const url = isMain ? product.imageUrl : extras[extraIdx]?.url;
return (
<div key={id} className={`flex items-center gap-2 rounded-md px-1.5 py-1 ${isMain ? 'bg-red-50' : 'bg-gray-50'}`}>
<span className="w-7 h-7 rounded bg-white border border-gray-200 overflow-hidden flex items-center justify-center shrink-0">
{url && (
// eslint-disable-next-line @next/next/no-img-element
<img src={url} alt="" className="w-full h-full object-contain" />
)}
</span>
<span className="flex-1 text-[11px] font-semibold text-gray-800">{isMain ? 'Hlavná fotka' : `Fotka ${extraIdx + 2}`}</span>
<button type="button" disabled={k === 0} onClick={() => move(id, 1)} className="p-0.5 text-gray-500 hover:text-gray-900 disabled:opacity-25" title="Dopredu">
<ChevronUp className="w-3.5 h-3.5" />
</button>
<button type="button" disabled={k === front.length - 1} onClick={() => move(id, -1)} className="p-0.5 text-gray-500 hover:text-gray-900 disabled:opacity-25" title="Dozadu">
<ChevronDown className="w-3.5 h-3.5" />
</button>
</div>
);
})}
</div>
);
};
// Ďalšia fotka v karte (bundle): obrázok, poloha, veľkosť
const ExtraPhotoEditor: React.FC<{
index: number;
photo: ProductPhoto;
onChange: (patch: Partial<ProductPhoto>) => void;
onRemove: () => void;
}> = ({ index, photo, onChange, onRemove }) => (
<div className="rounded-lg border border-gray-200 bg-white p-2 space-y-1.5">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold text-gray-600">Fotka {index + 2}</span>
<button type="button" onClick={onRemove} className="text-gray-300 hover:text-red-600 p-0.5" title="Odstrániť fotku">
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
<ImageUploadInput label="" value={photo.url} onChange={(url) => onChange({ url })} />
<Slider label="Vodorovne" value={photo.x} display={`${photo.x} %`} min={0} max={100} step={1} onChange={(v) => onChange({ x: v })} />
<Slider label="Zvisle" value={photo.y} display={`${photo.y} %`} min={0} max={100} step={1} onChange={(v) => onChange({ y: v })} />
<Slider label="Veľkosť" value={photo.scale} display={`${Math.round(photo.scale * 100)} %`} min={0.2} max={2} step={0.05} onChange={(v) => onChange({ scale: v })} />
</div>
);
// Nastavenie jedného vlastného štítka na fotke
const LABEL_COLORS: Array<[string, string]> = [
['#e2001a', 'Červená'],
['#ffde00', 'Žltá'],
['#003399', 'Modrá'],
['#000000', 'Čierna'],
['#ffffff', 'Biela']
];
const LabelEditor: React.FC<{
label: ProductLabel;
onChange: (patch: Partial<ProductLabel>) => void;
onRemove: () => void;
}> = ({ label, onChange, onRemove }) => {
const swatches = (key: 'bg' | 'color') => (
<div className="flex items-center gap-1">
{LABEL_COLORS.map(([c, name]) => (
<button
key={c}
type="button"
title={name}
onClick={() => onChange({ [key]: c })}
className={`w-4 h-4 rounded-full border ${label[key]?.toLowerCase() === c ? 'ring-2 ring-red-500 border-white' : 'border-black/20'}`}
style={{ backgroundColor: c }}
/>
))}
<input
type="color"
value={label[key] || '#ffffff'}
onChange={(e) => onChange({ [key]: e.target.value })}
className="w-4 h-4 rounded cursor-pointer border border-gray-300 p-0"
title="Vlastná farba"
/>
</div>
);
return (
<div className="rounded-lg border border-gray-200 bg-white p-2 space-y-1.5">
<div className="flex items-center gap-1.5">
<input
value={label.text}
onChange={(e) => onChange({ text: e.target.value })}
placeholder="Text štítka (<br> = nový riadok)"
className="flex-1 min-w-0 text-xs font-bold uppercase border border-gray-300 rounded-lg px-2 py-1"
/>
<button type="button" onClick={onRemove} className="text-gray-300 hover:text-red-600 p-0.5" title="Zmazať štítok">
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
<Slider label="Vodorovne" value={label.x} display={`${label.x} %`} min={0} max={100} step={1} onChange={(v) => onChange({ x: v })} />
<Slider label="Zvisle" value={label.y} display={`${label.y} %`} min={0} max={100} step={1} onChange={(v) => onChange({ y: v })} />
<Slider label="Veľkosť" value={label.size} display={`${label.size} px`} min={6} max={40} step={1} onChange={(v) => onChange({ size: v })} />
<Slider label="Natočenie" value={label.rotate || 0} display={`${label.rotate || 0}°`} min={-45} max={45} step={1} onChange={(v) => onChange({ rotate: v })} />
<div className="grid grid-cols-2 gap-2 pt-0.5">
<div className="space-y-1">
<span className="text-[10px] font-semibold text-gray-600">Pozadie</span>
{swatches('bg')}
</div>
<div className="space-y-1">
<span className="text-[10px] font-semibold text-gray-600">Text</span>
{swatches('color')}
</div>
</div>
</div>
);
};
// Šablóna používa ceny s kartou → zobrazíme aj polia s kartou
const usesCardPrice = (t?: BadgeTemplate) => !!t && JSON.stringify(t.layersConfig || []).includes('_s_kartou');
export const ProductEditor: React.FC<ProductEditorProps> = ({
product: p,
index,
variant,
availableBadges,
open,
onToggle,
onChange,
onDelete,
onOpenBadgeEditor,
onApplyBadgeToSection,
onApplyBadgeToAll,
onApplyStoresOffsetToAll
}) => {
// Po otvorení (aj klikom na produkt v náhľade) posunieme panel na tento produkt
const rootRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (open) rootRef.current?.scrollIntoView({ block: 'start', behavior: 'smooth' });
}, [open]);
const isLoyalty = variant === 'loyalty';
const set = (key: string) => (v: unknown) => onChange({ [key]: v });
const scale = p.imageScale ?? 1;
const offsetX = p.imageOffsetX || 0;
const offsetY = p.imageOffsetY || 0;
const storesX = p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X;
const storesY = p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y;
const storesSize = p.storesSize ?? DEFAULT_STORES_SIZE;
const hasStores = !!(p.stores?.potraviny || p.stores?.supermarket || p.stores?.tempo);
const badgeWidth = badgeWidthFor(p, variant);
const selectBadge = (id: string) => {
if (id === 'default') {
onChange({ badgeCustomTemplate: undefined, badgeId: undefined, customBadgePngUrl: undefined, badgeWidth: undefined });
return;
}
const found = availableBadges.find((b) => b.id === id);
if (found) {
onChange({ badgeCustomTemplate: found, badgeId: found.id, badgeWidth: found.width ?? 110, customBadgePngUrl: undefined });
}
};
const text = (key: TextKey) => p[key] as string | undefined;
const priceRow = (label: string, mainKey: TextKey, subKey: TextKey, discountKey: TextKey, unitKey?: TextKey) => (
<div className="space-y-1">
<span className="text-[10px] font-semibold text-gray-600">{label}</span>
<div className={`grid ${unitKey ? 'grid-cols-[1fr_1fr_1fr]' : 'grid-cols-3'} gap-1.5`}>
<Field label="€">
<TextInput value={text(mainKey)} onChange={set(mainKey)} bold />
</Field>
<Field label="Centy">
<TextInput value={text(subKey)} onChange={set(subKey)} bold />
</Field>
<Field label="Zľava">
<TextInput value={text(discountKey)} onChange={set(discountKey)} placeholder="25%" />
</Field>
</div>
{unitKey && (
<Field label="Jednotková cena">
<TextInput value={text(unitKey)} onChange={set(unitKey)} placeholder="jednotková cena 9,43 EUR/kg" />
</Field>
)}
</div>
);
return (
<div
ref={rootRef}
className={`rounded-xl border bg-gray-50 overflow-hidden scroll-mt-2 ${open ? 'border-red-400 ring-1 ring-red-200' : 'border-gray-200'}`}
>
{/* Hlavička produktu – klik rozbalí / zbalí */}
<div className="flex items-center gap-2 px-2.5 py-2 bg-white">
<button type="button" onClick={onToggle} className="flex-1 flex items-center gap-2 min-w-0 text-left">
<span className="w-8 h-8 rounded-md bg-gray-100 border border-gray-200 shrink-0 overflow-hidden flex items-center justify-center">
{p.imageUrl && (
// eslint-disable-next-line @next/next/no-img-element
<img src={p.imageUrl} alt="" className="w-full h-full object-contain" />
)}
</span>
<span className="text-xs font-bold text-gray-900 truncate">{pickAlt(p.title) || `Produkt ${index + 1}`}</span>
<ChevronDown className={`w-4 h-4 text-gray-400 shrink-0 ml-auto transition-transform ${open ? 'rotate-180' : ''}`} />
</button>
{onDelete && (
<button
type="button"
onClick={() => {
if (confirm(`Zmazať produkt "${pickAlt(p.title) || index + 1}"?`)) onDelete();
}}
className="text-gray-300 hover:text-red-600 p-1"
title="Zmazať produkt"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
)}
</div>
{open && (
<div className="p-3 space-y-4 border-t border-gray-200">
{/* TEXTY */}
<Block title="Texty">
<Field label="Názov (Enter = nový riadok)">
<textarea
rows={2}
value={p.title || ''}
onChange={(e) => onChange({ title: e.target.value })}
className={`${inputCls} font-bold resize-y`}
/>
</Field>
<div className="grid grid-cols-2 gap-1.5">
<Field label="Gramáž / množstvo">
<TextInput value={p.quantity} onChange={set('quantity')} placeholder="35 g" />
</Field>
{isLoyalty ? (
<Field label="Poznámka">
<TextInput value={p.note} onChange={set('note')} placeholder="Ilustračné foto" />
</Field>
) : (
<Field label="Jednotková cena">
<TextInput value={p.unitPrice} onChange={set('unitPrice')} placeholder="jednotková cena 9,43 EUR/kg" />
</Field>
)}
</div>
{!isLoyalty && (
<Field label="Poznámka">
<TextInput value={p.note} onChange={set('note')} placeholder="Ilustračné foto" />
</Field>
)}
</Block>
{/* FOTKA */}
<Block
title="Fotka"
action={(scale !== 1 || offsetX !== 0 || offsetY !== 0) && (
<ResetLink onClick={() => onChange({ imageScale: undefined, imageOffsetX: undefined, imageOffsetY: undefined })} />
)}
>
<ImageUploadInput
label=""
value={p.imageUrl || ''}
onChange={set('imageUrl')}
altValue={p.imageAlt}
onAltChange={set('imageAlt')}
altPlaceholder={productCardAlt({ ...p, imageAlt: undefined }, variant)}
/>
<Slider label="Veľkosť" value={scale} display={`${Math.round(scale * 100)} %`} min={0.4} max={2.5} step={0.05} onChange={set('imageScale')} />
<Slider label="Vodorovne" value={offsetX} display={`${offsetX}`} min={-150} max={150} step={1} onChange={set('imageOffsetX')} />
<Slider label="Zvisle" value={offsetY} display={`${offsetY}`} min={-120} max={120} step={1} onChange={set('imageOffsetY')} />
{/* Ďalšie fotky (bundle) */}
{(p.extraPhotos || []).map((f, i) => (
<ExtraPhotoEditor
key={f.id}
index={i}
photo={f}
onChange={(patch) => onChange({ extraPhotos: (p.extraPhotos || []).map((x, j) => (j === i ? { ...x, ...patch } : x)) })}
onRemove={() =>
onChange({
extraPhotos: (p.extraPhotos || []).filter((_, j) => j !== i),
photoOrder: photoStack(p).filter((id) => id !== f.id)
})
}
/>
))}
<button
type="button"
onClick={() => {
const id = `ph-${Date.now()}`;
onChange({
extraPhotos: [...(p.extraPhotos || []), { id, url: '', x: 62, y: 52, scale: 0.75 }],
photoOrder: [...photoStack(p), id] // nová fotka ide dopredu
});
}}
className="w-full text-[10px] font-bold text-gray-600 border border-dashed border-gray-300 hover:border-gray-400 hover:bg-white rounded-lg py-1.5"
>
+ Pridať ďalšiu fotku (bundle)
</button>
{(p.extraPhotos || []).length > 0 && <PhotoOrderList product={p} onChange={(photoOrder) => onChange({ photoOrder })} />}
</Block>
{/* CENOVKA */}
<Block title="Cenovka">
<div className="flex items-center gap-1.5">
<select
value={p.customBadgePngUrl ? 'png' : p.badgeCustomTemplate?.id || 'default'}
onChange={(e) => selectBadge(e.target.value)}
className={`${inputCls} font-medium`}
>
<option value="default">{isLoyalty ? 'Predvolená (bez karty / s kartou)' : 'Predvolená COOP cenovka'}</option>
{availableBadges.map((b) => (
<option key={b.id} value={b.id}>
{b.name}
</option>
))}
{p.customBadgePngUrl && <option value="png">Vlastná PNG cenovka</option>}
</select>
{onOpenBadgeEditor && (
<button
type="button"
onClick={onOpenBadgeEditor}
className="shrink-0 p-1.5 rounded-lg border border-gray-300 bg-white text-gray-600 hover:text-red-600 hover:border-red-300"
title="Editor cenoviek (vytvoriť / upraviť šablóny)"
>
<Pencil className="w-3.5 h-3.5" />
</button>
)}
</div>
<Slider label="Veľkosť" value={badgeWidth} display={`${badgeWidth} px`} min={60} max={200} step={1} onChange={set('badgeWidth')} />
{isLoyalty ? (
<>
{priceRow('S kartou', 'cardPriceMain', 'cardPriceSub', 'cardDiscount', 'cardUnitPrice')}
{priceRow('Bez karty', 'noCardPriceMain', 'noCardPriceSub', 'noCardDiscount', 'noCardUnitPrice')}
</>
) : (
<>
{priceRow('Cena', 'priceMain', 'priceSub', 'discountText')}
{usesCardPrice(p.badgeCustomTemplate) && priceRow('S kartou', 'cardPriceMain', 'cardPriceSub', 'cardDiscount')}
</>
)}
{p.badgeCustomTemplate && (onApplyBadgeToSection || onApplyBadgeToAll) && (
<div className="flex flex-wrap gap-1.5 pt-1">
{onApplyBadgeToSection && (
<button
type="button"
onClick={onApplyBadgeToSection}
className="flex items-center gap-1 text-[10px] font-bold text-gray-700 bg-white border border-gray-300 hover:bg-gray-100 px-2 py-1 rounded-lg"
>
<Layers className="w-3 h-3" /> Použiť v celej sekcii
</button>
)}
{onApplyBadgeToAll && (
<button
type="button"
onClick={onApplyBadgeToAll}
className="flex items-center gap-1 text-[10px] font-bold text-red-700 bg-red-50 border border-red-200 hover:bg-red-100 px-2 py-1 rounded-lg"
>
<Globe className="w-3 h-3" /> Použiť v celom newsletteri
</button>
)}
</div>
)}
</Block>
{/* DOSTUPNOSŤ */}
<Block
title="Dostupnosť"
action={hasStores && (storesX !== DEFAULT_STORES_OFFSET_X || storesY !== DEFAULT_STORES_OFFSET_Y || storesSize !== DEFAULT_STORES_SIZE) && (
<ResetLink onClick={() => onChange({ storesOffsetX: undefined, storesOffsetY: undefined, storesSize: undefined })} />
)}
>
<div className="grid grid-cols-3 gap-1.5">
{(
[
['potraviny', 'Potraviny', 'bg-[#e2001a]'],
['supermarket', 'Supermarket', 'bg-[#f9890e]'],
['tempo', 'Tempo', 'bg-[#005239]']
] as const
).map(([key, label, color]) => {
const on = !!p.stores?.[key];
return (
<button
key={key}
type="button"
onClick={() => onChange({ stores: { ...(p.stores || {}), [key]: !on } })}
className={`text-[10px] font-bold py-1.5 rounded-lg border transition-colors ${
on ? `${color} text-white border-transparent` : 'bg-white text-gray-400 border-gray-200'
}`}
>
{label}
</button>
);
})}
</div>
{hasStores && (
<>
<Slider label="Štítky ↔" value={storesX} display={`${storesX}`} min={-80} max={80} step={1} onChange={set('storesOffsetX')} />
<Slider label="Štítky ↕" value={storesY} display={`${storesY}`} min={-40} max={60} step={1} onChange={set('storesOffsetY')} />
<Slider label="Veľkosť" value={storesSize} display={`${storesSize} px`} min={8} max={30} step={1} onChange={set('storesSize')} />
{onApplyStoresOffsetToAll && (
<button
type="button"
onClick={() => onApplyStoresOffsetToAll(storesX, storesY, storesSize)}
className="flex items-center gap-1 text-[10px] font-bold text-red-700 bg-red-50 border border-red-200 hover:bg-red-100 px-2 py-1 rounded-lg"
>
<Globe className="w-3 h-3" /> Polohu a veľkosť štítkov použiť na všetky produkty
</button>
)}
</>
)}
</Block>
{/* VLASTNÉ ŠTÍTKY NA FOTKE */}
<Block
title="Štítky na fotke"
action={
<button
type="button"
onClick={() =>
onChange({
labels: [
...(p.labels || []),
{ id: `lbl-${Date.now()}`, text: 'NOVINKA', x: 30, y: 20, size: 12, bg: '#e2001a', color: '#ffffff', rotate: 0 }
]
})
}
className="text-[10px] font-bold text-red-700 hover:text-red-900"
>
+ Pridať štítok
</button>
}
>
{(p.labels || []).length === 0 && (
<p className="text-[10px] text-gray-400">Napr. „PODIEL MÄSA 92 %“ alebo „NOVINKA“ priamo na fotke produktu.</p>
)}
{(p.labels || []).map((l, i) => (
<LabelEditor
key={l.id}
label={l}
onChange={(patch) => onChange({ labels: (p.labels || []).map((x, j) => (j === i ? { ...x, ...patch } : x)) })}
onRemove={() => onChange({ labels: (p.labels || []).filter((_, j) => j !== i) })}
/>
))}
</Block>
{/* ĎALŠIE MOŽNOSTI */}
<details className="group">
<summary className="text-[10px] font-bold text-gray-500 uppercase tracking-wider cursor-pointer select-none hover:text-gray-800">
Ďalšie možnosti
</summary>
<div className="pt-3 space-y-3">
<Field label="Farba karty">
<div className="flex items-center gap-1.5 flex-wrap">
{PRODUCT_BG_PRESETS.map((preset) => (
<button
key={preset.color}
type="button"
title={preset.label}
onClick={() => onChange({ cardBgColor: preset.color })}
className={`w-6 h-6 rounded-full border ${
(p.cardBgColor || '').toLowerCase() === preset.color ? 'ring-2 ring-red-500 border-white' : 'border-black/15'
}`}
style={{ backgroundColor: preset.color }}
/>
))}
<input
type="color"
value={p.cardBgColor || '#feeaea'}
onChange={(e) => onChange({ cardBgColor: e.target.value })}
className="w-6 h-6 rounded cursor-pointer border border-gray-300 p-0 bg-white"
title="Vlastná farba"
/>
</div>
</Field>
<Field label="Vlastná platnosť (len pre tento produkt)">
<TextInput value={p.validityOverride} onChange={set('validityOverride')} placeholder="napr. 2. 5. – 9. 5. 2026" />
</Field>
<div className="space-y-1.5">
<ImageUploadInput
label="Vlastná PNG cenovka (výnimka)"
value={p.customBadgePngUrl || ''}
onChange={(url) => onChange({ customBadgePngUrl: url || undefined })}
placeholder="Celá cenovka ako PNG – prekryje šablónu"
/>
{p.customBadgePngUrl && (
<button
type="button"
onClick={() => onChange({ customBadgePngUrl: undefined })}
className="w-full py-1 text-[10px] font-bold text-amber-800 bg-amber-50 hover:bg-amber-100 border border-amber-200 rounded-lg"
>
Odstrániť PNG a vrátiť šablónu
</button>
)}
</div>
</div>
</details>
</div>
)}
</div>
);
};