'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; 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 }) => ( ); const TextInput: React.FC<{ value?: string; onChange: (v: string) => void; placeholder?: string; bold?: boolean }> = ({ value, onChange, placeholder, bold }) => ( 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 }) => (
{label} onChange(Number(e.target.value))} className="w-full h-1 accent-red-600 cursor-pointer" /> {display}
); const Block: React.FC<{ title: string; action?: React.ReactNode; children: React.ReactNode }> = ({ title, action, children }) => (
{title} {action}
{children}
); const ResetLink: React.FC<{ onClick: () => void }> = ({ onClick }) => ( ); // 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 (
Poradie fotiek (hore = vpredu) {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 (
{url && ( // eslint-disable-next-line @next/next/no-img-element )} {isMain ? 'Hlavná fotka' : `Fotka ${extraIdx + 2}`}
); })}
); }; // Ďalšia fotka v karte (bundle): obrázok, poloha, veľkosť const ExtraPhotoEditor: React.FC<{ index: number; photo: ProductPhoto; onChange: (patch: Partial) => void; onRemove: () => void; }> = ({ index, photo, onChange, onRemove }) => (
Fotka {index + 2}
onChange({ url })} /> onChange({ x: v })} /> onChange({ y: v })} /> onChange({ scale: v })} />
); // 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) => void; onRemove: () => void; }> = ({ label, onChange, onRemove }) => { const swatches = (key: 'bg' | 'color') => (
{LABEL_COLORS.map(([c, name]) => (
); return (
onChange({ text: e.target.value })} placeholder="Text štítka (
= nový riadok)" className="flex-1 min-w-0 text-xs font-bold uppercase border border-gray-300 rounded-lg px-2 py-1" />
onChange({ x: v })} /> onChange({ y: v })} /> onChange({ size: v })} /> onChange({ rotate: v })} />
Pozadie {swatches('bg')}
Text {swatches('color')}
); }; // Š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 = ({ 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(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) => (
{label}
{unitKey && ( )}
); return (
{/* Hlavička produktu – klik rozbalí / zbalí */}
{onDelete && ( )}
{open && (
{/* TEXTY */}