feat: vernostne zlavy - pri neparnom pocte je prvy produkt cez cely riadok
Parny pocet: vsetky produkty po 2 v riadku. Neparny: prvy produkt sam cez celu sirku (vacsi vizual), ostatne po 2. Export obrazkov kariet pouziva rovnaku sirku karty ako nahlad. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b13a447c64
commit
23f23c8903
3 files changed
+38
-15
No files matched your search
@@ -3,7 +3,7 @@
|
|||||||
// a jeho adresa sa zapamätá pri produkte (cardImage). Pri ďalšom exporte sa nezmenené karty nenahrávajú znova.
|
// a jeho adresa sa zapamätá pri produkte (cardImage). Pri ďalšom exporte sa nezmenené karty nenahrávajú znova.
|
||||||
import { toJpeg } from 'html-to-image';
|
import { toJpeg } from 'html-to-image';
|
||||||
import { CardProduct, NewsletterCampaign } from '@/types/newsletter';
|
import { CardProduct, NewsletterCampaign } from '@/types/newsletter';
|
||||||
import { cardContextForModule, cardVisualKey, cardVisualHeight } from './productCard';
|
import { cardContextFor, cardVisualKey, cardVisualHeight } from './productCard';
|
||||||
|
|
||||||
async function uploadJpeg(dataUrl: string, fileName: string): Promise<string> {
|
async function uploadJpeg(dataUrl: string, fileName: string): Promise<string> {
|
||||||
const blob = await (await fetch(dataUrl)).blob();
|
const blob = await (await fetch(dataUrl)).blob();
|
||||||
@@ -26,8 +26,8 @@ export async function prepareCardImages(
|
|||||||
campaign.modules.forEach((m, mIdx) => {
|
campaign.modules.forEach((m, mIdx) => {
|
||||||
const isCards = m.type === 'products_grid' || (m.type === 'loyalty_club' && m.data?.enabled !== false);
|
const isCards = m.type === 'products_grid' || (m.type === 'loyalty_club' && m.data?.enabled !== false);
|
||||||
if (!isCards) return;
|
if (!isCards) return;
|
||||||
const ctx = cardContextForModule(m, 'preview');
|
|
||||||
(m.data.products || []).forEach((p: CardProduct, pIdx: number) => {
|
(m.data.products || []).forEach((p: CardProduct, pIdx: number) => {
|
||||||
|
const ctx = cardContextFor(m, pIdx, 'preview');
|
||||||
const key = cardVisualKey(p, ctx);
|
const key = cardVisualKey(p, ctx);
|
||||||
if (p.cardImage?.key !== key) {
|
if (p.cardImage?.key !== key) {
|
||||||
jobs.push({ mIdx, pIdx, key, bg: p.cardBgColor || ctx.cardBg, width: ctx.cardWidth });
|
jobs.push({ mIdx, pIdx, key, bg: p.cardBgColor || ctx.cardBg, width: ctx.cardWidth });
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { NewsletterCampaign } from '@/types/newsletter';
|
|||||||
import { SUPABASE_ASSETS_BASE } from './defaultCampaign';
|
import { SUPABASE_ASSETS_BASE } from './defaultCampaign';
|
||||||
import { pickAlt } from './altText';
|
import { pickAlt } from './altText';
|
||||||
import { escapeHtml, escapeWithBreaks } from './html';
|
import { escapeHtml, escapeWithBreaks } from './html';
|
||||||
import { RenderMode, cardContextForModule, renderProductGrid } from './productCard';
|
import { RenderMode, renderProductGrid } from './productCard';
|
||||||
|
|
||||||
// E-mail na export (ZIP/Brevo): produktové karty použijú obrázky pripravené cez prepareCardImages()
|
// E-mail na export (ZIP/Brevo): produktové karty použijú obrázky pripravené cez prepareCardImages()
|
||||||
export function renderEmailHtml(campaign: NewsletterCampaign): string {
|
export function renderEmailHtml(campaign: NewsletterCampaign): string {
|
||||||
@@ -392,7 +392,7 @@ function renderModule(m: any, campaign: NewsletterCampaign, mode: RenderMode = '
|
|||||||
<h2 class="force-text-white" style="margin: 0; font-size: 24px; color: #fefefe; font-weight: 900; text-transform: uppercase; text-shadow: 0px 2px 5px rgba(0,0,0,0.5);">${escapeWithBreaks(m.data.sectionTitle)}</h2>
|
<h2 class="force-text-white" style="margin: 0; font-size: 24px; color: #fefefe; font-weight: 900; text-transform: uppercase; text-shadow: 0px 2px 5px rgba(0,0,0,0.5);">${escapeWithBreaks(m.data.sectionTitle)}</h2>
|
||||||
</td>
|
</td>
|
||||||
</tr>` : ''}
|
</tr>` : ''}
|
||||||
${renderProductGrid(m.data.products || [], cardContextForModule(m, mode))}
|
${renderProductGrid(m, mode)}
|
||||||
</table>
|
</table>
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><tr><td height="12" style="font-size: 12px; line-height: 12px;"> </td></tr></table>`;
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><tr><td height="12" style="font-size: 12px; line-height: 12px;"> </td></tr></table>`;
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ function renderModule(m: any, campaign: NewsletterCampaign, mode: RenderMode = '
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- Produktové karty -->
|
<!-- Produktové karty -->
|
||||||
${renderProductGrid(m.data.products || [], cardContextForModule(m, mode))}
|
${renderProductGrid(m, mode)}
|
||||||
</table>
|
</table>
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><tr><td height="25" style="font-size: 25px; line-height: 25px;"> </td></tr></table>`;
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><tr><td height="25" style="font-size: 25px; line-height: 25px;"> </td></tr></table>`;
|
||||||
|
|
||||||
|
|||||||
+33
-10
@@ -372,23 +372,46 @@ export function renderProductCard(p: CardProduct, ctx: CardContext): string {
|
|||||||
</table>`;
|
</table>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mriežka kariet (1 alebo 2 na riadok) s rovnakou výškou kariet v riadku
|
// Kontext konkrétnej karty: vo vernostných zľavách pri nepárnom počte je prvý produkt cez celý riadok
|
||||||
export function renderProductGrid(products: CardProduct[], ctx: CardContext): string {
|
export function cardContextFor(m: NewsletterModule, index: number, mode: RenderMode): CardContext {
|
||||||
const cols = ctx.cardWidth > 300 ? 1 : 2;
|
const base = cardContextForModule(m, mode);
|
||||||
const rows: CardProduct[][] = [];
|
const count = (m.data.products || []).length;
|
||||||
for (let i = 0; i < products.length; i += cols) rows.push(products.slice(i, i + cols));
|
if (base.variant === 'loyalty' && count % 2 === 1 && index === 0) return { ...base, cardWidth: 600 };
|
||||||
return rows
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Riadky mriežky (indexy produktov): 1 na riadok, alebo po 2;
|
||||||
|
// vernostné zľavy s nepárnym počtom: prvý produkt sám cez celý riadok, ostatné po 2
|
||||||
|
function gridRows(m: NewsletterModule, count: number, oneColumn: boolean): number[][] {
|
||||||
|
const idx = Array.from({ length: count }, (_, i) => i);
|
||||||
|
if (oneColumn) return idx.map((i) => [i]);
|
||||||
|
const rows: number[][] = [];
|
||||||
|
let start = 0;
|
||||||
|
if (m.type === 'loyalty_club' && count % 2 === 1) {
|
||||||
|
rows.push([0]);
|
||||||
|
start = 1;
|
||||||
|
}
|
||||||
|
for (let i = start; i < count; i += 2) rows.push(idx.slice(i, i + 2));
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mriežka kariet s rovnakou výškou kariet v riadku
|
||||||
|
export function renderProductGrid(m: NewsletterModule, mode: RenderMode): string {
|
||||||
|
const products: CardProduct[] = m.data.products || [];
|
||||||
|
const oneColumn = cardContextForModule(m, mode).cardWidth > 300;
|
||||||
|
return gridRows(m, products.length, oneColumn)
|
||||||
.map((row, rIdx) => {
|
.map((row, rIdx) => {
|
||||||
const spacer = rIdx > 0 ? '<tr><td height="12" style="font-size: 12px; line-height: 12px;"> </td></tr>' : '';
|
const spacer = rIdx > 0 ? '<tr><td height="12" style="font-size: 12px; line-height: 12px;"> </td></tr>' : '';
|
||||||
const cells = row
|
const cells = row
|
||||||
.map(
|
.map(
|
||||||
(p, idx) => `
|
(i, idx) => `
|
||||||
<td width="${cols === 1 ? 100 : 50}%" valign="top" height="1" style="${cols === 1 ? '' : idx === 0 ? 'padding-right: 6px;' : 'padding-left: 6px;'} height: 100%;">
|
<td width="${row.length === 1 ? 100 : 50}%" valign="top" height="1" style="${row.length === 1 ? '' : idx === 0 ? 'padding-right: 6px;' : 'padding-left: 6px;'} height: 100%;">
|
||||||
${renderProductCard(p, ctx)}
|
${renderProductCard(products[i], cardContextFor(m, i, mode))}
|
||||||
</td>`
|
</td>`
|
||||||
)
|
)
|
||||||
.join('');
|
.join('');
|
||||||
const filler = cols === 2 && row.length === 1 ? '<td width="50%" style="padding-left: 6px;"></td>' : '';
|
// posledný riadok s 1 produktom v 2-stĺpcovej mriežke (bežné sekcie) – produkt ostane v ľavej polovici
|
||||||
|
const filler = !oneColumn && row.length === 1 && cardContextFor(m, row[0], mode).cardWidth <= 300 ? '<td width="50%" style="padding-left: 6px;"></td>' : '';
|
||||||
return `${spacer}
|
return `${spacer}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Reference in new issue
Block a user