feat: predvolene rozlozenie v sablone cenovky (poloha, velkost, stitky predajni)

- sablona moze mat layout (posun cenovky, posun a velkost stitkov); produkt
  bez vlastneho nastavenia ho preberie
- tlacidlo v produkte ulozi aktualne rozlozenie ako predvolene pre vsetky
  sablony rovnakeho druhu a produkty v newsletteri ho prevezmu
- Zakladna: cenovka -14 / -14, stitky -4 / +8, 14 px

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-22 15:22:57 +02:00
1 parent 944233f196
commit b67db3cd49
7 files changed
+111 -16

No files matched your search

+51
View File
@@ -15,6 +15,7 @@ import { SavedCampaignsModal } from '@/components/editor/SavedCampaignsModal';
import { NameDialog } from '@/components/editor/NameDialog'; import { NameDialog } from '@/components/editor/NameDialog';
import JSZip from 'jszip'; import JSZip from 'jszip';
import { prepareCardImages } from '@/lib/cardImages'; import { prepareCardImages } from '@/lib/cardImages';
import { cardLayout } from '@/lib/productCard';
import { normalizeUnitPrice } from '@/lib/unitPrice'; import { normalizeUnitPrice } from '@/lib/unitPrice';
import { useCampaignReview } from '@/components/review/useCampaignReview'; import { useCampaignReview } from '@/components/review/useCampaignReview';
import { ReviewModal } from '@/components/review/ReviewModal'; import { ReviewModal } from '@/components/review/ReviewModal';
@@ -404,6 +405,7 @@ export default function Home() {
const inherited: Partial<ProductItem> = {}; const inherited: Partial<ProductItem> = {};
for (const k of layoutKeys) { for (const k of layoutKeys) {
if (k === 'cardBgColor' && target.type === 'loyalty_club') continue; // vernostné zľavy: farba pozadia ostáva if (k === 'cardBgColor' && target.type === 'loyalty_club') continue; // vernostné zľavy: farba pozadia ostáva
if (p.badgeCustomTemplate?.layout && k !== 'cardBgColor') continue; // šablóna má vlastné predvolené rozloženie
if (p[k] === undefined && first[k] !== undefined) Object.assign(inherited, { [k]: first[k] }); if (p[k] === undefined && first[k] !== undefined) Object.assign(inherited, { [k]: first[k] });
} }
return { ...inherited, ...p }; return { ...inherited, ...p };
@@ -648,6 +650,54 @@ 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)
// Rozloženie produktu (poloha a veľkosť cenovky, poloha a veľkosť štítkov) ako predvolené pre všetky šablóny
// rovnakého druhu (napr. všetky „Základná“); produkty s týmito šablónami v newsletteri ho prevezmú
const handleSaveLayoutAsDefault = async (p: CardProduct) => {
const t = p.badgeCustomTemplate;
if (!t?.importTags?.type) return;
const kind = (b: BadgeTemplate) =>
b.importTags?.type === t.importTags?.type && !!b.importTags?.app === !!t.importTags?.app && !!b.importTags?.loyalty === !!t.importTags?.loyalty;
const group = availableBadges.filter(kind);
const L = cardLayout(p);
const layout = { offsetX: L.offsetX, offsetY: L.offsetY, storesOffsetX: L.storesX, storesOffsetY: L.storesY, storesSize: L.storesSize };
const width = p.badgeWidth || t.width;
const label = t.name.split(' | ')[0];
if (!confirm(`Uložiť polohu cenovky (${layout.offsetX} / ${layout.offsetY}), veľkosť (${width} px) a štítky (${layout.storesOffsetX} / ${layout.storesOffsetY}, ${layout.storesSize} px) ako predvolené pre ${group.length} šablón „${label}“?\n\nProdukty s týmito cenovkami v newsletteri ho prevezmú.`)) return;
try {
for (const b of group) {
const { data, error } = await supabase.from('badges').select('layers_config').eq('id', b.id).single();
if (error) throw error;
const cfg = data.layers_config && !Array.isArray(data.layers_config) ? data.layers_config : { layers: data.layers_config || [] };
const res = await supabase.from('badges').update({ layers_config: { ...cfg, layout, width } }).eq('id', b.id);
if (res.error) throw res.error;
}
} catch (err) {
alert(`Predvolené rozloženie sa nepodarilo uložiť: ${(err as Error).message}`);
return;
}
const updated = new Map(group.map((b) => [b.id, { ...b, layout, width }]));
setAvailableBadges((prev) => prev.map((b) => updated.get(b.id) || b));
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((pp: ProductItem) => {
const u = pp.badgeCustomTemplate && !pp.customBadgePngUrl ? updated.get(pp.badgeCustomTemplate.id) : undefined;
return u
? { ...pp, badgeCustomTemplate: u, badgeWidth: undefined, badgeOffsetX: undefined, badgeOffsetY: undefined, storesOffsetX: undefined, storesOffsetY: undefined, storesSize: undefined }
: pp;
})
}
}
: m
)
}));
};
const handleApplyStoresOffsetToAll = (x: number, y: number, size: number) => { 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; 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) => ({
@@ -1007,6 +1057,7 @@ export default function Home() {
onUpdateModule={handleUpdateModule} onUpdateModule={handleUpdateModule}
onApplyBadgeToAll={handleApplyBadgeToAll} onApplyBadgeToAll={handleApplyBadgeToAll}
onApplyStoresOffsetToAll={handleApplyStoresOffsetToAll} onApplyStoresOffsetToAll={handleApplyStoresOffsetToAll}
onSaveLayoutAsDefault={handleSaveLayoutAsDefault}
onUpdateAllProductSections={handleUpdateAllProductSections} onUpdateAllProductSections={handleUpdateAllProductSections}
campaignValidity={getCampaignValidity(campaign)} campaignValidity={getCampaignValidity(campaign)}
availableBadges={availableBadges} availableBadges={availableBadges}
+2 -1
View File
@@ -50,7 +50,8 @@ function toBadgeDbPayload(badge: BadgeTemplate) {
paddingTop: badge.paddingTop ?? 4, paddingTop: badge.paddingTop ?? 4,
paddingBottom: badge.paddingBottom ?? 4, paddingBottom: badge.paddingBottom ?? 4,
importTags: badge.importTags, importTags: badge.importTags,
isDefault: badge.isDefault || undefined isDefault: badge.isDefault || undefined,
layout: badge.layout
} }
}; };
} }
+4
View File
@@ -16,6 +16,7 @@ interface ConfigPanelProps {
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, size: number) => void; onApplyStoresOffsetToAll?: (x: number, y: number, size: number) => void;
onSaveLayoutAsDefault?: (p: CardProduct) => 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)
@@ -95,6 +96,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
onUpdateModule, onUpdateModule,
onApplyBadgeToAll, onApplyBadgeToAll,
onApplyStoresOffsetToAll, onApplyStoresOffsetToAll,
onSaveLayoutAsDefault,
onUpdateAllProductSections, onUpdateAllProductSections,
campaignValidity = '', campaignValidity = '',
onOpenBadgeManager, onOpenBadgeManager,
@@ -703,6 +705,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
onApplyBadgeToSection={() => applyBadgeToSection(p)} onApplyBadgeToSection={() => applyBadgeToSection(p)}
onApplyBadgeToAll={onApplyBadgeToAll ? () => applyBadgeToAll(p, 'standard') : undefined} onApplyBadgeToAll={onApplyBadgeToAll ? () => applyBadgeToAll(p, 'standard') : undefined}
onApplyStoresOffsetToAll={onApplyStoresOffsetToAll} onApplyStoresOffsetToAll={onApplyStoresOffsetToAll}
onSaveLayoutAsDefault={onSaveLayoutAsDefault}
/> />
))} ))}
</div> </div>
@@ -928,6 +931,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
onApplyBadgeToSection={() => applyBadgeToSection(p)} onApplyBadgeToSection={() => applyBadgeToSection(p)}
onApplyBadgeToAll={onApplyBadgeToAll ? () => applyBadgeToAll(p, 'loyalty') : undefined} onApplyBadgeToAll={onApplyBadgeToAll ? () => applyBadgeToAll(p, 'loyalty') : undefined}
onApplyStoresOffsetToAll={onApplyStoresOffsetToAll} onApplyStoresOffsetToAll={onApplyStoresOffsetToAll}
onSaveLayoutAsDefault={onSaveLayoutAsDefault}
/> />
))} ))}
</div> </div>
+21 -7
View File
@@ -7,7 +7,7 @@ import { BADGE_IMPORT_TYPES, BadgeTemplate, CardProduct, ProductLabel, ProductPh
import { ChevronDown, ChevronUp, Trash2, Pencil, Globe, Layers } from 'lucide-react'; import { ChevronDown, ChevronUp, 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_IMAGE_SCALE, DEFAULT_STORES_OFFSET_X, DEFAULT_STORES_OFFSET_Y, DEFAULT_STORES_SIZE, MAIN_PHOTO, PRODUCT_BG_PRESETS, badgeWidthFor, photoStack, productCardAlt, usesAppPrice } from '@/lib/productCard'; import { DEFAULT_IMAGE_SCALE, DEFAULT_STORES_OFFSET_X, DEFAULT_STORES_OFFSET_Y, DEFAULT_STORES_SIZE, MAIN_PHOTO, PRODUCT_BG_PRESETS, badgeWidthFor, cardLayout, photoStack, productCardAlt, usesAppPrice } from '@/lib/productCard';
export { PRODUCT_BG_PRESETS }; export { PRODUCT_BG_PRESETS };
@@ -33,6 +33,7 @@ interface ProductEditorProps {
onApplyBadgeToSection?: () => void; onApplyBadgeToSection?: () => void;
onApplyBadgeToAll?: () => void; onApplyBadgeToAll?: () => void;
onApplyStoresOffsetToAll?: (x: number, y: number, size: number) => void; // poloha a veľkosť štítkov pre všetky produkty onApplyStoresOffsetToAll?: (x: number, y: number, size: number) => void; // poloha a veľkosť štítkov pre všetky produkty
onSaveLayoutAsDefault?: (p: CardProduct) => void; // rozloženie produktu ako predvolené pre šablóny rovnakého druhu
} }
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';
@@ -321,7 +322,8 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
onOpenBadgeEditor, onOpenBadgeEditor,
onApplyBadgeToSection, onApplyBadgeToSection,
onApplyBadgeToAll, onApplyBadgeToAll,
onApplyStoresOffsetToAll onApplyStoresOffsetToAll,
onSaveLayoutAsDefault
}) => { }) => {
// Po otvorení (aj klikom na produkt v náhľade) posunieme panel na tento produkt // Po otvorení (aj klikom na produkt v náhľade) posunieme panel na tento produkt
const rootRef = useRef<HTMLDivElement>(null); const rootRef = useRef<HTMLDivElement>(null);
@@ -347,9 +349,10 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
const scale = p.imageScale ?? DEFAULT_IMAGE_SCALE; const scale = p.imageScale ?? DEFAULT_IMAGE_SCALE;
const offsetX = p.imageOffsetX || 0; const offsetX = p.imageOffsetX || 0;
const offsetY = p.imageOffsetY || 0; const offsetY = p.imageOffsetY || 0;
const storesX = p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X; const lay = cardLayout(p);
const storesY = p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y; const storesX = lay.storesX;
const storesSize = p.storesSize ?? DEFAULT_STORES_SIZE; const storesY = lay.storesY;
const storesSize = lay.storesSize;
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);
@@ -544,8 +547,8 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
</div> </div>
<Slider label="Veľkosť" value={badgeWidth} display={`${badgeWidth} px`} min={60} max={200} step={1} onChange={set('badgeWidth')} /> <Slider label="Veľkosť" value={badgeWidth} display={`${badgeWidth} px`} min={60} max={200} step={1} onChange={set('badgeWidth')} />
<Slider label="Vodorovne" value={p.badgeOffsetX || 0} display={`${p.badgeOffsetX || 0}`} min={-150} max={8} step={1} onChange={set('badgeOffsetX')} /> <Slider label="Vodorovne" value={lay.offsetX} display={`${lay.offsetX}`} min={-150} max={8} step={1} onChange={set('badgeOffsetX')} />
<Slider label="Zvisle" value={p.badgeOffsetY || 0} display={`${p.badgeOffsetY || 0}`} min={-150} max={8} step={1} onChange={set('badgeOffsetY')} /> <Slider label="Zvisle" value={lay.offsetY} display={`${lay.offsetY}`} min={-150} max={8} step={1} onChange={set('badgeOffsetY')} />
{isLoyalty ? ( {isLoyalty ? (
<> <>
@@ -582,6 +585,17 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
)} )}
</div> </div>
)} )}
{p.badgeCustomTemplate?.importTags?.type && onSaveLayoutAsDefault && (
<button
type="button"
onClick={() => onSaveLayoutAsDefault(p)}
className="w-full text-[10px] font-bold text-gray-700 bg-white border border-dashed border-gray-300 hover:border-gray-400 hover:bg-gray-50 rounded-lg py-1.5"
title="Poloha cenovky, jej veľkosť a poloha a veľkosť štítkov predajní sa uložia do všetkých šablón tohto druhu"
>
Uložiť polohu a veľkosť ako predvolené pre všetky „{p.badgeCustomTemplate.name.split(' | ')[0]}“
</button>
)}
</Block> </Block>
{/* DOSTUPNOSŤ */} {/* DOSTUPNOSŤ */}
+4 -2
View File
@@ -1,4 +1,4 @@
import { BadgeTemplate, BadgeLayer, BadgeImportTags } from '@/types/newsletter'; import { BadgeTemplate, BadgeLayer, BadgeImportTags, BadgeLayout } from '@/types/newsletter';
// Vymazané všetky preddefinované šablóny — v systéme sú výhradne vlastné cenovky vytvorené používateľom // Vymazané všetky preddefinované šablóny — v systéme sú výhradne vlastné cenovky vytvorené používateľom
export const DEFAULT_BADGE_TEMPLATES: BadgeTemplate[] = []; export const DEFAULT_BADGE_TEMPLATES: BadgeTemplate[] = [];
@@ -21,6 +21,7 @@ export interface BadgeRow {
paddingBottom?: number; paddingBottom?: number;
importTags?: BadgeImportTags; importTags?: BadgeImportTags;
isDefault?: boolean; isDefault?: boolean;
layout?: BadgeLayout;
} }
| null; | null;
} }
@@ -38,7 +39,8 @@ export function badgeFromRow(d: BadgeRow): BadgeTemplate {
paddingTop: cfg?.paddingTop ?? 4, paddingTop: cfg?.paddingTop ?? 4,
paddingBottom: cfg?.paddingBottom ?? 4, paddingBottom: cfg?.paddingBottom ?? 4,
importTags: cfg?.importTags, importTags: cfg?.importTags,
isDefault: cfg?.isDefault || undefined isDefault: cfg?.isDefault || undefined,
layout: cfg?.layout
}; };
} }
+19 -6
View File
@@ -103,6 +103,18 @@ function badgeData(p: CardProduct, variant: CardVariant): BadgeProductData {
return p; return p;
} }
// Poloha cenovky a štítkov predajní: vlastné nastavenie produktu, inak predvolené zo šablóny, inak pevné
export function cardLayout(p: CardProduct) {
const l = p.customBadgePngUrl ? undefined : p.badgeCustomTemplate?.layout;
return {
offsetX: p.badgeOffsetX ?? l?.offsetX ?? 0,
offsetY: p.badgeOffsetY ?? l?.offsetY ?? 0,
storesX: p.storesOffsetX ?? l?.storesOffsetX ?? DEFAULT_STORES_OFFSET_X,
storesY: p.storesOffsetY ?? l?.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y,
storesSize: p.storesSize ?? l?.storesSize ?? DEFAULT_STORES_SIZE
};
}
export function badgeWidthFor(p: CardProduct, variant: CardVariant): number { export function badgeWidthFor(p: CardProduct, variant: CardVariant): number {
if (p.badgeWidth) return p.badgeWidth; if (p.badgeWidth) return p.badgeWidth;
if (p.badgeCustomTemplate) return p.badgeCustomTemplate.width || 110; if (p.badgeCustomTemplate) return p.badgeCustomTemplate.width || 110;
@@ -225,12 +237,12 @@ export function cardVisualKey(p: CardProduct, ctx: CardContext): string {
'v2', // verzia vykreslenia – zvýšiť, keď sa zmení spôsob prípravy obrázkov (v2: písma z Adobe Fonts) 'v2', // verzia vykreslenia – zvýšiť, keď sa zmení spôsob prípravy obrázkov (v2: písma z Adobe Fonts)
ctx.variant, ctx.cardWidth, p.cardBgColor || ctx.cardBg, ctx.variant === 'standard' ? ctx.defaultBadge : null, ctx.variant, ctx.cardWidth, p.cardBgColor || ctx.cardBg, ctx.variant === 'standard' ? ctx.defaultBadge : null,
p.imageUrl, p.imageScale ?? DEFAULT_IMAGE_SCALE, p.imageOffsetX || 0, p.imageOffsetY || 0, p.imageUrl, p.imageScale ?? DEFAULT_IMAGE_SCALE, p.imageOffsetX || 0, p.imageOffsetY || 0,
p.customBadgePngUrl, badgeWidthFor(p, ctx.variant), p.badgeOffsetX || 0, p.badgeOffsetY || 0, p.customBadgePngUrl, badgeWidthFor(p, ctx.variant), cardLayout(p),
t ? [t.svgRaw, t.layersConfig, t.width] : null, t ? [t.svgRaw, t.layersConfig, t.width] : null,
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.appPriceMain, p.appPriceSub, p.appDiscount, p.appUnitPrice, p.cardPriceMain, p.cardPriceSub, p.cardDiscount, p.cardUnitPrice, p.appPriceMain, p.appPriceSub, p.appDiscount, p.appUnitPrice,
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.storesSize ?? DEFAULT_STORES_SIZE, p.stores,
p.labels || [], p.extraPhotos || [], photoStack(p) p.labels || [], p.extraPhotos || [], photoStack(p)
]; ];
const str = JSON.stringify(relevant); const str = JSON.stringify(relevant);
@@ -336,9 +348,10 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str
const scale = p.imageScale ?? DEFAULT_IMAGE_SCALE; const scale = p.imageScale ?? DEFAULT_IMAGE_SCALE;
const ox = p.imageOffsetX || 0; const ox = p.imageOffsetX || 0;
const oy = p.imageOffsetY || 0; const oy = p.imageOffsetY || 0;
const sx = Math.round(p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X); const lay = cardLayout(p);
const sy = Math.round(p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y); const sx = Math.round(lay.storesX);
const stores = renderStores(p, ctx.mode, p.storesSize ?? DEFAULT_STORES_SIZE, bw); const sy = Math.round(lay.storesY);
const stores = renderStores(p, ctx.mode, lay.storesSize, bw);
// 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;
@@ -355,7 +368,7 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str
}) })
.join('')} .join('')}
${(p.labels || []).map(renderLabel).join('')} ${(p.labels || []).map(renderLabel).join('')}
<div style="position: absolute; right: ${8 - (p.badgeOffsetX || 0)}px; bottom: ${8 - (p.badgeOffsetY || 0)}px; width: ${bw}px;"> <div style="position: absolute; right: ${8 - lay.offsetX}px; bottom: ${8 - lay.offsetY}px; width: ${bw}px;">
${stores ? `<div style="position: absolute; right: ${insetRight - sx}px; bottom: calc(100% - ${insetTop}px + ${sy}px);">${stores}</div>` : ''} ${stores ? `<div style="position: absolute; right: ${insetRight - sx}px; bottom: calc(100% - ${insetTop}px + ${sy}px);">${stores}</div>` : ''}
${renderBadge(p, ctx, bw)} ${renderBadge(p, ctx, bw)}
</div> </div>
+10
View File
@@ -252,6 +252,16 @@ export interface BadgeTemplate {
paddingBottom?: number; paddingBottom?: number;
importTags?: BadgeImportTags; // podľa čoho sa šablóna vyberie pri importe podkladov (ZIP) importTags?: BadgeImportTags; // podľa čoho sa šablóna vyberie pri importe podkladov (ZIP)
isDefault?: boolean; // predvolená cenovka produktov – nedá sa zmazať isDefault?: boolean; // predvolená cenovka produktov – nedá sa zmazať
layout?: BadgeLayout; // predvolené rozloženie pre produkty s touto cenovkou (ak ho produkt nemá vlastné)
}
// Predvolená poloha cenovky a štítkov predajní pre šablónu (napr. všetky „Základná“)
export interface BadgeLayout {
offsetX?: number; // posun cenovky vodorovne, + doprava
offsetY?: number; // posun cenovky zvisle, + nadol
storesOffsetX?: number; // štítky predajní ↔
storesOffsetY?: number; // štítky predajní ↕
storesSize?: number; // výška štítka predajne v px
} }
// Druhy cenoviek v podkladoch: „Zľava grafická“, a keď je prázdna, základná cenovka s výkričníkom alebo bez // Druhy cenoviek v podkladoch: „Zľava grafická“, a keď je prázdna, základná cenovka s výkričníkom alebo bez