feat: premenne {cena_s_kartou} a {zlava_s_kartou} v cenovkach (uloha 12)

- bezne produkty maju volitelne polia Cena s kartou / Zlava s kartou,
  vernostne produkty pouzivaju svoje existujuce hodnoty
- editor cenoviek: tlacidla na pridanie vrstiev a nahlad premennych
- jedinecne ID novych vrstiev (rychle pridanie viacerych vrstiev)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-19 10:30:32 +02:00
1 parent 22a9a51d00
commit 3bd93fde20
6 files changed
+138 -43

No files matched your search

+50 -28
View File
@@ -3,7 +3,7 @@
import React, { useState, useRef, useEffect } from 'react';
import { BadgeTemplate, BadgeLayer } from '@/types/newsletter';
import { supabase } from '@/lib/supabase';
import { DEFAULT_BADGE_TEMPLATES, renderCleanBadgeSvg, svgToCleanPng } from '@/lib/badgeUtils';
import { DEFAULT_BADGE_TEMPLATES, renderCleanBadgeSvg, svgToCleanPng, BADGE_PRICE_TOKEN_RE, badgePriceParts, replaceBadgeTextVars } from '@/lib/badgeUtils';
import { Tag, Plus, Check, Trash2, X, Upload, Eye, Move, AlignLeft, AlignCenter, AlignRight, ZoomIn, ZoomOut, Copy } from 'lucide-react';
// Riadok tabuľky badges v Supabase pre danú šablónu
@@ -34,6 +34,9 @@ interface BadgeEditorModalProps {
currentPriceMain?: string;
currentPriceSub?: string;
currentUnitPrice?: string;
currentCardPriceMain?: string;
currentCardPriceSub?: string;
currentCardDiscount?: string;
}
export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
@@ -43,7 +46,10 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
currentDiscount = '25%',
currentPriceMain = '1',
currentPriceSub = '99',
currentUnitPrice = '1kg = 4,91 €'
currentUnitPrice = '1kg = 4,91 €',
currentCardPriceMain = '1',
currentCardPriceSub = '49',
currentCardDiscount = '30%'
}) => {
const POPULAR_FONTS = [
{ name: 'Poppins', source: 'google' as const },
@@ -391,7 +397,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
const addTextLayer = (presetText: string = 'Text') => {
if (!activeBadge) return;
const newL: BadgeLayer = {
id: `l-${Date.now()}`,
id: `l-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
type: 'text',
text: presetText,
x: 70,
@@ -538,6 +544,17 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
};
// Vykreslenie vrstvy v React náhľade s centami ako horný index (sup) a BEZ znaku €
// Ceny produktu, pre ktorý sa cenovka upravuje (náhľad premenných)
const previewProduct = {
priceMain: currentPriceMain,
priceSub: currentPriceSub,
discountText: currentDiscount,
unitPrice: currentUnitPrice,
cardPriceMain: currentCardPriceMain,
cardPriceSub: currentCardPriceSub,
cardDiscount: currentCardDiscount
};
const renderLayerVisualContent = (layer: BadgeLayer) => {
const raw = layer.text || '';
const baseFontSize = layer.fontSize || 16;
@@ -545,12 +562,14 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
const subY = layer.indexOffsetY !== undefined ? `${layer.indexOffsetY}px` : '-10px';
const subX = layer.indexOffsetX !== undefined ? `${layer.indexOffsetX}px` : '2px';
if (raw.includes('{cena}')) {
const parts = raw.split('{cena}');
const priceMatch = raw.match(BADGE_PRICE_TOKEN_RE);
if (priceMatch) {
const [tokMain, tokSub] = badgePriceParts(priceMatch[1], previewProduct);
const idx = priceMatch.index || 0;
return (
<span>
{parts[0]}
<span>{currentPriceMain}</span>
{replaceBadgeTextVars(raw.slice(0, idx), previewProduct)}
<span>{tokMain}</span>
<span
style={{
display: 'inline-block',
@@ -560,9 +579,9 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
lineHeight: 1
}}
>
{currentPriceSub}
{tokSub}
</span>
{parts[1]}
{replaceBadgeTextVars(raw.slice(idx + priceMatch[0].length), previewProduct)}
</span>
);
}
@@ -588,13 +607,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
);
}
const replaced = raw
.replace(/{cela_suma}/g, currentPriceMain)
.replace(/{zlava}/g, currentDiscount.replace('-', ''))
.replace(/{jednotkova_cena}/g, currentUnitPrice)
.replace(/{mena}/g, '€');
return replaced;
return replaceBadgeTextVars(raw, previewProduct);
};
// Aplikovanie cenovky na produkt: čisté vektorové SVG a čistý PNG (BEZ akéhokoľvek screenshotu nástrojov/rámčekov)
@@ -604,12 +617,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
try {
const cleanSvg = renderCleanBadgeSvg(
activeBadge,
{
priceMain: currentPriceMain,
priceSub: currentPriceSub,
discountText: currentDiscount,
unitPrice: currentUnitPrice
},
previewProduct,
activeBadge.width || 110
);
@@ -709,12 +717,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
dangerouslySetInnerHTML={{
__html: renderCleanBadgeSvg(
b,
{
priceMain: currentPriceMain,
priceSub: currentPriceSub,
discountText: currentDiscount,
unitPrice: currentUnitPrice
},
previewProduct,
90
)
}}
@@ -876,6 +879,13 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
<span className="bg-slate-800 px-2 py-0.5 rounded font-mono text-emerald-300">
{'{zlava}'} &rarr; {currentDiscount.replace('-', '')}
</span>
<span className="bg-slate-800 px-2 py-0.5 rounded font-mono text-amber-300">
{'{cena_s_kartou}'} &rarr; {currentCardPriceMain}
<sup>{currentCardPriceSub}</sup>
</span>
<span className="bg-slate-800 px-2 py-0.5 rounded font-mono text-emerald-300">
{'{zlava_s_kartou}'} &rarr; {currentCardDiscount.replace('-', '')}
</span>
<span className="bg-slate-800 px-2 py-0.5 rounded font-mono text-blue-300">
{'{mena}'} &rarr; €
</span>
@@ -1032,6 +1042,18 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
>
+ Zľava %
</button>
<button
onClick={() => addTextLayer('{cena_s_kartou}')}
className="text-[11px] font-bold bg-amber-50 text-amber-800 border border-amber-200 hover:bg-amber-100 px-2 py-1 rounded-lg"
>
+ Cena s kartou
</button>
<button
onClick={() => addTextLayer('-{zlava_s_kartou}')}
className="text-[11px] font-bold bg-red-50 text-red-800 border border-red-200 hover:bg-red-100 px-2 py-1 rounded-lg"
>
+ Zľava s kartou
</button>
<button
onClick={() => addTextLayer('€')}
className="text-[11px] font-bold bg-blue-50 text-blue-800 border border-blue-200 hover:bg-blue-100 px-2 py-1 rounded-lg"
+46
View File
@@ -656,6 +656,52 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
</div>
</div>
{/* Cena s kartou (voliteľné) – premenné {cena_s_kartou} a {zlava_s_kartou} v cenovke */}
<div className="grid grid-cols-3 gap-2 p-2 rounded-lg bg-amber-50/60 border border-amber-100">
<div>
<label className="text-[10px] text-gray-500">S kartou (€)</label>
<input
type="text"
value={p.cardPriceMain || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardPriceMain = e.target.value;
updateData({ products });
}}
placeholder="—"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[10px] text-gray-500">Centy</label>
<input
type="text"
value={p.cardPriceSub || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardPriceSub = e.target.value;
updateData({ products });
}}
placeholder="—"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white font-bold"
/>
</div>
<div>
<label className="text-[10px] text-gray-500">Zľava s kartou</label>
<input
type="text"
value={p.cardDiscount || ''}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].cardDiscount = e.target.value;
updateData({ products });
}}
placeholder="—"
className="w-full text-xs border border-gray-300 rounded p-1 bg-white"
/>
</div>
</div>
{/* Predajne */}
<div>
<label className="text-[10px] text-gray-500 block mb-1">Dostupnosť (Piktogramy)</label>