From 6d73660cac86127fd08df971b95667079725fe5c Mon Sep 17 00:00:00 2001 From: Jandl-hostacny Date: Wed, 21 Oct 2026 12:36:48 +0200 Subject: [PATCH] feat: velkost stitkov predajni nezavisla od velkosti cenovky - novy posuvnik Velkost stitkov pri produkte (predvolene 14 px) - Polohu a velkost stitkov pouzit na vsetky produkty prenesie aj velkost Co-Authored-By: Claude Opus 5.5 --- src/app/page.tsx | 6 +++--- src/components/editor/ConfigPanel.tsx | 2 +- src/components/editor/ProductEditor.tsx | 14 ++++++++------ src/lib/productCard.ts | 6 ++++-- src/types/newsletter.ts | 1 + 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 4a52c31..ec35df3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -607,13 +607,13 @@ export default function Home() { }; // Poloha štítkov predajní pre všetky produkty v celej kampani (všetky sekcie + vernostné zľavy) - const handleApplyStoresOffsetToAll = (x: number, y: number) => { - if (!confirm(`Použiť polohu štítkov (${x} / ${y}) pre všetky produkty v newsletteri?`)) return; + const handleApplyStoresOffsetToAll = (x: number, y: number, size: number) => { + if (!confirm(`Použiť polohu (${x} / ${y}) a veľkosť (${size} px) štítkov pre všetky produkty v newsletteri?`)) return; setCampaign((prev) => ({ ...prev, modules: prev.modules.map((m) => m.type === 'products_grid' || m.type === 'loyalty_club' - ? { ...m, data: { ...m.data, products: (m.data.products || []).map((p: ProductItem) => ({ ...p, storesOffsetX: x, storesOffsetY: y })) } } + ? { ...m, data: { ...m.data, products: (m.data.products || []).map((p: ProductItem) => ({ ...p, storesOffsetX: x, storesOffsetY: y, storesSize: size })) } } : m ) })); diff --git a/src/components/editor/ConfigPanel.tsx b/src/components/editor/ConfigPanel.tsx index c0aac60..9f9a9bc 100644 --- a/src/components/editor/ConfigPanel.tsx +++ b/src/components/editor/ConfigPanel.tsx @@ -15,7 +15,7 @@ interface ConfigPanelProps { module: NewsletterModule | null; onUpdateModule: (updated: NewsletterModule) => void; onApplyBadgeToAll?: (badge: BadgeTemplate, badgeLayout?: 'below' | 'side') => void; - onApplyStoresOffsetToAll?: (x: number, y: number) => void; + onApplyStoresOffsetToAll?: (x: number, y: number, size: number) => void; // Zmena nastavení spoločných pre všetky produktové sekcie a vernostné zľavy (platnosť akcie...) onUpdateAllProductSections?: (patch: Record) => void; campaignValidity?: string; // globálna platnosť akcie (pre Lištu platnosti) diff --git a/src/components/editor/ProductEditor.tsx b/src/components/editor/ProductEditor.tsx index 14257d4..31cfdc8 100644 --- a/src/components/editor/ProductEditor.tsx +++ b/src/components/editor/ProductEditor.tsx @@ -7,7 +7,7 @@ import { BadgeTemplate, CardProduct } from '@/types/newsletter'; import { ChevronDown, 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, badgeWidthFor, productCardAlt } from '@/lib/productCard'; +import { DEFAULT_STORES_OFFSET_X, DEFAULT_STORES_OFFSET_Y, DEFAULT_STORES_SIZE, badgeWidthFor, productCardAlt } from '@/lib/productCard'; export const PRODUCT_BG_PRESETS = [ { label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' }, @@ -38,7 +38,7 @@ interface ProductEditorProps { onOpenBadgeEditor?: () => void; onApplyBadgeToSection?: () => void; onApplyBadgeToAll?: () => void; - onApplyStoresOffsetToAll?: (x: number, y: number) => void; // poloha štítkov pre všetky produkty + 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'; @@ -135,6 +135,7 @@ export const ProductEditor: React.FC = ({ 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); @@ -326,8 +327,8 @@ export const ProductEditor: React.FC = ({ {/* DOSTUPNOSŤ */} onChange({ storesOffsetX: undefined, storesOffsetY: undefined })} /> + action={hasStores && (storesX !== DEFAULT_STORES_OFFSET_X || storesY !== DEFAULT_STORES_OFFSET_Y || storesSize !== DEFAULT_STORES_SIZE) && ( + onChange({ storesOffsetX: undefined, storesOffsetY: undefined, storesSize: undefined })} /> )} >
@@ -357,13 +358,14 @@ export const ProductEditor: React.FC = ({ <> + {onApplyStoresOffsetToAll && ( )} diff --git a/src/lib/productCard.ts b/src/lib/productCard.ts index 6974d11..834c027 100644 --- a/src/lib/productCard.ts +++ b/src/lib/productCard.ts @@ -31,6 +31,8 @@ export interface StoresOffset { } // Predvolená poloha štítkov voči pravému hornému rohu pozadia cenovky export const DEFAULT_STORES_OFFSET_X = -15; +// Predvolená výška jedného štítka predajne (px), nezávislá od veľkosti cenovky +export const DEFAULT_STORES_SIZE = 14; export const DEFAULT_STORES_OFFSET_Y = 5; // Výška vizuálu: pomer ako v šablóne (294 × 245), pri 1 produkte na riadok max 300 px @@ -205,7 +207,7 @@ export function cardVisualKey(p: CardProduct, ctx: CardContext): string { p.priceMain, p.priceSub, p.discountText, p.badgeText, p.unitPrice, 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.stores, p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X, p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y, p.storesSize ?? DEFAULT_STORES_SIZE ]; const str = JSON.stringify(relevant); let h1 = 5381; @@ -267,7 +269,7 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str const oy = p.imageOffsetY || 0; const sx = Math.round(p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X); const sy = Math.round(p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y); - const stores = renderStores(p, ctx.mode, Math.max(10, Math.round(bw * 0.12))); + const stores = renderStores(p, ctx.mode, p.storesSize ?? DEFAULT_STORES_SIZE); // Pravý horný roh viditeľného pozadia cenovky (pri PNG a predvolenej cenovke = roh cenovky) const bgBox = p.badgeCustomTemplate && !p.customBadgePngUrl ? badgeBackgroundBox(p.badgeCustomTemplate) : null; const insetTop = bgBox ? Math.round(bgBox.top * bw * bgBox.aspect) : 0; diff --git a/src/types/newsletter.ts b/src/types/newsletter.ts index f099142..f69783b 100644 --- a/src/types/newsletter.ts +++ b/src/types/newsletter.ts @@ -49,6 +49,7 @@ export interface ProductItem { imageAlt?: string; // Ručný alt text fotky (inak názov produktu) storesOffsetX?: number; // posun štítkov predajní voči cenovke (+ doprava) storesOffsetY?: number; // posun štítkov predajní voči hornej hrane cenovky (+ vyššie) + storesSize?: number; // výška jedného štítka predajne v px (nezávislá od veľkosti cenovky) imageScale?: number; // Zoom fotky (1 = 100 %) imageOffsetX?: number; // Posun fotky vodorovne v px (+ doprava) imageOffsetY?: number; // Posun fotky zvisle v px (+ nadol)