feat: vlastna PNG cenovka pre vynimocny produkt (uloha 16)
- pri produkte aj vernostnom produkte sa da nahrat cela cenovka ako PNG, ktora prekryje generovanu sablonu - tlacidlo Odstranit PNG vrati generovanu cenovku (sablona ostava zachovana) - hromadne pouzitie cenovky (sekcia / cely newsletter) produkty s PNG neprepisuje Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
92dc2081d0
commit
22a9a51d00
3 files changed
+76
-27
No files matched your search
+3
-3
@@ -572,7 +572,8 @@ export default function Home() {
|
|||||||
...m,
|
...m,
|
||||||
data: {
|
data: {
|
||||||
...m.data,
|
...m.data,
|
||||||
products: (m.data.products || []).map((prod: any) => ({
|
// Produkty s vlastnou PNG cenovkou (výnimky) sa neprepisujú
|
||||||
|
products: (m.data.products || []).map((prod: any) => prod.customBadgePngUrl ? prod : ({
|
||||||
...prod,
|
...prod,
|
||||||
badgeId: badge.id,
|
badgeId: badge.id,
|
||||||
badgeCustomTemplate: badge,
|
badgeCustomTemplate: badge,
|
||||||
@@ -580,8 +581,7 @@ export default function Home() {
|
|||||||
badgeAlign: badge.align ?? prod.badgeAlign ?? 'center',
|
badgeAlign: badge.align ?? prod.badgeAlign ?? 'center',
|
||||||
badgePaddingTop: badge.paddingTop ?? prod.badgePaddingTop ?? 4,
|
badgePaddingTop: badge.paddingTop ?? prod.badgePaddingTop ?? 4,
|
||||||
badgePaddingBottom: badge.paddingBottom ?? prod.badgePaddingBottom ?? 4,
|
badgePaddingBottom: badge.paddingBottom ?? prod.badgePaddingBottom ?? 4,
|
||||||
badgeLayout: badgeLayout ?? prod.badgeLayout,
|
badgeLayout: badgeLayout ?? prod.badgeLayout
|
||||||
customBadgePngUrl: undefined
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,29 @@ interface ConfigPanelProps {
|
|||||||
availableBadges?: BadgeTemplate[];
|
availableBadges?: BadgeTemplate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nahratie celej cenovky ako PNG pre výnimočný produkt (prekryje šablónu)
|
||||||
|
const CustomBadgePngInput: React.FC<{ value?: string; onChange: (url: string) => void }> = ({ value, onChange }) => (
|
||||||
|
<div className="p-2.5 rounded-xl border border-dashed border-amber-300 bg-amber-50/60 space-y-1.5">
|
||||||
|
<ImageUploadInput
|
||||||
|
label="Vlastná PNG cenovka (výnimka)"
|
||||||
|
value={value || ''}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder="Nahrajte celú cenovku ako PNG"
|
||||||
|
/>
|
||||||
|
{value ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onChange('')}
|
||||||
|
className="w-full py-1.5 text-[11px] font-bold text-amber-800 bg-white hover:bg-amber-100 border border-amber-300 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Odstrániť PNG a vrátiť generovanú cenovku
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<p className="text-[10px] text-amber-800/80">Nahraná PNG nahradí generovanú cenovku len pri tomto produkte.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
||||||
module,
|
module,
|
||||||
onUpdateModule,
|
onUpdateModule,
|
||||||
@@ -731,6 +754,16 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Vlastná PNG cenovka (výnimka): prekryje šablónu, po odstránení sa vráti generovaná */}
|
||||||
|
<CustomBadgePngInput
|
||||||
|
value={p.customBadgePngUrl}
|
||||||
|
onChange={(url) => {
|
||||||
|
const products = [...module.data.products];
|
||||||
|
products[pIdx].customBadgePngUrl = url || undefined;
|
||||||
|
updateData({ products });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Tlačidlá aplikácie cenovky */}
|
{/* Tlačidlá aplikácie cenovky */}
|
||||||
{p.badgeCustomTemplate && (
|
{p.badgeCustomTemplate && (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -738,7 +771,8 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na všetky produkty v tejto sekcii?`)) {
|
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na všetky produkty v tejto sekcii?`)) {
|
||||||
const products = (module.data.products || []).map((prod: ProductItem) => ({
|
// Produkty s vlastnou PNG cenovkou (výnimky) sa neprepisujú
|
||||||
|
const products = (module.data.products || []).map((prod: ProductItem) => prod.customBadgePngUrl ? prod : ({
|
||||||
...prod,
|
...prod,
|
||||||
badgeCustomTemplate: p.badgeCustomTemplate,
|
badgeCustomTemplate: p.badgeCustomTemplate,
|
||||||
badgeId: p.badgeCustomTemplate?.id,
|
badgeId: p.badgeCustomTemplate?.id,
|
||||||
@@ -746,8 +780,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
badgeAlign: p.badgeAlign || p.badgeCustomTemplate?.align || 'center',
|
badgeAlign: p.badgeAlign || p.badgeCustomTemplate?.align || 'center',
|
||||||
badgePaddingTop: p.badgePaddingTop ?? p.badgeCustomTemplate?.paddingTop ?? 4,
|
badgePaddingTop: p.badgePaddingTop ?? p.badgeCustomTemplate?.paddingTop ?? 4,
|
||||||
badgePaddingBottom: p.badgePaddingBottom ?? p.badgeCustomTemplate?.paddingBottom ?? 4,
|
badgePaddingBottom: p.badgePaddingBottom ?? p.badgeCustomTemplate?.paddingBottom ?? 4,
|
||||||
badgeLayout: p.badgeLayout || 'below',
|
badgeLayout: p.badgeLayout || 'below'
|
||||||
customBadgePngUrl: undefined
|
|
||||||
}));
|
}));
|
||||||
updateData({ products });
|
updateData({ products });
|
||||||
}
|
}
|
||||||
@@ -1347,6 +1380,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
if (val === 'default_loyalty') {
|
if (val === 'default_loyalty') {
|
||||||
products[pIdx].badgeCustomTemplate = undefined;
|
products[pIdx].badgeCustomTemplate = undefined;
|
||||||
products[pIdx].badgeId = undefined;
|
products[pIdx].badgeId = undefined;
|
||||||
|
products[pIdx].customBadgePngUrl = undefined;
|
||||||
} else {
|
} else {
|
||||||
const found = (availableBadges || []).find((b) => b.id === val);
|
const found = (availableBadges || []).find((b) => b.id === val);
|
||||||
if (found) {
|
if (found) {
|
||||||
@@ -1356,6 +1390,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
products[pIdx].badgeAlign = found.align ?? products[pIdx].badgeAlign ?? 'center';
|
products[pIdx].badgeAlign = found.align ?? products[pIdx].badgeAlign ?? 'center';
|
||||||
products[pIdx].badgePaddingTop = found.paddingTop ?? products[pIdx].badgePaddingTop ?? 4;
|
products[pIdx].badgePaddingTop = found.paddingTop ?? products[pIdx].badgePaddingTop ?? 4;
|
||||||
products[pIdx].badgePaddingBottom = found.paddingBottom ?? products[pIdx].badgePaddingBottom ?? 4;
|
products[pIdx].badgePaddingBottom = found.paddingBottom ?? products[pIdx].badgePaddingBottom ?? 4;
|
||||||
|
products[pIdx].customBadgePngUrl = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateData({ products });
|
updateData({ products });
|
||||||
@@ -1371,6 +1406,16 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Vlastná PNG cenovka (výnimka): prekryje šablónu, po odstránení sa vráti generovaná */}
|
||||||
|
<CustomBadgePngInput
|
||||||
|
value={p.customBadgePngUrl}
|
||||||
|
onChange={(url) => {
|
||||||
|
const products = [...module.data.products];
|
||||||
|
products[pIdx].customBadgePngUrl = url || undefined;
|
||||||
|
updateData({ products });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Tlačidlá aplikácie cenovky */}
|
{/* Tlačidlá aplikácie cenovky */}
|
||||||
{p.badgeCustomTemplate && (
|
{p.badgeCustomTemplate && (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -1378,7 +1423,8 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na všetky vernostné produkty?`)) {
|
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na všetky vernostné produkty?`)) {
|
||||||
const products = (module.data.products || []).map((prod: any) => ({
|
// Produkty s vlastnou PNG cenovkou (výnimky) sa neprepisujú
|
||||||
|
const products = (module.data.products || []).map((prod: any) => prod.customBadgePngUrl ? prod : ({
|
||||||
...prod,
|
...prod,
|
||||||
badgeCustomTemplate: p.badgeCustomTemplate,
|
badgeCustomTemplate: p.badgeCustomTemplate,
|
||||||
badgeId: p.badgeCustomTemplate?.id,
|
badgeId: p.badgeCustomTemplate?.id,
|
||||||
@@ -1458,7 +1504,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Rýchle ovládanie veľkosti (Width), zarovnania a paddingu pre vernostnú cenovku */}
|
{/* Rýchle ovládanie veľkosti (Width), zarovnania a paddingu pre vernostnú cenovku */}
|
||||||
{p.badgeCustomTemplate && (
|
{(p.badgeCustomTemplate || p.customBadgePngUrl) && (
|
||||||
<div className="bg-gray-50 p-2.5 rounded-xl border border-gray-200 space-y-2">
|
<div className="bg-gray-50 p-2.5 rounded-xl border border-gray-200 space-y-2">
|
||||||
{/* Šírka */}
|
{/* Šírka */}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+22
-19
@@ -642,16 +642,7 @@ function renderExactProductCard(
|
|||||||
|
|
||||||
// Pomocná funkcia na render samotnej cenovky (bez obalového <td>)
|
// Pomocná funkcia na render samotnej cenovky (bez obalového <td>)
|
||||||
const renderBadgeContent = (bWidth: number, bAlign: string, padTop: number, padBottom: number, marginStyle: string) => {
|
const renderBadgeContent = (bWidth: number, bAlign: string, padTop: number, padBottom: number, marginStyle: string) => {
|
||||||
if (p.badgeCustomTemplate) {
|
// Vlastná PNG cenovka (výnimka) má prednosť pred šablónou; po jej odstránení sa vráti šablóna
|
||||||
return `
|
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
|
||||||
<tr>
|
|
||||||
<td align="${bAlign}">
|
|
||||||
${renderCleanBadgeSvg(p.badgeCustomTemplate, p, bWidth)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>`;
|
|
||||||
}
|
|
||||||
if (p.customBadgePngUrl) {
|
if (p.customBadgePngUrl) {
|
||||||
return `
|
return `
|
||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
||||||
@@ -662,6 +653,16 @@ function renderExactProductCard(
|
|||||||
</tr>
|
</tr>
|
||||||
</table>`;
|
</table>`;
|
||||||
}
|
}
|
||||||
|
if (p.badgeCustomTemplate) {
|
||||||
|
return `
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
||||||
|
<tr>
|
||||||
|
<td align="${bAlign}">
|
||||||
|
${renderCleanBadgeSvg(p.badgeCustomTemplate, p, bWidth)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>`;
|
||||||
|
}
|
||||||
const mainFontSize = Math.max(28, Math.round(bWidth * 0.54));
|
const mainFontSize = Math.max(28, Math.round(bWidth * 0.54));
|
||||||
const subFontSize = Math.max(14, Math.round(bWidth * 0.27));
|
const subFontSize = Math.max(14, Math.round(bWidth * 0.27));
|
||||||
return `
|
return `
|
||||||
@@ -809,14 +810,14 @@ function renderExactLoyaltyCard(p: any, validity: string): string {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr height="1">
|
<tr height="1">
|
||||||
<td style="padding: 0 8px ${p.badgeCustomTemplate ? '4px' : '12px'} 8px; text-align: center; height: 1px;" valign="bottom">
|
<td style="padding: 0 8px ${p.badgeCustomTemplate || p.customBadgePngUrl ? '4px' : '12px'} 8px; text-align: center; height: 1px;" valign="bottom">
|
||||||
${(() => {
|
${(() => {
|
||||||
// 1. Ak má vernostný produkt vlastnú dynamickú šablónu cenovky
|
// 1. Ak má vernostný produkt vlastnú PNG cenovku (prednosť) alebo dynamickú šablónu cenovky
|
||||||
if (p.badgeCustomTemplate) {
|
if (p.customBadgePngUrl || p.badgeCustomTemplate) {
|
||||||
const bWidth = p.badgeWidth || p.badgeCustomTemplate.width || 120;
|
const bWidth = p.badgeWidth || p.badgeCustomTemplate?.width || 120;
|
||||||
const bAlign = p.badgeAlign || p.badgeCustomTemplate.align || 'center';
|
const bAlign = p.badgeAlign || p.badgeCustomTemplate?.align || 'center';
|
||||||
const padTop = p.badgePaddingTop ?? (p.badgeCustomTemplate.paddingTop ?? 0);
|
const padTop = p.badgePaddingTop ?? (p.badgeCustomTemplate?.paddingTop ?? 0);
|
||||||
const padBottom = p.badgePaddingBottom ?? (p.badgeCustomTemplate.paddingBottom ?? 0);
|
const padBottom = p.badgePaddingBottom ?? (p.badgeCustomTemplate?.paddingBottom ?? 0);
|
||||||
|
|
||||||
let marginStyle = 'margin: 0 auto;';
|
let marginStyle = 'margin: 0 auto;';
|
||||||
if (bAlign === 'left') {
|
if (bAlign === 'left') {
|
||||||
@@ -836,7 +837,9 @@ function renderExactLoyaltyCard(p: any, validity: string): string {
|
|||||||
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="${bAlign}" style="${marginStyle} width: ${bWidth}px; padding-top: ${padTop}px; padding-bottom: ${padBottom}px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="${bAlign}">
|
<td align="${bAlign}">
|
||||||
${renderCleanBadgeSvg(p.badgeCustomTemplate, loyaltyProductData, bWidth)}
|
${p.customBadgePngUrl
|
||||||
|
? `<img src="${escapeHtml(p.customBadgePngUrl)}" width="${bWidth}" alt="${escapeHtml(pickAlt(p.title, 'Cenovka'))}" style="display: block; width: 100%; max-width: ${bWidth}px; margin: 0 auto;">`
|
||||||
|
: renderCleanBadgeSvg(p.badgeCustomTemplate, loyaltyProductData, bWidth)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>`;
|
</table>`;
|
||||||
@@ -900,7 +903,7 @@ function renderExactLoyaltyCard(p: any, validity: string): string {
|
|||||||
})()}
|
})()}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
${p.badgeCustomTemplate ? `
|
${p.badgeCustomTemplate || p.customBadgePngUrl ? `
|
||||||
<tr height="1">
|
<tr height="1">
|
||||||
<td align="center" valign="bottom" style="padding: 2px 8px 12px 8px; text-align: center; height: 1px;">
|
<td align="center" valign="bottom" style="padding: 2px 8px 12px 8px; text-align: center; height: 1px;">
|
||||||
${p.cardUnitPrice || p.unitPrice ? `<div style="font-size: 9px; color: #666666; font-weight: 500; font-family: Arial, sans-serif; text-align: center;">${escapeHtml(p.cardUnitPrice || p.unitPrice)}</div>` : '<div style="font-size: 9px; line-height: 9px; height: 9px;"> </div>'}
|
${p.cardUnitPrice || p.unitPrice ? `<div style="font-size: 9px; color: #666666; font-weight: 500; font-family: Arial, sans-serif; text-align: center;">${escapeHtml(p.cardUnitPrice || p.unitPrice)}</div>` : '<div style="font-size: 9px; line-height: 9px; height: 9px;"> </div>'}
|
||||||
|
|||||||
Reference in new issue
Block a user