diff --git a/src/lib/emailRenderer.ts b/src/lib/emailRenderer.ts
index ff9a02f..1c0cc47 100644
--- a/src/lib/emailRenderer.ts
+++ b/src/lib/emailRenderer.ts
@@ -347,6 +347,7 @@ function renderModule(m: any, campaign: NewsletterCampaign): string {
// Platnosť na kartách je predvolene skrytá (zobrazí sa v Lište platnosti akcie);
// produkty s vlastnou platnosťou (validityOverride) ju zobrazia vždy
const globalValidity = m.data.showCardValidity === true ? (m.data.globalValidity || '') : '';
+ const storesOffset: StoresOffset = { x: m.data.storesOffsetX, y: m.data.storesOffsetY };
const products: ProductItem[] = m.data.products || [];
const cardBg = m.data.cardBgColor || '#feeaea';
const cardBorder = m.data.cardBorderColor || '#f5d4d4';
@@ -369,7 +370,7 @@ function renderModule(m: any, campaign: NewsletterCampaign): string {
|
- ${renderExactProductCard(p, globalValidity, cardBg, cardBorder, badgeHdrBg, badgeHdrColor, badgePrcBg, badgePrcColor, badgeCustomTxt, m.data.sectionTitle, 600)}
+ ${renderExactProductCard(p, globalValidity, cardBg, cardBorder, badgeHdrBg, badgeHdrColor, badgePrcBg, badgePrcColor, badgeCustomTxt, m.data.sectionTitle, 600, storesOffset)}
|
`;
@@ -380,7 +381,7 @@ function renderModule(m: any, campaign: NewsletterCampaign): string {
${row.map((p, idx) => `
|
- ${renderExactProductCard(p, globalValidity, cardBg, cardBorder, badgeHdrBg, badgeHdrColor, badgePrcBg, badgePrcColor, badgeCustomTxt, m.data.sectionTitle)}
+ ${renderExactProductCard(p, globalValidity, cardBg, cardBorder, badgeHdrBg, badgeHdrColor, badgePrcBg, badgePrcColor, badgeCustomTxt, m.data.sectionTitle, 294, storesOffset)}
| `).join('\n')}
${row.length === 1 ? ' | ' : ''}
@@ -438,7 +439,7 @@ function renderModule(m: any, campaign: NewsletterCampaign): string {
${lProducts.map((p: any, idx: number) => `
|
- ${renderExactLoyaltyCard(p, m.data.showCardValidity === true ? (m.data.globalValidity || '2. 5. – 15. 5. 2026') : '')}
+ ${renderExactLoyaltyCard(p, m.data.showCardValidity === true ? (m.data.globalValidity || '2. 5. – 15. 5. 2026') : '', { x: m.data.storesOffsetX, y: m.data.storesOffsetY })}
| `).join('\n')}
@@ -712,6 +713,56 @@ function renderStackedImageRow(p: ProductItem, imgHtml: string, cellStyle: strin
`;
}
+// Štítky dostupnosti v predajniach (Potraviny / Supermarket / Tempo); prázdne, ak nie je zvolená žiadna
+function renderStoresHtml(p: any): string {
+ const tag = (color: string, file: string, alt: string) =>
+ `
|
`;
+ const rows = [
+ p.stores?.potraviny ? tag('#e2001a', 'stit_potraviny.png', 'Potraviny') : '',
+ p.stores?.supermarket ? tag('#f9890e', 'stit_supermarket.png', 'Supermarket') : '',
+ p.stores?.tempo ? tag('#005239', 'stit_tempo.png', 'Tempo') : ''
+ ].join('');
+ return rows ? `
` : '';
+}
+
+// Posun štítkov predajní voči hornej hrane cenovky (nastavenie sekcie)
+export interface StoresOffset {
+ x?: number; // + doprava, − doľava
+ y?: number; // + vyššie nad cenovku, − nižšie (prekryje horný okraj cenovky)
+}
+export const DEFAULT_STORES_OFFSET_Y = 2;
+
+// Štítky predajní "prilepené" k hornej hrane cenovky (zarovnané k jej pravému okraju),
+// takže sa posúvajú spolu s cenovkou; padTop = odsadenie celej cenovky zhora
+function attachStoresToBadge(
+ badgeHtml: string,
+ storesHtml: string,
+ bWidth: number,
+ bAlign: string,
+ marginStyle: string,
+ padTop: number,
+ offset: StoresOffset = {}
+): string {
+ if (!storesHtml) {
+ return `
${badgeHtml}
`;
+ }
+ const x = Math.round(offset.x || 0);
+ const y = Math.round(offset.y ?? DEFAULT_STORES_OFFSET_Y);
+ const cellPadding = `padding: ${padTop}px ${x < 0 ? -x : 0}px ${y > 0 ? y : 0}px 0;`;
+ const storesStyle = `margin-right: ${x > 0 ? -x : 0}px; margin-bottom: ${y < 0 ? y : 0}px; position: relative; z-index: 2;`;
+ return `
+
+
+ |
+ ${storesHtml}
+ |
+
+
+ | ${badgeHtml} |
+
+
`;
+}
+
function renderExactProductCard(
p: ProductItem,
globalValidity: string,
@@ -723,22 +774,22 @@ function renderExactProductCard(
badgePrcColor: string,
badgeCustomTxt: string | null,
sectionTitle?: string,
- cardWidth: number = 294 // nominálna šírka karty v px (2 stĺpce = 294, 1 stĺpec = 600)
+ cardWidth: number = 294, // nominálna šírka karty v px (2 stĺpce = 294, 1 stĺpec = 600)
+ storesOffset: StoresOffset = {}
): string {
const validity = p.validityOverride || globalValidity;
const effectiveCardBg = p.cardBgColor || cardBg || '#feeaea';
- const storesHtml = `
-
- ${p.stores?.potraviny ? ` |
` : ''}
- ${p.stores?.supermarket ? ` |
` : ''}
- ${p.stores?.tempo ? ` |
` : ''}
-
`;
+ const storesHtml = renderStoresHtml(p);
const badgeLayout = p.badgeLayout || 'below';
+ // Cenovka so štítkami predajní nad hornou hranou (bez obalového
)
+ const renderBadgeContent = (bWidth: number, bAlign: string, padTop: number, padBottom: number, marginStyle: string) =>
+ attachStoresToBadge(renderBadgeOnly(bWidth, bAlign, 0, padBottom, marginStyle), storesHtml, bWidth, bAlign, marginStyle, padTop, storesOffset);
+
// Pomocná funkcia na render samotnej cenovky (bez obalového | )
- const renderBadgeContent = (bWidth: number, bAlign: string, padTop: number, padBottom: number, marginStyle: string) => {
+ const renderBadgeOnly = (bWidth: number, bAlign: string, padTop: number, padBottom: number, marginStyle: string) => {
// Vlastná PNG cenovka (výnimka) má prednosť pred šablónou; po jej odstránení sa vráti šablóna
if (p.customBadgePngUrl) {
return `
@@ -804,7 +855,6 @@ function renderExactProductCard(
| ${escapeHtml(validity)} |
- ${storesHtml} |
|
@@ -855,7 +905,6 @@ function renderExactProductCard(
| ${escapeHtml(validity)} |
- ${storesHtml} |
@@ -898,14 +947,9 @@ function renderExactProductCard(
`;
}
-function renderExactLoyaltyCard(p: any, validity: string): string {
+function renderExactLoyaltyCard(p: any, validity: string, storesOffset: StoresOffset = {}): string {
const cardValidity = p.validityOverride || validity;
- const storesHtml = `
-
- ${p.stores?.potraviny ? ` |
` : ''}
- ${p.stores?.supermarket ? ` |
` : ''}
- ${p.stores?.tempo ? ` |
` : ''}
-
`;
+ const storesHtml = renderStoresHtml(p);
const loyaltyBg = p.cardBgColor || '#e6e1d9';
return `
@@ -915,7 +959,6 @@ function renderExactLoyaltyCard(p: any, validity: string): string {
| ${escapeHtml(cardValidity)} |
- ${storesHtml} |
@@ -967,8 +1010,8 @@ function renderExactLoyaltyCard(p: any, validity: string): string {
cardDiscount: p.cardDiscount
};
- return `
-
+ return attachStoresToBadge(`
+
|
${p.customBadgePngUrl
@@ -976,11 +1019,11 @@ function renderExactLoyaltyCard(p: any, validity: string): string {
: renderCleanBadgeSvg(p.badgeCustomTemplate, loyaltyProductData, bWidth)}
|
-
`;
+
`, storesHtml, bWidth, bAlign, marginStyle, padTop, storesOffset);
}
// 2. Predvolený Dual Sticker Block (Bez karty / S kartou) 1:1
- return `
+ return attachStoresToBadge(`
|
@@ -1033,7 +1076,7 @@ function renderExactLoyaltyCard(p: any, validity: string): string {
${escapeHtml(p.cardUnitPrice)}
|
-
`;
+ `, storesHtml, 140, 'center', 'margin: 0 auto;', 0, storesOffset);
})()}
diff --git a/src/types/newsletter.ts b/src/types/newsletter.ts
index dd04159..0f165f1 100644
--- a/src/types/newsletter.ts
+++ b/src/types/newsletter.ts
@@ -112,6 +112,8 @@ export interface ProductsGridModuleData {
cardBorderColor?: string;
columns?: 1 | 2;
showCardValidity?: boolean; // platnosť na kartách produktov (predvolene skrytá)
+ storesOffsetX?: number; // posun štítkov predajní voči cenovke (+ doprava)
+ storesOffsetY?: number; // posun štítkov predajní voči hornej hrane cenovky (+ vyššie)
}
export interface LoyaltyClubModuleData {