diff --git a/src/components/editor/ProductEditor.tsx b/src/components/editor/ProductEditor.tsx index 6abc51c..c8432bf 100644 --- a/src/components/editor/ProductEditor.tsx +++ b/src/components/editor/ProductEditor.tsx @@ -4,10 +4,10 @@ // 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, Trash2, Pencil, Globe, Layers } from 'lucide-react'; +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, badgeWidthFor, productCardAlt } from '@/lib/productCard'; +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' }, @@ -105,7 +105,49 @@ const ResetLink: React.FC<{ onClick: () => void }> = ({ onClick }) => ( ); -// Ďalšia fotka v karte (bundle): obrázok, poloha, veľkosť, pred/za hlavnou fotkou +// 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; @@ -123,10 +165,6 @@ const ExtraPhotoEditor: React.FC<{ onChange({ x: v })} /> onChange({ y: v })} /> onChange({ scale: v })} /> - ); @@ -355,23 +393,29 @@ export const ProductEditor: React.FC = ({ 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) })} + onRemove={() => + onChange({ + extraPhotos: (p.extraPhotos || []).filter((_, j) => j !== i), + photoOrder: photoStack(p).filter((id) => id !== f.id) + }) + } /> ))} + + {(p.extraPhotos || []).length > 0 && onChange({ photoOrder })} />} {/* CENOVKA */} diff --git a/src/lib/productCard.ts b/src/lib/productCard.ts index b6923c3..00044e8 100644 --- a/src/lib/productCard.ts +++ b/src/lib/productCard.ts @@ -208,7 +208,7 @@ export function cardVisualKey(p: CardProduct, ctx: CardContext): string { p.cardPriceMain, p.cardPriceSub, p.cardDiscount, p.cardUnitPrice, p.noCardPriceMain, p.noCardPriceSub, p.noCardDiscount, p.noCardUnitPrice, p.stores, p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X, p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y, p.storesSize ?? DEFAULT_STORES_SIZE, - p.labels || [], p.extraPhotos || [] + p.labels || [], p.extraPhotos || [], photoStack(p) ]; const str = JSON.stringify(relevant); let h1 = 5381; @@ -221,6 +221,20 @@ export function cardVisualKey(p: CardProduct, ctx: CardContext): string { return `cv-${(h1 >>> 0).toString(36)}${(h2 >>> 0).toString(36)}`; } +// Poradie fotiek v karte odzadu dopredu ('main' = hlavná fotka). Uložené poradie sa doplní o nové fotky +// (dopredu) a očistí o zmazané; bez uloženého poradia platí staršie nastavenie „za hlavnou fotkou“. +export const MAIN_PHOTO = 'main'; +export function photoStack(p: Pick): string[] { + const extras = p.extraPhotos || []; + const ids = new Set([MAIN_PHOTO, ...extras.map((f) => f.id)]); + if (p.photoOrder?.length) { + const order = p.photoOrder.filter((id) => ids.has(id)); + for (const id of ids) if (!order.includes(id)) order.push(id); + return order; + } + return [...extras.filter((f) => f.behind).map((f) => f.id), MAIN_PHOTO, ...extras.filter((f) => !f.behind).map((f) => f.id)]; +} + // Ďalšia fotka v karte (bundle): poloha v % vizuálu, veľkosť voči hlavnej fotke function renderExtraPhoto(f: ProductPhoto, mode: RenderMode): string { if (!f.url) return ''; @@ -290,9 +304,15 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str const insetRight = bgBox ? Math.round(bgBox.right * bw) : 0; return `
- ${(p.extraPhotos || []).filter((f) => f.behind).map((f) => renderExtraPhoto(f, ctx.mode)).join('')} - ${p.imageUrl ? `` : ''} - ${(p.extraPhotos || []).filter((f) => !f.behind).map((f) => renderExtraPhoto(f, ctx.mode)).join('')} + ${photoStack(p) + .map((id) => { + if (id === MAIN_PHOTO) { + return p.imageUrl ? `` : ''; + } + const f = (p.extraPhotos || []).find((x) => x.id === id); + return f ? renderExtraPhoto(f, ctx.mode) : ''; + }) + .join('')} ${(p.labels || []).map(renderLabel).join('')}
${stores ? `
${stores}
` : ''} diff --git a/src/types/newsletter.ts b/src/types/newsletter.ts index 6450206..7e63122 100644 --- a/src/types/newsletter.ts +++ b/src/types/newsletter.ts @@ -52,6 +52,7 @@ export interface ProductItem { storesSize?: number; // výška jedného štítka predajne v px (nezávislá od veľkosti cenovky) labels?: ProductLabel[]; // vlastné štítky na fotke (napr. „PODIEL MÄSA 92 %“) extraPhotos?: ProductPhoto[]; // ďalšie fotky v karte (bundle) popri hlavnej fotke + photoOrder?: string[]; // poradie fotiek odzadu dopredu: id ďalších fotiek a 'main' (hlavná fotka) imageScale?: number; // Zoom fotky (1 = 100 %) imageOffsetX?: number; // Posun fotky vodorovne v px (+ doprava) imageOffsetY?: number; // Posun fotky zvisle v px (+ nadol) @@ -76,7 +77,7 @@ export interface ProductPhoto { x: number; // stred fotky, % šírky vizuálu (0–100) y: number; // stred fotky, % výšky vizuálu (0–100) scale: number; // 1 = rovnaká veľkosť ako hlavná fotka bez zoomu - behind?: boolean; // za hlavnou fotkou (inak pred ňou) + behind?: boolean; // (staršie) za hlavnou fotkou – nahradené poradím photoOrder } // Vlastný štítok na fotke produktu