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:
1 parent
22a9a51d00
commit
3bd93fde20
6 files changed
+138
-43
No files matched your search
@@ -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}'} → {currentDiscount.replace('-', '')}
|
||||
</span>
|
||||
<span className="bg-slate-800 px-2 py-0.5 rounded font-mono text-amber-300">
|
||||
{'{cena_s_kartou}'} → {currentCardPriceMain}
|
||||
<sup>{currentCardPriceSub}</sup>
|
||||
</span>
|
||||
<span className="bg-slate-800 px-2 py-0.5 rounded font-mono text-emerald-300">
|
||||
{'{zlava_s_kartou}'} → {currentCardDiscount.replace('-', '')}
|
||||
</span>
|
||||
<span className="bg-slate-800 px-2 py-0.5 rounded font-mono text-blue-300">
|
||||
{'{mena}'} → €
|
||||
</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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user