fix: jednotkova cena v tvare 'jednotkova cena 7,75 EUR/kg' aj v existujucich produktoch
- normalizeUnitPrice prevedie stare zapisy '1kg = 7,75 EUR' pri nacitani newslettera, v CSV importe a v predvolenych produktoch - jednotkova cena pod nazvom sa neopakuje, ked ju ma priamo cenovka (vernostna karta, appkaci) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9635b89e7c
commit
d778ed345c
6 files changed
+54
-23
No files matched your search
+13
-4
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { NewsletterCampaign, NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter';
|
import { NewsletterCampaign, NewsletterModule, ProductItem, BadgeTemplate, CardProduct } from '@/types/newsletter';
|
||||||
import { DEFAULT_CAMPAIGN, SUPABASE_ASSETS_BASE, ensureValidityBar } from '@/lib/defaultCampaign';
|
import { DEFAULT_CAMPAIGN, SUPABASE_ASSETS_BASE, ensureValidityBar } from '@/lib/defaultCampaign';
|
||||||
import { renderEmailHtml, renderSingleModuleHtml, getCampaignValidity } from '@/lib/emailRenderer';
|
import { renderEmailHtml, renderSingleModuleHtml, getCampaignValidity } from '@/lib/emailRenderer';
|
||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
@@ -15,6 +15,7 @@ import { SavedCampaignsModal } from '@/components/editor/SavedCampaignsModal';
|
|||||||
import { NameDialog } from '@/components/editor/NameDialog';
|
import { NameDialog } from '@/components/editor/NameDialog';
|
||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
import { prepareCardImages } from '@/lib/cardImages';
|
import { prepareCardImages } from '@/lib/cardImages';
|
||||||
|
import { normalizeUnitPrice } from '@/lib/unitPrice';
|
||||||
import { useCampaignReview } from '@/components/review/useCampaignReview';
|
import { useCampaignReview } from '@/components/review/useCampaignReview';
|
||||||
import { ReviewModal } from '@/components/review/ReviewModal';
|
import { ReviewModal } from '@/components/review/ReviewModal';
|
||||||
import { AnnotationLayer, useModuleLayout } from '@/components/review/AnnotationLayer';
|
import { AnnotationLayer, useModuleLayout } from '@/components/review/AnnotationLayer';
|
||||||
@@ -88,7 +89,15 @@ export default function Home() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!availableBadges.length) return;
|
if (!availableBadges.length) return;
|
||||||
const byId = new Map(availableBadges.map((b) => [b.id, b]));
|
const byId = new Map(availableBadges.map((b) => [b.id, b]));
|
||||||
const update = (p: ProductItem, def: BadgeTemplate | undefined): ProductItem => {
|
const UNIT_KEYS = ['unitPrice', 'cardUnitPrice', 'noCardUnitPrice', 'appUnitPrice'] as const;
|
||||||
|
const update = (input: ProductItem, def: BadgeTemplate | undefined): ProductItem => {
|
||||||
|
// jednotkové ceny v jednotnom tvare „jednotková cena 7,75 EUR/kg“ (staršie produkty mali „1kg = 7,75 €“)
|
||||||
|
let p = input;
|
||||||
|
for (const k of UNIT_KEYS) {
|
||||||
|
const cur = (p as CardProduct)[k];
|
||||||
|
const next = cur ? normalizeUnitPrice(cur) : cur;
|
||||||
|
if (next !== cur) p = { ...p, [k]: next };
|
||||||
|
}
|
||||||
if (p.customBadgePngUrl) return p;
|
if (p.customBadgePngUrl) return p;
|
||||||
if (!p.badgeCustomTemplate) return def ? { ...p, badgeCustomTemplate: def, badgeId: def.id, badgeWidth: def.width } : p;
|
if (!p.badgeCustomTemplate) return def ? { ...p, badgeCustomTemplate: def, badgeId: def.id, badgeWidth: def.width } : p;
|
||||||
const fresh = byId.get(p.badgeCustomTemplate.id);
|
const fresh = byId.get(p.badgeCustomTemplate.id);
|
||||||
@@ -346,7 +355,7 @@ export default function Home() {
|
|||||||
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_5.png`,
|
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_5.png`,
|
||||||
priceMain: '2',
|
priceMain: '2',
|
||||||
priceSub: '49',
|
priceSub: '49',
|
||||||
unitPrice: '1kg = 9,90 €',
|
unitPrice: 'jednotková cena 9,90 EUR/kg',
|
||||||
discountText: '15%',
|
discountText: '15%',
|
||||||
badgeText: 'zľava',
|
badgeText: 'zľava',
|
||||||
cardBgColor: '#feeaea',
|
cardBgColor: '#feeaea',
|
||||||
@@ -1050,7 +1059,7 @@ export default function Home() {
|
|||||||
currentDiscount={targetProduct?.discountText || targetProduct?.cardDiscount || '25%'}
|
currentDiscount={targetProduct?.discountText || targetProduct?.cardDiscount || '25%'}
|
||||||
currentPriceMain={targetProduct?.priceMain || targetProduct?.cardPriceMain || '1'}
|
currentPriceMain={targetProduct?.priceMain || targetProduct?.cardPriceMain || '1'}
|
||||||
currentPriceSub={targetProduct?.priceSub || targetProduct?.cardPriceSub || '99'}
|
currentPriceSub={targetProduct?.priceSub || targetProduct?.cardPriceSub || '99'}
|
||||||
currentUnitPrice={targetProduct?.unitPrice || targetProduct?.cardUnitPrice || '1kg = 4,91 €'}
|
currentUnitPrice={targetProduct?.unitPrice || targetProduct?.cardUnitPrice || 'jednotková cena 4,91 EUR/kg'}
|
||||||
currentCardPriceMain={targetProduct?.cardPriceMain || undefined}
|
currentCardPriceMain={targetProduct?.cardPriceMain || undefined}
|
||||||
currentCardPriceSub={targetProduct?.cardPriceSub || undefined}
|
currentCardPriceSub={targetProduct?.cardPriceSub || undefined}
|
||||||
currentCardDiscount={targetProduct?.cardDiscount || undefined}
|
currentCardDiscount={targetProduct?.cardDiscount || undefined}
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
currentDiscount = '25%',
|
currentDiscount = '25%',
|
||||||
currentPriceMain = '1',
|
currentPriceMain = '1',
|
||||||
currentPriceSub = '99',
|
currentPriceSub = '99',
|
||||||
currentUnitPrice = '1kg = 4,91 €',
|
currentUnitPrice = 'jednotková cena 4,91 EUR/kg',
|
||||||
currentCardPriceMain = '1',
|
currentCardPriceMain = '1',
|
||||||
currentCardPriceSub = '49',
|
currentCardPriceSub = '49',
|
||||||
currentCardDiscount = '30%',
|
currentCardDiscount = '30%',
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Upload, Check, AlertCircle, FileText, X } from 'lucide-react';
|
|||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
import { PodkladyProduct, pickLoyaltyBadge, podkladyToLoyaltyItem, podkladyToProductItem, readPodkladyZip, splitPrice } from '@/lib/podkladyZip';
|
import { PodkladyProduct, pickLoyaltyBadge, podkladyToLoyaltyItem, podkladyToProductItem, readPodkladyZip, splitPrice } from '@/lib/podkladyZip';
|
||||||
import { CARD_BG_BY_BADGE_COLOR } from '@/lib/productCard';
|
import { CARD_BG_BY_BADGE_COLOR } from '@/lib/productCard';
|
||||||
|
import { normalizeUnitPrice } from '@/lib/unitPrice';
|
||||||
import { shrinkImage, uploadFile } from '@/lib/upload';
|
import { shrinkImage, uploadFile } from '@/lib/upload';
|
||||||
|
|
||||||
interface ImportModalProps {
|
interface ImportModalProps {
|
||||||
@@ -222,7 +223,7 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
|||||||
imageUrl: row[mapping.imageUrl] || 'https://dqexxfspkylqttrtxsww.supabase.co/storage/v1/object/public/newsletter-assets/produkt_1.png',
|
imageUrl: row[mapping.imageUrl] || 'https://dqexxfspkylqttrtxsww.supabase.co/storage/v1/object/public/newsletter-assets/produkt_1.png',
|
||||||
priceMain: pMain,
|
priceMain: pMain,
|
||||||
priceSub: pSub,
|
priceSub: pSub,
|
||||||
unitPrice: row[mapping.unitPrice] || '',
|
unitPrice: normalizeUnitPrice(String(row[mapping.unitPrice] || '')),
|
||||||
discountText: row[mapping.discountText] ? `${row[mapping.discountText]}%` : '',
|
discountText: row[mapping.discountText] ? `${row[mapping.discountText]}%` : '',
|
||||||
badgeText: 'zľava',
|
badgeText: 'zľava',
|
||||||
cardBgColor: '#feeaea',
|
cardBgColor: '#feeaea',
|
||||||
@@ -416,7 +417,7 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold text-gray-600 mb-1">Merná cena (napr. 1kg = 4,91 €)</label>
|
<label className="block text-xs font-semibold text-gray-600 mb-1">Jednotková cena (napr. 4,91 EUR/kg)</label>
|
||||||
<select
|
<select
|
||||||
value={mapping.unitPrice}
|
value={mapping.unitPrice}
|
||||||
onChange={(e) => setMapping({ ...mapping, unitPrice: e.target.value })}
|
onChange={(e) => setMapping({ ...mapping, unitPrice: e.target.value })}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
|
|||||||
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_1.png`,
|
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_1.png`,
|
||||||
priceMain: '1',
|
priceMain: '1',
|
||||||
priceSub: '99',
|
priceSub: '99',
|
||||||
unitPrice: '1kg = 4,91 €',
|
unitPrice: 'jednotková cena 4,91 EUR/kg',
|
||||||
discountText: '23%',
|
discountText: '23%',
|
||||||
badgeText: 'zľava',
|
badgeText: 'zľava',
|
||||||
cardBgColor: '#feeaea',
|
cardBgColor: '#feeaea',
|
||||||
@@ -121,7 +121,7 @@ export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
|
|||||||
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_2.png`,
|
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_2.png`,
|
||||||
priceMain: '0',
|
priceMain: '0',
|
||||||
priceSub: '89',
|
priceSub: '89',
|
||||||
unitPrice: '1kg = 8,90 €',
|
unitPrice: 'jednotková cena 8,90 EUR/kg',
|
||||||
discountText: '36%',
|
discountText: '36%',
|
||||||
badgeText: 'zľava',
|
badgeText: 'zľava',
|
||||||
cardBgColor: '#feeaea',
|
cardBgColor: '#feeaea',
|
||||||
@@ -151,7 +151,7 @@ export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
|
|||||||
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_3.png`,
|
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_3.png`,
|
||||||
priceMain: '1',
|
priceMain: '1',
|
||||||
priceSub: '05',
|
priceSub: '05',
|
||||||
unitPrice: '1kg = 0,53 €',
|
unitPrice: 'jednotková cena 0,53 EUR/kg',
|
||||||
discountText: '34%',
|
discountText: '34%',
|
||||||
badgeText: 'zľava',
|
badgeText: 'zľava',
|
||||||
cardBgColor: '#fef0c7',
|
cardBgColor: '#fef0c7',
|
||||||
@@ -195,7 +195,7 @@ export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
|
|||||||
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_5.png`,
|
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_5.png`,
|
||||||
priceMain: '3',
|
priceMain: '3',
|
||||||
priceSub: '99',
|
priceSub: '99',
|
||||||
unitPrice: '1kg = 3,99 €',
|
unitPrice: 'jednotková cena 3,99 EUR/kg',
|
||||||
discountText: '',
|
discountText: '',
|
||||||
badgeText: 'Skvelá cena',
|
badgeText: 'Skvelá cena',
|
||||||
cardBgColor: '#dcb085',
|
cardBgColor: '#dcb085',
|
||||||
@@ -208,7 +208,7 @@ export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
|
|||||||
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_6.png`,
|
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_6.png`,
|
||||||
priceMain: '1',
|
priceMain: '1',
|
||||||
priceSub: '99',
|
priceSub: '99',
|
||||||
unitPrice: '1kg = 7,96 €',
|
unitPrice: 'jednotková cena 7,96 EUR/kg',
|
||||||
discountText: '38%',
|
discountText: '38%',
|
||||||
badgeText: 'zľava',
|
badgeText: 'zľava',
|
||||||
cardBgColor: '#dcb085',
|
cardBgColor: '#dcb085',
|
||||||
@@ -238,12 +238,12 @@ export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
|
|||||||
noCardDiscount: '21%',
|
noCardDiscount: '21%',
|
||||||
noCardPriceMain: '0',
|
noCardPriceMain: '0',
|
||||||
noCardPriceSub: '79',
|
noCardPriceSub: '79',
|
||||||
noCardUnitPrice: '1kg = 9,88 €',
|
noCardUnitPrice: 'jednotková cena 9,88 EUR/kg',
|
||||||
// S kartou
|
// S kartou
|
||||||
cardDiscount: '38%',
|
cardDiscount: '38%',
|
||||||
cardPriceMain: '0',
|
cardPriceMain: '0',
|
||||||
cardPriceSub: '62',
|
cardPriceSub: '62',
|
||||||
cardUnitPrice: '1kg = 7,75 €',
|
cardUnitPrice: 'jednotková cena 7,75 EUR/kg',
|
||||||
stores: { potraviny: true, supermarket: true, tempo: true },
|
stores: { potraviny: true, supermarket: true, tempo: true },
|
||||||
showSlovakFlag: true
|
showSlovakFlag: true
|
||||||
},
|
},
|
||||||
@@ -255,12 +255,12 @@ export const DEFAULT_CAMPAIGN: NewsletterCampaign = {
|
|||||||
noCardDiscount: '23%',
|
noCardDiscount: '23%',
|
||||||
noCardPriceMain: '0',
|
noCardPriceMain: '0',
|
||||||
noCardPriceSub: '45',
|
noCardPriceSub: '45',
|
||||||
noCardUnitPrice: '1kg = 11,25 €',
|
noCardUnitPrice: 'jednotková cena 11,25 EUR/kg',
|
||||||
// S kartou
|
// S kartou
|
||||||
cardDiscount: '40%',
|
cardDiscount: '40%',
|
||||||
cardPriceMain: '0',
|
cardPriceMain: '0',
|
||||||
cardPriceSub: '35',
|
cardPriceSub: '35',
|
||||||
cardUnitPrice: '1kg = 8,75 €',
|
cardUnitPrice: 'jednotková cena 8,75 EUR/kg',
|
||||||
stores: { potraviny: true, supermarket: true, tempo: true },
|
stores: { potraviny: true, supermarket: true, tempo: true },
|
||||||
showSlovakFlag: true
|
showSlovakFlag: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -409,13 +409,9 @@ function renderVisualExport(p: CardProduct, ctx: CardContext, key: string): stri
|
|||||||
// Texty pod vizuálom: názov 15px/700 + gramáž 15px/400, jednotková cena 11px #555, poznámka a platnosť 10px #888
|
// Texty pod vizuálom: názov 15px/700 + gramáž 15px/400, jednotková cena 11px #555, poznámka a platnosť 10px #888
|
||||||
const TEXT_FONT = "font-family: 'Poppins', Helvetica, Arial, sans-serif;";
|
const TEXT_FONT = "font-family: 'Poppins', Helvetica, Arial, sans-serif;";
|
||||||
function renderTexts(p: CardProduct, ctx: CardContext): string {
|
function renderTexts(p: CardProduct, ctx: CardContext): string {
|
||||||
// Pri predvolenej vernostnej cenovke sú jednotkové ceny priamo na cenovke
|
// Jednotková cena pod názvom, len ak ju nemá priamo cenovka (predvolená vernostná, šablóny s {jednotkova_cena…})
|
||||||
const unitPrice =
|
const badgeHasUnit = !p.customBadgePngUrl && (!p.badgeCustomTemplate ? ctx.variant === 'loyalty' : JSON.stringify(p.badgeCustomTemplate.layersConfig || []).includes('{jednotkova_cena'));
|
||||||
ctx.variant === 'loyalty'
|
const unitPrice = badgeHasUnit ? '' : ctx.variant === 'loyalty' ? p.cardUnitPrice || p.unitPrice : p.unitPrice;
|
||||||
? p.badgeCustomTemplate || p.customBadgePngUrl
|
|
||||||
? p.cardUnitPrice || p.unitPrice
|
|
||||||
: ''
|
|
||||||
: p.unitPrice;
|
|
||||||
const validity = p.validityOverride || ctx.validity;
|
const validity = p.validityOverride || ctx.validity;
|
||||||
const small = (text: string) =>
|
const small = (text: string) =>
|
||||||
`<div class="prod-unit force-text-dark" style="margin-top: 2px; ${TEXT_FONT} font-size: 10px; line-height: 1.3; color: #888888;">${escapeWithBreaks(text)}</div>`;
|
`<div class="prod-unit force-text-dark" style="margin-top: 2px; ${TEXT_FONT} font-size: 10px; line-height: 1.3; color: #888888;">${escapeWithBreaks(text)}</div>`;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Jednotná podoba jednotkovej ceny: "jednotková cena 7,75 EUR/kg"
|
||||||
|
// Prevedie staršie zápisy ("1kg = 7,75 €", "1 l = 0,79 €") aj zápis z podkladov ("7,75 EUR/kg").
|
||||||
|
// Iný text (napr. ručne napísaný) ostane bez zmeny.
|
||||||
|
export function normalizeUnitPrice(value: string | undefined, fallbackUnit = ''): string {
|
||||||
|
const v = (value || '').trim();
|
||||||
|
if (!v) return '';
|
||||||
|
const num = (s: string) => {
|
||||||
|
const n = parseFloat(s.replace(/\s/g, '').replace(',', '.'));
|
||||||
|
return Number.isFinite(n) ? n.toFixed(2).replace('.', ',') : null;
|
||||||
|
};
|
||||||
|
// "1kg = 7,75 €", "1 kg = 7,75 EUR"
|
||||||
|
let m = v.match(/^1\s*([a-zA-Zá-ž.]+)\s*=\s*([\d\s.,]+?)\s*(?:€|EUR)$/i);
|
||||||
|
if (m) {
|
||||||
|
const n = num(m[2]);
|
||||||
|
return n ? `jednotková cena ${n} EUR/${m[1]}` : v;
|
||||||
|
}
|
||||||
|
// "7,75 EUR/kg", "jednotková cena 7,75 EUR/kg", "7,75 €/kg", "7,75" (jednotka z fallbackUnit)
|
||||||
|
m = v.match(/^(?:jednotková cena\s*)?([\d\s.,]+?)\s*(?:EUR|€)?\s*(?:\/\s*(.+))?$/i);
|
||||||
|
if (m) {
|
||||||
|
const n = num(m[1]);
|
||||||
|
const unit = (m[2] || fallbackUnit).trim();
|
||||||
|
return n ? `jednotková cena ${n} EUR${unit ? `/${unit}` : ''}` : v;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user