feat: zakladne cenovky (s vykricnikom a bez) v tagoch sablon pre import

Prazdna Zlava graficka v podkladoch = zakladna cenovka; stlpec Vykricnik
rozlisi variant s vykricnikom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-21 14:12:55 +02:00
1 parent 08fb233da0
commit 11392722e2
3 files changed
+23 -14

No files matched your search

+5 -6
View File
@@ -5,7 +5,7 @@
// v súradniciach viewBoxu. Klávesnica: šípky posun (Shift = viac), Delete zmaže vrstvu, Ctrl+Z / Ctrl+Y späť / znova, // v súradniciach viewBoxu. Klávesnica: šípky posun (Shift = viac), Delete zmaže vrstvu, Ctrl+Z / Ctrl+Y späť / znova,
// Ctrl+S uloží. // Ctrl+S uloží.
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { BadgeTemplate, BadgeLayer, BadgeImportTags, BadgeColor } from '@/types/newsletter'; import { BadgeTemplate, BadgeLayer, BadgeImportTags, BadgeColor, BADGE_IMPORT_TYPES } from '@/types/newsletter';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import { renderCleanBadgeSvg, BadgeProductData, BadgeRow, badgeFromRow, badgeCode } from '@/lib/badgeUtils'; import { renderCleanBadgeSvg, BadgeProductData, BadgeRow, badgeFromRow, badgeCode } from '@/lib/badgeUtils';
import { import {
@@ -149,7 +149,6 @@ function loadFont(layer: BadgeLayer) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Tagy šablóny pre import podkladov: ktorým riadkom tabuľky (Zľava grafická + Farba cenovky + Pôvod) patrí // Tagy šablóny pre import podkladov: ktorým riadkom tabuľky (Zľava grafická + Farba cenovky + Pôvod) patrí
const BADGE_TYPES = ['SUPER CENA', 'SUPER VÝHODNÉ'];
const ImportTagsEditor: React.FC<{ tags: BadgeImportTags; onChange: (tags: BadgeImportTags) => void }> = ({ tags, onChange }) => { const ImportTagsEditor: React.FC<{ tags: BadgeImportTags; onChange: (tags: BadgeImportTags) => void }> = ({ tags, onChange }) => {
const cls = 'w-full text-[11px] border border-gray-300 rounded-lg px-2 py-1.5 bg-white'; const cls = 'w-full text-[11px] border border-gray-300 rounded-lg px-2 py-1.5 bg-white';
return ( return (
@@ -158,10 +157,10 @@ const ImportTagsEditor: React.FC<{ tags: BadgeImportTags; onChange: (tags: Badge
<span className="text-[10px] font-semibold text-gray-600">Typ</span> <span className="text-[10px] font-semibold text-gray-600">Typ</span>
<select value={tags.type || ''} onChange={(e) => onChange({ ...tags, type: e.target.value || undefined })} className={cls}> <select value={tags.type || ''} onChange={(e) => onChange({ ...tags, type: e.target.value || undefined })} className={cls}>
<option value="">— nepoužiť pri importe —</option> <option value="">— nepoužiť pri importe —</option>
{BADGE_TYPES.map((t) => ( {BADGE_IMPORT_TYPES.map((t) => (
<option key={t} value={t}>{t}</option> <option key={t.value} value={t.value}>{t.label}</option>
))} ))}
{tags.type && !BADGE_TYPES.includes(tags.type) && <option value={tags.type}>{tags.type}</option>} {tags.type && !BADGE_IMPORT_TYPES.some((t) => t.value === tags.type) && <option value={tags.type}>{tags.type}</option>}
</select> </select>
<span className="text-[10px] font-semibold text-gray-600">Farba</span> <span className="text-[10px] font-semibold text-gray-600">Farba</span>
<select value={tags.color || ''} onChange={(e) => onChange({ ...tags, color: (e.target.value || undefined) as BadgeColor | undefined })} className={cls}> <select value={tags.color || ''} onChange={(e) => onChange({ ...tags, color: (e.target.value || undefined) as BadgeColor | undefined })} className={cls}>
@@ -177,7 +176,7 @@ const ImportTagsEditor: React.FC<{ tags: BadgeImportTags; onChange: (tags: Badge
</select> </select>
</div> </div>
<p className="text-[10px] text-gray-400 leading-snug"> <p className="text-[10px] text-gray-400 leading-snug">
Pri importe ZIP-u sa šablóna priradí produktom so stĺpcami Zľava grafická, Farba cenovky a Pôvod podľa týchto tagov. Pri importe ZIP-u sa šablóna priradí produktom podľa stĺpcov Zľava grafická (prázdna = základná, s výkričníkom podľa stĺpca Výkričník), Farba cenovky a Pôvod.
</p> </p>
</div> </div>
); );
+9 -7
View File
@@ -1,7 +1,7 @@
// Import ZIP-u s podkladmi z Databázy produktov: tabuľka podklady_*.xlsx (jeden riadok = jeden produkt) // Import ZIP-u s podkladmi z Databázy produktov: tabuľka podklady_*.xlsx (jeden riadok = jeden produkt)
// a fotky v priečinkoch <EAN>/web/*.png. // a fotky v priečinkoch <EAN>/web/*.png.
import JSZip from 'jszip'; import JSZip from 'jszip';
import { BadgeColor, BadgeTemplate, ProductItem } from '@/types/newsletter'; import { BADGE_IMPORT_TYPES, BadgeColor, BadgeTemplate, ProductItem } from '@/types/newsletter';
import { findBadgeByCode } from './badgeUtils'; import { findBadgeByCode } from './badgeUtils';
import { CARD_BG_BY_BADGE_COLOR } from './productCard'; import { CARD_BG_BY_BADGE_COLOR } from './productCard';
@@ -43,14 +43,13 @@ function toBadgeColor(value: string): BadgeColor | undefined {
// opačne nie – zahraničný produkt nesmie dostať cenovku so slovenskou vlajkou. // opačne nie – zahraničný produkt nesmie dostať cenovku so slovenskou vlajkou.
function findBadgeByTags( function findBadgeByTags(
badges: BadgeTemplate[], badges: BadgeTemplate[],
graphic: string, type: string,
color: BadgeColor | undefined, color: BadgeColor | undefined,
origin: string origin: string
): { badge: BadgeTemplate; match: 'exact' | 'origin' } | undefined { ): { badge: BadgeTemplate; match: 'exact' | 'origin' } | undefined {
if (!graphic) return undefined;
const sk = norm(origin) === 'sk' ? 'SK' : ''; const sk = norm(origin) === 'sk' ? 'SK' : '';
const candidates = badges.filter( const candidates = badges.filter(
(b) => b.importTags?.type && norm(b.importTags.type) === norm(graphic) && (!b.importTags.color || b.importTags.color === color) (b) => b.importTags?.type && norm(b.importTags.type) === norm(type) && (!b.importTags.color || b.importTags.color === color)
); );
// presná farba má prednosť pred „hocijakou“ // presná farba má prednosť pred „hocijakou“
const rank = (b: BadgeTemplate) => (b.importTags?.color ? 0 : 1); const rank = (b: BadgeTemplate) => (b.importTags?.color ? 0 : 1);
@@ -143,7 +142,8 @@ export async function readPodkladyZip(file: File, badges: BadgeTemplate[]): Prom
color: col('Farba cenovky'), color: col('Farba cenovky'),
origin: col('Pôvod'), origin: col('Pôvod'),
formats: col('Formát'), formats: col('Formát'),
deposit: col('Zálohovanie') deposit: col('Zálohovanie'),
exclaim: col('Výkričník')
}; };
if (c.ean < 0) throw new Error('Tabuľka nemá stĺpec EAN – nejde o podklady z Databázy produktov?'); if (c.ean < 0) throw new Error('Tabuľka nemá stĺpec EAN – nejde o podklady z Databázy produktov?');
const get = (row: string[], i: number) => (i >= 0 ? String(row[i] ?? '').trim() : ''); const get = (row: string[], i: number) => (i >= 0 ? String(row[i] ?? '').trim() : '');
@@ -163,8 +163,10 @@ export async function readPodkladyZip(file: File, badges: BadgeTemplate[]): Prom
const colorText = get(row, c.color); const colorText = get(row, c.color);
const color = toBadgeColor(colorText); const color = toBadgeColor(colorText);
const origin = get(row, c.origin); const origin = get(row, c.origin);
// prázdna „Zľava grafická“ = základná cenovka (s výkričníkom podľa stĺpca Výkričník)
const type = graphic || (norm(get(row, c.exclaim)) === 'ano' ? 'ZÁKLADNÁ !' : 'ZÁKLADNÁ');
const tries = [[graphic, colorText, origin], [graphic, colorText], [graphic]].map((parts) => parts.filter(Boolean).join(' | ')); const tries = [[graphic, colorText, origin], [graphic, colorText], [graphic]].map((parts) => parts.filter(Boolean).join(' | '));
const byTags = findBadgeByTags(badges, graphic, color, origin); const byTags = findBadgeByTags(badges, type, color, origin);
const byName = !byTags && graphic ? tries.map((t) => findBadgeByCode(badges, t)).find(Boolean) : undefined; const byName = !byTags && graphic ? tries.map((t) => findBadgeByCode(badges, t)).find(Boolean) : undefined;
const formats = norm(get(row, c.formats)); const formats = norm(get(row, c.formats));
@@ -178,7 +180,7 @@ export async function readPodkladyZip(file: File, badges: BadgeTemplate[]): Prom
cardPrice: toNumber(get(row, c.cardPrice)), cardPrice: toNumber(get(row, c.cardPrice)),
unitPrice: formatUnitPrice(get(row, c.unitPrice)), unitPrice: formatUnitPrice(get(row, c.unitPrice)),
discount: discount ? `${Math.round(discount)}%` : '', discount: discount ? `${Math.round(discount)}%` : '',
badgeLabel: tries[0], badgeLabel: [BADGE_IMPORT_TYPES.find((t) => t.value === type)?.label || type, colorText, origin].filter(Boolean).join(' | '),
badge: byTags?.badge || byName, badge: byTags?.badge || byName,
badgeMatch: byTags ? byTags.match : byName ? 'exact' : undefined, badgeMatch: byTags ? byTags.match : byName ? 'exact' : undefined,
color, color,
+9 -1
View File
@@ -243,10 +243,18 @@ export interface BadgeTemplate {
importTags?: BadgeImportTags; // podľa čoho sa šablóna vyberie pri importe podkladov (ZIP) importTags?: BadgeImportTags; // podľa čoho sa šablóna vyberie pri importe podkladov (ZIP)
} }
// Druhy cenoviek v podkladoch: „Zľava grafická“, a keď je prázdna, základná cenovka s výkričníkom alebo bez
export const BADGE_IMPORT_TYPES = [
{ value: 'SUPER CENA', label: 'SUPER CENA' },
{ value: 'SUPER VÝHODNÉ', label: 'SUPER VÝHODNÉ' },
{ value: 'ZÁKLADNÁ !', label: 'Základná s výkričníkom' },
{ value: 'ZÁKLADNÁ', label: 'Základná (bez nápisu)' }
];
// Tagy šablóny cenovky pre import podkladov – zodpovedajú stĺpcom tabuľky z Databázy produktov // Tagy šablóny cenovky pre import podkladov – zodpovedajú stĺpcom tabuľky z Databázy produktov
export type BadgeColor = 'cervena' | 'oranzova' | 'zelena'; export type BadgeColor = 'cervena' | 'oranzova' | 'zelena';
export interface BadgeImportTags { export interface BadgeImportTags {
type?: string; // „Zľava grafická“, napr. SUPER CENA, SUPER VÝHODNÉ type?: string; // druh cenovky – jeden z BADGE_IMPORT_TYPES (SUPER CENA, SUPER VÝHODNÉ, ZÁKLADNÁ, ZÁKLADNÁ !)
color?: BadgeColor; // „Farba cenovky“ color?: BadgeColor; // „Farba cenovky“
origin?: 'SK' | ''; // „Pôvod“: SK alebo bez pôvodu origin?: 'SK' | ''; // „Pôvod“: SK alebo bez pôvodu
} }