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 <noreply@anthropic.com>
This commit is contained in:
1 parent
8e4ad03af9
commit
6d73660cac
5 files changed
+17
-12
No files matched your search
+3
-3
@@ -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)
|
// Poloha štítkov predajní pre všetky produkty v celej kampani (všetky sekcie + vernostné zľavy)
|
||||||
const handleApplyStoresOffsetToAll = (x: number, y: number) => {
|
const handleApplyStoresOffsetToAll = (x: number, y: number, size: number) => {
|
||||||
if (!confirm(`Použiť polohu štítkov (${x} / ${y}) pre všetky produkty v newsletteri?`)) return;
|
if (!confirm(`Použiť polohu (${x} / ${y}) a veľkosť (${size} px) štítkov pre všetky produkty v newsletteri?`)) return;
|
||||||
setCampaign((prev) => ({
|
setCampaign((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
modules: prev.modules.map((m) =>
|
modules: prev.modules.map((m) =>
|
||||||
m.type === 'products_grid' || m.type === 'loyalty_club'
|
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
|
: m
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface ConfigPanelProps {
|
|||||||
module: NewsletterModule | null;
|
module: NewsletterModule | null;
|
||||||
onUpdateModule: (updated: NewsletterModule) => void;
|
onUpdateModule: (updated: NewsletterModule) => void;
|
||||||
onApplyBadgeToAll?: (badge: BadgeTemplate, badgeLayout?: 'below' | 'side') => 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...)
|
// Zmena nastavení spoločných pre všetky produktové sekcie a vernostné zľavy (platnosť akcie...)
|
||||||
onUpdateAllProductSections?: (patch: Record<string, unknown>) => void;
|
onUpdateAllProductSections?: (patch: Record<string, unknown>) => void;
|
||||||
campaignValidity?: string; // globálna platnosť akcie (pre Lištu platnosti)
|
campaignValidity?: string; // globálna platnosť akcie (pre Lištu platnosti)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { BadgeTemplate, CardProduct } from '@/types/newsletter';
|
|||||||
import { ChevronDown, Trash2, Pencil, Globe, Layers } from 'lucide-react';
|
import { ChevronDown, Trash2, Pencil, Globe, Layers } from 'lucide-react';
|
||||||
import { ImageUploadInput } from './ImageUploadInput';
|
import { ImageUploadInput } from './ImageUploadInput';
|
||||||
import { pickAlt } from '@/lib/altText';
|
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 = [
|
export const PRODUCT_BG_PRESETS = [
|
||||||
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
|
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
|
||||||
@@ -38,7 +38,7 @@ interface ProductEditorProps {
|
|||||||
onOpenBadgeEditor?: () => void;
|
onOpenBadgeEditor?: () => void;
|
||||||
onApplyBadgeToSection?: () => void;
|
onApplyBadgeToSection?: () => void;
|
||||||
onApplyBadgeToAll?: () => 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';
|
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<ProductEditorProps> = ({
|
|||||||
const offsetY = p.imageOffsetY || 0;
|
const offsetY = p.imageOffsetY || 0;
|
||||||
const storesX = p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X;
|
const storesX = p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X;
|
||||||
const storesY = p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y;
|
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 hasStores = !!(p.stores?.potraviny || p.stores?.supermarket || p.stores?.tempo);
|
||||||
const badgeWidth = badgeWidthFor(p, variant);
|
const badgeWidth = badgeWidthFor(p, variant);
|
||||||
|
|
||||||
@@ -326,8 +327,8 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
|||||||
{/* DOSTUPNOSŤ */}
|
{/* DOSTUPNOSŤ */}
|
||||||
<Block
|
<Block
|
||||||
title="Dostupnosť"
|
title="Dostupnosť"
|
||||||
action={hasStores && (storesX !== DEFAULT_STORES_OFFSET_X || storesY !== DEFAULT_STORES_OFFSET_Y) && (
|
action={hasStores && (storesX !== DEFAULT_STORES_OFFSET_X || storesY !== DEFAULT_STORES_OFFSET_Y || storesSize !== DEFAULT_STORES_SIZE) && (
|
||||||
<ResetLink onClick={() => onChange({ storesOffsetX: undefined, storesOffsetY: undefined })} />
|
<ResetLink onClick={() => onChange({ storesOffsetX: undefined, storesOffsetY: undefined, storesSize: undefined })} />
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-3 gap-1.5">
|
<div className="grid grid-cols-3 gap-1.5">
|
||||||
@@ -357,13 +358,14 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
|||||||
<>
|
<>
|
||||||
<Slider label="Štítky ↔" value={storesX} display={`${storesX}`} min={-80} max={80} step={1} onChange={set('storesOffsetX')} />
|
<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="Š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 && (
|
{onApplyStoresOffsetToAll && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onApplyStoresOffsetToAll(storesX, storesY)}
|
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"
|
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 štítkov použiť na všetky produkty
|
<Globe className="w-3 h-3" /> Polohu a veľkosť štítkov použiť na všetky produkty
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export interface StoresOffset {
|
|||||||
}
|
}
|
||||||
// Predvolená poloha štítkov voči pravému hornému rohu pozadia cenovky
|
// Predvolená poloha štítkov voči pravému hornému rohu pozadia cenovky
|
||||||
export const DEFAULT_STORES_OFFSET_X = -15;
|
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;
|
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
|
// 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.priceMain, p.priceSub, p.discountText, p.badgeText, p.unitPrice,
|
||||||
p.cardPriceMain, p.cardPriceSub, p.cardDiscount, p.cardUnitPrice,
|
p.cardPriceMain, p.cardPriceSub, p.cardDiscount, p.cardUnitPrice,
|
||||||
p.noCardPriceMain, p.noCardPriceSub, p.noCardDiscount, p.noCardUnitPrice,
|
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);
|
const str = JSON.stringify(relevant);
|
||||||
let h1 = 5381;
|
let h1 = 5381;
|
||||||
@@ -267,7 +269,7 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str
|
|||||||
const oy = p.imageOffsetY || 0;
|
const oy = p.imageOffsetY || 0;
|
||||||
const sx = Math.round(p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X);
|
const sx = Math.round(p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X);
|
||||||
const sy = Math.round(p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y);
|
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)
|
// 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 bgBox = p.badgeCustomTemplate && !p.customBadgePngUrl ? badgeBackgroundBox(p.badgeCustomTemplate) : null;
|
||||||
const insetTop = bgBox ? Math.round(bgBox.top * bw * bgBox.aspect) : 0;
|
const insetTop = bgBox ? Math.round(bgBox.top * bw * bgBox.aspect) : 0;
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export interface ProductItem {
|
|||||||
imageAlt?: string; // Ručný alt text fotky (inak názov produktu)
|
imageAlt?: string; // Ručný alt text fotky (inak názov produktu)
|
||||||
storesOffsetX?: number; // posun štítkov predajní voči cenovke (+ doprava)
|
storesOffsetX?: number; // posun štítkov predajní voči cenovke (+ doprava)
|
||||||
storesOffsetY?: number; // posun štítkov predajní voči hornej hrane cenovky (+ vyššie)
|
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 %)
|
imageScale?: number; // Zoom fotky (1 = 100 %)
|
||||||
imageOffsetX?: number; // Posun fotky vodorovne v px (+ doprava)
|
imageOffsetX?: number; // Posun fotky vodorovne v px (+ doprava)
|
||||||
imageOffsetY?: number; // Posun fotky zvisle v px (+ nadol)
|
imageOffsetY?: number; // Posun fotky zvisle v px (+ nadol)
|
||||||
|
|||||||
Reference in new issue
Block a user