feat: filter roletky cenoviek v produkte (typ, farba, povod, appka), zoradenie podla nazvu
Aktualna sablona ostava v zozname aj ked ju filter skryje alebo bola zmazana. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2a0b1f37ba
commit
c8543b4d9b
1 file changed
+95
-3
@@ -2,8 +2,8 @@
|
||||
|
||||
// Editor jedného produktu (bežné aj vernostné produkty) – zbalený po produktoch, len to, čo treba:
|
||||
// texty, fotka (zoom/posun), cenovka (šablóna, veľkosť, ceny), dostupnosť a ďalšie možnosti.
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { BadgeTemplate, CardProduct, ProductLabel, ProductPhoto } from '@/types/newsletter';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { BADGE_IMPORT_TYPES, BadgeTemplate, CardProduct, ProductLabel, ProductPhoto } from '@/types/newsletter';
|
||||
import { ChevronDown, ChevronUp, Trash2, Pencil, Globe, Layers } from 'lucide-react';
|
||||
import { ImageUploadInput } from './ImageUploadInput';
|
||||
import { pickAlt } from '@/lib/altText';
|
||||
@@ -99,6 +99,84 @@ const ResetLink: React.FC<{ onClick: () => void }> = ({ onClick }) => (
|
||||
</button>
|
||||
);
|
||||
|
||||
// Filter šablón cenoviek v produkte podľa tagov (typ, farba, pôvod, appka); prázdne = všetky
|
||||
interface BadgeFilter {
|
||||
type: string;
|
||||
color: string;
|
||||
origin: string; // '' | 'SK' | 'none'
|
||||
app: string; // '' | 'app' | 'none'
|
||||
}
|
||||
const EMPTY_BADGE_FILTER: BadgeFilter = { type: '', color: '', origin: '', app: '' };
|
||||
// posledný filter – pri otvorení ďalšieho produktu ostane nastavený
|
||||
let lastBadgeFilter: BadgeFilter = EMPTY_BADGE_FILTER;
|
||||
|
||||
export function filterBadges(badges: BadgeTemplate[], f: BadgeFilter): BadgeTemplate[] {
|
||||
return badges
|
||||
.filter((b) => {
|
||||
const t = b.importTags || {};
|
||||
if (f.type && t.type !== f.type) return false;
|
||||
if (f.color && t.color !== f.color) return false;
|
||||
if (f.origin === 'SK' && t.origin !== 'SK') return false;
|
||||
if (f.origin === 'none' && t.origin === 'SK') return false;
|
||||
if (f.app === 'app' && !t.app) return false;
|
||||
if (f.app === 'none' && t.app) return false;
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name, 'sk'));
|
||||
}
|
||||
|
||||
const BadgeFilterBar: React.FC<{ filter: BadgeFilter; onChange: (f: BadgeFilter) => void; shown: number; total: number }> = ({
|
||||
filter,
|
||||
onChange,
|
||||
shown,
|
||||
total
|
||||
}) => {
|
||||
const cls = (active: boolean) =>
|
||||
`min-w-0 text-[10px] rounded-md border px-1 py-1 bg-white ${active ? 'border-red-400 text-red-700 font-bold' : 'border-gray-200 text-gray-600'}`;
|
||||
const set = (k: keyof BadgeFilter) => (e: React.ChangeEvent<HTMLSelectElement>) => onChange({ ...filter, [k]: e.target.value });
|
||||
const active = Object.values(filter).some(Boolean);
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="grid grid-cols-4 gap-1 flex-1 min-w-0">
|
||||
<select value={filter.type} onChange={set('type')} className={cls(!!filter.type)} title="Typ cenovky">
|
||||
<option value="">Typ: všetky</option>
|
||||
{BADGE_IMPORT_TYPES.map((t) => (
|
||||
<option key={t.value} value={t.value}>
|
||||
{t.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select value={filter.color} onChange={set('color')} className={cls(!!filter.color)} title="Farba cenovky">
|
||||
<option value="">Farba: všetky</option>
|
||||
<option value="cervena">Červená</option>
|
||||
<option value="oranzova">Oranžová</option>
|
||||
<option value="zelena">Zelená</option>
|
||||
</select>
|
||||
<select value={filter.origin} onChange={set('origin')} className={cls(!!filter.origin)} title="Pôvod">
|
||||
<option value="">Pôvod: všetky</option>
|
||||
<option value="SK">SK</option>
|
||||
<option value="none">Bez pôvodu</option>
|
||||
</select>
|
||||
<select value={filter.app} onChange={set('app')} className={cls(!!filter.app)} title="Cenovka pre appkáčov">
|
||||
<option value="">Appka: všetky</option>
|
||||
<option value="app">Pre appkáčov</option>
|
||||
<option value="none">Bežná</option>
|
||||
</select>
|
||||
</div>
|
||||
{active && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(EMPTY_BADGE_FILTER)}
|
||||
className="shrink-0 text-[10px] font-bold text-gray-500 hover:text-red-700 px-1"
|
||||
title="Zrušiť filter"
|
||||
>
|
||||
{shown}/{total} ✕
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Poradie fotiek ako vrstvy: navrchu zoznamu je fotka najviac vpredu
|
||||
const PhotoOrderList: React.FC<{ product: CardProduct; onChange: (order: string[]) => void }> = ({ product, onChange }) => {
|
||||
const stack = photoStack(product); // odzadu dopredu
|
||||
@@ -247,6 +325,12 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
||||
}) => {
|
||||
// Po otvorení (aj klikom na produkt v náhľade) posunieme panel na tento produkt
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const [badgeFilter, setBadgeFilterState] = useState<BadgeFilter>(() => lastBadgeFilter);
|
||||
const setBadgeFilter = (f: BadgeFilter) => {
|
||||
lastBadgeFilter = f;
|
||||
setBadgeFilterState(f);
|
||||
};
|
||||
const filteredBadges = filterBadges(availableBadges, badgeFilter);
|
||||
useEffect(() => {
|
||||
if (open) rootRef.current?.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
||||
}, [open]);
|
||||
@@ -416,6 +500,7 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
||||
|
||||
{/* CENOVKA */}
|
||||
<Block title="Cenovka">
|
||||
<BadgeFilterBar filter={badgeFilter} onChange={setBadgeFilter} shown={filteredBadges.length} total={availableBadges.length} />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<select
|
||||
value={p.customBadgePngUrl ? 'png' : p.badgeCustomTemplate?.id || 'default'}
|
||||
@@ -423,7 +508,14 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
||||
className={`${inputCls} font-medium`}
|
||||
>
|
||||
<option value="default">{isLoyalty ? 'Predvolená (bez karty / s kartou)' : 'Predvolená COOP cenovka'}</option>
|
||||
{availableBadges.map((b) => (
|
||||
{/* aktuálna šablóna ostáva v zozname, aj keď ju filter skryje alebo už bola zmazaná */}
|
||||
{p.badgeCustomTemplate && !p.customBadgePngUrl && !filteredBadges.some((b) => b.id === p.badgeCustomTemplate!.id) && (
|
||||
<option value={p.badgeCustomTemplate.id}>
|
||||
{p.badgeCustomTemplate.name}
|
||||
{availableBadges.some((b) => b.id === p.badgeCustomTemplate!.id) ? '' : ' (zmazaná šablóna)'}
|
||||
</option>
|
||||
)}
|
||||
{filteredBadges.map((b) => (
|
||||
<option key={b.id} value={b.id}>
|
||||
{b.name}
|
||||
</option>
|
||||
|
||||
Reference in new issue
Block a user