feat: kod sablony cenovky pre hromadny import
- sablona cenovky ma kod (napr. SUPER_CENA), nastavuje sa v hlavicke editora cenoviek; kontrola jedinecnosti, ulozi sa v layers_config (bez zmeny DB) - import CSV/JSON/XML: novy stlpec Cenovka (kod sablony), automaticky rozpoznany (cenovka/badge/sablona); pouzije sa kod alebo nazov sablony, bez ohladu na velke pismena a diakritiku - nahlad importu ukazuje priradenu sablonu, neznamy kod nahlasi upozornenie - spolocne nacitanie sablon z DB (badgeFromRow) pre editor aj stranku Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6d73660cac
commit
332ab833e9
5 files changed
+138
-41
No files matched your search
@@ -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 } from '@/lib/badgeUtils';
|
||||
import { renderCleanBadgeSvg, BadgeProductData, BadgeRow, badgeFromRow, normalizeBadgeCode } from '@/lib/badgeUtils';
|
||||
import {
|
||||
Tag,
|
||||
Plus,
|
||||
@@ -45,6 +45,7 @@ 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,
|
||||
@@ -53,27 +54,6 @@ function toBadgeDbPayload(badge: BadgeTemplate) {
|
||||
};
|
||||
}
|
||||
|
||||
interface BadgeRow {
|
||||
id: string;
|
||||
name: string;
|
||||
svg_raw: string | null;
|
||||
layers_config: BadgeLayer[] | { layers?: BadgeLayer[]; width?: number; align?: BadgeTemplate['align']; paddingTop?: number; paddingBottom?: number } | null;
|
||||
}
|
||||
|
||||
function fromBadgeRow(d: BadgeRow): BadgeTemplate {
|
||||
const cfg = d.layers_config && !Array.isArray(d.layers_config) ? d.layers_config : null;
|
||||
return {
|
||||
id: d.id,
|
||||
name: d.name,
|
||||
svgRaw: d.svg_raw || '',
|
||||
layersConfig: cfg ? cfg.layers || [] : (d.layers_config as BadgeLayer[]) || [],
|
||||
width: cfg?.width || 110,
|
||||
align: cfg?.align || 'center',
|
||||
paddingTop: cfg?.paddingTop ?? 4,
|
||||
paddingBottom: cfg?.paddingBottom ?? 4
|
||||
};
|
||||
}
|
||||
|
||||
function saveLocal(list: BadgeTemplate[]) {
|
||||
try {
|
||||
localStorage.setItem(LOCAL_KEY, JSON.stringify(list));
|
||||
@@ -294,6 +274,10 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
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));
|
||||
const layer = badge?.layersConfig.find((l) => l.id === selectedId) || null;
|
||||
const vb = useMemo(() => viewBoxOf(badge?.svgRaw), [badge?.svgRaw]);
|
||||
const canvasWidth = Math.round(300 * zoom);
|
||||
@@ -324,7 +308,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
let loaded: BadgeTemplate[] = [];
|
||||
try {
|
||||
const { data } = await supabase.from('badges').select('*').order('created_at', { ascending: false });
|
||||
loaded = ((data as BadgeRow[]) || []).map(fromBadgeRow);
|
||||
loaded = ((data as BadgeRow[]) || []).map(badgeFromRow);
|
||||
} catch {
|
||||
// bez databázy použijeme kópiu v prehliadači
|
||||
}
|
||||
@@ -663,6 +647,21 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
title="Názov šablóny"
|
||||
/>
|
||||
)}
|
||||
{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>
|
||||
)}
|
||||
{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>}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user