refactor: kod sablony cenovky vznika z nazvu

Kod pre hromadny import = nazov sablony bez diakritiky, velkymi pismenami,
medzery a interpunkcia -> _ (napr. Super cena so zlavou -> SUPER_CENA_SO_ZLAVOU).
Samostatne pole Kod v editore je nahradene zobrazenim kodu pri nazve
s upozornenim, ak rovnaky kod ma ina sablona.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-21 12:53:25 +02:00
1 parent 4510e4f999
commit 905170f66e
4 files changed
+20 -30

No files matched your search

+11 -18
View File
@@ -7,7 +7,7 @@
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { BadgeTemplate, BadgeLayer } from '@/types/newsletter';
import { supabase } from '@/lib/supabase';
import { renderCleanBadgeSvg, BadgeProductData, BadgeRow, badgeFromRow, normalizeBadgeCode } from '@/lib/badgeUtils';
import { renderCleanBadgeSvg, BadgeProductData, BadgeRow, badgeFromRow, badgeCode } from '@/lib/badgeUtils';
import {
Tag,
Plus,
@@ -45,7 +45,6 @@ function toBadgeDbPayload(badge: BadgeTemplate) {
svg_raw: badge.svgRaw,
layers_config: {
layers: badge.layersConfig,
code: badge.code ? normalizeBadgeCode(badge.code) : null,
width: badge.width || 110,
align: badge.align || 'center',
paddingTop: badge.paddingTop ?? 4,
@@ -275,9 +274,9 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
const isDirty = !!badge && JSON.stringify(badge) !== savedJson;
// Kód pre import musí byť jedinečný
const codeTaken =
!!badge?.code &&
badges.some((b) => b.id !== badge.id && b.code && normalizeBadgeCode(b.code) === normalizeBadgeCode(badge.code));
// Kód pre hromadný import vzniká z názvu – dve šablóny nesmú mať rovnaký
const code = badge ? badgeCode(badge) : '';
const codeTaken = !!code && badges.some((b) => b.id !== badge?.id && badgeCode(b) === code);
const layer = badge?.layersConfig.find((l) => l.id === selectedId) || null;
const vb = useMemo(() => viewBoxOf(badge?.svgRaw), [badge?.svgRaw]);
const canvasWidth = Math.round(300 * zoom);
@@ -648,19 +647,13 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
/>
)}
{badge && (
<label className="flex items-center gap-1 shrink-0" title="Kód pre hromadný import: v stĺpci cenovky v CSV/JSON/XML zadajte tento kód">
<span className="text-[10px] font-bold text-gray-500">Kód</span>
<input
value={badge.code || ''}
onChange={(e) => commit({ ...badge, code: e.target.value.toUpperCase() }, false)}
onBlur={() => badge.code && commit({ ...badge, code: normalizeBadgeCode(badge.code) }, false)}
placeholder={normalizeBadgeCode(badge.name) || 'KOD'}
className={`w-32 text-[11px] font-mono uppercase border rounded-lg px-1.5 py-1 ${
codeTaken ? 'border-red-400 bg-red-50' : 'border-gray-200'
}`}
/>
{codeTaken && <span className="text-[10px] font-bold text-red-600">už používa iná šablóna</span>}
</label>
<span
className={`shrink-0 text-[10px] font-mono rounded-md px-1.5 py-0.5 ${codeTaken ? 'text-red-700 bg-red-50 border border-red-300' : 'text-gray-500 bg-gray-100'}`}
title="Kód pre hromadný import (vzniká z názvu): v stĺpci cenovky v CSV/JSON/XML zadajte tento kód alebo názov"
>
{code || '—'}
{codeTaken && ' · rovnaký kód má iná šablóna'}
</span>
)}
{isDirty && <span className="text-[10px] font-bold text-amber-600 bg-amber-50 border border-amber-200 rounded-full px-2 py-0.5">Neuložené</span>}
{savedFlash && <span className="text-[10px] font-bold text-emerald-600 flex items-center gap-1"><Check className="w-3 h-3" /> Uložené</span>}
+3 -3
View File
@@ -4,7 +4,7 @@ import React, { useState } from 'react';
import Papa from 'papaparse';
import { XMLParser } from 'fast-xml-parser';
import { BadgeTemplate, ProductItem } from '@/types/newsletter';
import { findBadgeByCode, normalizeBadgeCode } from '@/lib/badgeUtils';
import { findBadgeByCode, badgeCode } from '@/lib/badgeUtils';
import { Upload, Check, AlertCircle, FileText, X } from 'lucide-react';
import { supabase } from '@/lib/supabase';
@@ -305,9 +305,9 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
<p className="text-[11px] text-gray-500 mt-1">
Kódy šablón:{' '}
{availableBadges.length
? availableBadges.map((b) => normalizeBadgeCode(b.code || b.name)).join(', ')
? availableBadges.map(badgeCode).join(', ')
: 'zatiaľ žiadne šablóny'}
{' '}(kód nastavíte v Editore cenoviek; funguje aj názov šablóny)
{' '}(kód vzniká z názvu šablóny; v súbore môže byť kód aj presný názov)
</p>
</div>
+6 -8
View File
@@ -15,7 +15,6 @@ export interface BadgeRow {
| BadgeLayer[]
| {
layers?: BadgeLayer[];
code?: string | null;
width?: number;
align?: BadgeTemplate['align'];
paddingTop?: number;
@@ -30,7 +29,6 @@ export function badgeFromRow(d: BadgeRow): BadgeTemplate {
return {
id: d.id,
name: d.name,
code: cfg?.code || undefined,
svgRaw: d.svg_raw || '',
layersConfig: cfg ? cfg.layers || [] : (d.layers_config as BadgeLayer[]) || [],
width: cfg?.width || 110,
@@ -40,7 +38,8 @@ export function badgeFromRow(d: BadgeRow): BadgeTemplate {
};
}
// Kód šablóny: veľké písmená bez diakritiky, medzery a pomlčky → _ (napr. "Super cena" → SUPER_CENA)
// Kód šablóny pre hromadný import = názov šablóny upravený na kód:
// bez diakritiky, veľké písmená, medzery a interpunkcia → _ (napr. „Super cena – 1+1“ → SUPER_CENA_1_1)
export function normalizeBadgeCode(value: string | null | undefined): string {
return String(value || '')
.normalize('NFD')
@@ -51,14 +50,13 @@ export function normalizeBadgeCode(value: string | null | undefined): string {
.replace(/^_+|_+$/g, '');
}
// Šablóna podľa hodnoty zo súboru: najprv kód, potom názov šablóny
export const badgeCode = (b: Pick<BadgeTemplate, 'name'>) => normalizeBadgeCode(b.name);
// Šablóna podľa hodnoty zo súboru (kód alebo názov – oboje sa porovná po úprave na kód)
export function findBadgeByCode(badges: BadgeTemplate[], value: string | null | undefined): BadgeTemplate | undefined {
const wanted = normalizeBadgeCode(value);
if (!wanted) return undefined;
return (
badges.find((b) => b.code && normalizeBadgeCode(b.code) === wanted) ||
badges.find((b) => normalizeBadgeCode(b.name) === wanted)
);
return badges.find((b) => badgeCode(b) === wanted);
}
export interface BadgeProductData {
-1
View File
@@ -219,7 +219,6 @@ export interface BadgeLayer {
export interface BadgeTemplate {
id: string;
name: string;
code?: string; // kód pre hromadný import (napr. SUPER_CENA) – stĺpec v CSV/JSON/XML určí šablónu
svgBackgroundUrl?: string;
svgRaw?: string;
layersConfig: BadgeLayer[];