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
@@ -3,7 +3,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import Papa from 'papaparse';
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
import { ProductItem } from '@/types/newsletter';
|
||||
import { BadgeTemplate, ProductItem } from '@/types/newsletter';
|
||||
import { findBadgeByCode, normalizeBadgeCode } from '@/lib/badgeUtils';
|
||||
import { Upload, Check, AlertCircle, FileText, X } from 'lucide-react';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
|
||||
@@ -11,12 +12,14 @@ interface ImportModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onImportProducts: (products: ProductItem[]) => void;
|
||||
availableBadges?: BadgeTemplate[]; // šablóny cenoviek – stĺpec s kódom určí, ktorú použiť
|
||||
}
|
||||
|
||||
export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onImportProducts
|
||||
onImportProducts,
|
||||
availableBadges = []
|
||||
}) => {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [fileType, setFileType] = useState<'csv' | 'json' | 'xml'>('csv');
|
||||
@@ -32,6 +35,7 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
discountText: string;
|
||||
imageUrl: string;
|
||||
validity: string;
|
||||
badgeCode: string;
|
||||
}>({
|
||||
title: '',
|
||||
priceMain: '',
|
||||
@@ -39,7 +43,8 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
unitPrice: '',
|
||||
discountText: '',
|
||||
imageUrl: '',
|
||||
validity: ''
|
||||
validity: '',
|
||||
badgeCode: ''
|
||||
});
|
||||
|
||||
const [presetName, setPresetName] = useState('');
|
||||
@@ -125,6 +130,7 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
if (lower.includes('zlava') || lower.includes('discount')) newMapping.discountText = h;
|
||||
if (lower.includes('obrazok') || lower.includes('image') || lower.includes('foto')) newMapping.imageUrl = h;
|
||||
if (lower.includes('platnost') || lower.includes('valid')) newMapping.validity = h;
|
||||
if (lower.includes('cenovk') || lower.includes('badge') || lower.includes('sablon') || lower.includes('template')) newMapping.badgeCode = h;
|
||||
});
|
||||
setMapping(newMapping);
|
||||
};
|
||||
@@ -148,7 +154,11 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
if (pSub.length === 1) pSub += '0';
|
||||
}
|
||||
|
||||
// Šablóna cenovky podľa kódu (alebo názvu) zo súboru
|
||||
const badge = mapping.badgeCode ? findBadgeByCode(availableBadges, row[mapping.badgeCode]) : undefined;
|
||||
|
||||
return {
|
||||
...(badge ? { badgeCustomTemplate: badge, badgeId: badge.id, badgeWidth: badge.width ?? 110 } : {}),
|
||||
id: `imported-${Date.now()}-${idx}`,
|
||||
title: String(row[mapping.title] || 'Nový produkt'),
|
||||
imageUrl: row[mapping.imageUrl] || 'https://dqexxfspkylqttrtxsww.supabase.co/storage/v1/object/public/newsletter-assets/produkt_1.png',
|
||||
@@ -163,6 +173,20 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
};
|
||||
});
|
||||
|
||||
// Kódy cenoviek, ku ktorým sa nenašla šablóna
|
||||
if (mapping.badgeCode) {
|
||||
const unknown = [
|
||||
...new Set(
|
||||
parsedRows
|
||||
.map((row) => String(row[mapping.badgeCode] || '').trim())
|
||||
.filter((v) => v && !findBadgeByCode(availableBadges, v))
|
||||
)
|
||||
];
|
||||
if (unknown.length) {
|
||||
alert(`Pre tieto kódy cenoviek sa nenašla šablóna (produkty dostanú predvolenú cenovku):\n${unknown.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
onImportProducts(importedProducts);
|
||||
onClose();
|
||||
};
|
||||
@@ -268,6 +292,25 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<label className="block text-xs font-semibold text-gray-600 mb-1">Cenovka (kód šablóny)</label>
|
||||
<select
|
||||
value={mapping.badgeCode}
|
||||
onChange={(e) => setMapping({ ...mapping, badgeCode: e.target.value })}
|
||||
className="w-full text-sm border border-gray-300 rounded-lg p-2 bg-white"
|
||||
>
|
||||
<option value="">-- Bez cenovky zo súboru (predvolená) --</option>
|
||||
{columns.map((c) => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
<p className="text-[11px] text-gray-500 mt-1">
|
||||
Kódy šablón:{' '}
|
||||
{availableBadges.length
|
||||
? availableBadges.map((b) => normalizeBadgeCode(b.code || b.name)).join(', ')
|
||||
: 'zatiaľ žiadne šablóny'}
|
||||
{' '}(kód nastavíte v Editore cenoviek; funguje aj názov šablóny)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-gray-600 mb-1">URL obrázka</label>
|
||||
<select
|
||||
@@ -290,7 +333,14 @@ export const ProductImportModal: React.FC<ImportModalProps> = ({
|
||||
<div className="p-3 text-xs space-y-2 bg-white">
|
||||
{parsedRows.slice(0, 2).map((row, idx) => (
|
||||
<div key={idx} className="flex items-center justify-between border-b border-gray-100 pb-2">
|
||||
<span className="font-semibold text-gray-800">{row[mapping.title] || 'Bez názvu'}</span>
|
||||
<span className="font-semibold text-gray-800">
|
||||
{row[mapping.title] || 'Bez názvu'}
|
||||
{mapping.badgeCode && (
|
||||
<span className="ml-2 text-[10px] font-medium text-gray-500">
|
||||
cenovka: {findBadgeByCode(availableBadges, row[mapping.badgeCode])?.name || `? (${row[mapping.badgeCode] || 'prázdne'})`}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-red-600 font-bold">
|
||||
{row[mapping.priceMain] || '0'},{row[mapping.priceSub] || '00'} €
|
||||
</span>
|
||||
|
||||
Reference in new issue
Block a user