feat: reusable badge templates, dynamic price and superscript cents, size/align/padding controls, fix saving

This commit is contained in:
Jakub_Hostacny committed 2026-09-24 12:07:52 +02:00
1 parent f9aa2297ba
commit 5a6788b593
6 files changed
+1196 -359

No files matched your search

File diff suppressed because it is too large. Load diff
+194 -13
View File
@@ -1,7 +1,7 @@
'use client';
import React from 'react';
import { NewsletterModule, ProductItem } from '@/types/newsletter';
import { NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter';
import { Plus, Trash2, Image as ImageIcon, Tag, Store } from 'lucide-react';
import { SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
import { ImageUploadInput } from './ImageUploadInput';
@@ -11,13 +11,15 @@ interface ConfigPanelProps {
onUpdateModule: (updated: NewsletterModule) => void;
onOpenBadgeManager?: (productId: string) => void;
onOpenImport?: () => void;
availableBadges?: BadgeTemplate[];
}
export const ConfigPanel: React.FC<ConfigPanelProps> = ({
module,
onUpdateModule,
onOpenBadgeManager,
onOpenImport
onOpenImport,
availableBadges = []
}) => {
if (!module) {
return (
@@ -510,18 +512,197 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
</label>
</div>
{/* Grafická cenovka - Editor cenoviek */}
<div className="pt-2 border-t border-gray-200 flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Tag className="w-3.5 h-3.5 text-red-600" />
<span className="text-xs font-semibold text-gray-700">Grafická cenovka</span>
{/* Grafická cenovka a šablóna */}
<div className="pt-2 border-t border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Tag className="w-3.5 h-3.5 text-red-600" />
<span className="text-xs font-bold text-gray-800">Cenovka</span>
</div>
<button
type="button"
onClick={() => onOpenBadgeManager && onOpenBadgeManager(p.id)}
className="text-[11px] font-bold text-red-600 hover:text-red-700 bg-red-50 hover:bg-red-100 border border-red-200 px-2.5 py-1 rounded-lg transition-colors"
>
Otvoriť Editor cenoviek
</button>
</div>
{/* Výber opakovane použiteľnej šablóny */}
<div>
<label className="text-[10px] text-gray-500 font-semibold block mb-0.5">
Šablóna cenovky
</label>
<select
value={p.badgeCustomTemplate?.id || (p.customBadgePngUrl ? 'custom_png' : 'default_coop')}
onChange={(e) => {
const val = e.target.value;
const products = [...module.data.products];
if (val === 'default_coop') {
products[pIdx].badgeCustomTemplate = undefined;
products[pIdx].customBadgePngUrl = undefined;
products[pIdx].badgeId = undefined;
} else {
const found = (availableBadges || []).find((b) => b.id === val);
if (found) {
products[pIdx].badgeCustomTemplate = found;
products[pIdx].badgeId = found.id;
products[pIdx].badgeWidth = found.width ?? products[pIdx].badgeWidth ?? 110;
products[pIdx].badgeAlign = found.align ?? products[pIdx].badgeAlign ?? 'center';
products[pIdx].badgePaddingTop = found.paddingTop ?? products[pIdx].badgePaddingTop ?? 4;
products[pIdx].badgePaddingBottom = found.paddingBottom ?? products[pIdx].badgePaddingBottom ?? 4;
products[pIdx].customBadgePngUrl = undefined;
}
}
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1.5 bg-white font-medium"
>
<option value="default_coop">Predvolená COOP cenovka (červeno-biela)</option>
{(availableBadges || []).map((b) => (
<option key={b.id} value={b.id}>
{b.name}
</option>
))}
</select>
</div>
{/* Tlačidlo: Aplikovať túto cenovku na všetky produkty v sekcii */}
{p.badgeCustomTemplate && (
<button
type="button"
onClick={() => {
if (confirm(`Naozaj chcete aplikovať šablónu "${p.badgeCustomTemplate?.name}" na všetky produkty v tejto sekcii?`)) {
const products = (module.data.products || []).map((prod: ProductItem) => ({
...prod,
badgeCustomTemplate: p.badgeCustomTemplate,
badgeId: p.badgeCustomTemplate?.id,
badgeWidth: p.badgeWidth || p.badgeCustomTemplate?.width || 110,
badgeAlign: p.badgeAlign || p.badgeCustomTemplate?.align || 'center',
badgePaddingTop: p.badgePaddingTop ?? p.badgeCustomTemplate?.paddingTop ?? 4,
badgePaddingBottom: p.badgePaddingBottom ?? p.badgeCustomTemplate?.paddingBottom ?? 4,
customBadgePngUrl: undefined
}));
updateData({ products });
}
}}
className="w-full py-1.5 text-[11px] font-bold text-slate-700 bg-slate-100 hover:bg-slate-200 border border-slate-300 rounded-lg transition-colors text-center"
>
Použiť túto cenovku pre všetky produkty v sekcii
</button>
)}
{/* Rýchle ovládanie veľkosti (Width), zarovnania a paddingu */}
<div className="bg-gray-50 p-2.5 rounded-xl border border-gray-200 space-y-2">
{/* Šírka */}
<div>
<div className="flex justify-between items-center text-[10px] text-gray-600 mb-0.5">
<span className="font-semibold">Veľkosť cenovky (Šírka)</span>
<span className="font-bold text-gray-800 bg-white px-1.5 py-0.2 rounded border border-gray-200">
{p.badgeWidth || (p.badgeCustomTemplate ? 110 : 95)} px
</span>
</div>
<input
type="range"
min="70"
max="180"
step="2"
value={p.badgeWidth || (p.badgeCustomTemplate ? 110 : 95)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgeWidth = Number(e.target.value);
updateData({ products });
}}
className="w-full accent-red-600"
/>
</div>
{/* Zarovnanie */}
<div>
<span className="text-[10px] text-gray-600 font-semibold block mb-1">Zarovnanie</span>
<div className="grid grid-cols-3 gap-1 bg-gray-200 p-0.5 rounded-lg text-[10px] font-bold text-center">
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'left';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'left'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Vľavo
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'center';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'center'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Na stred
</button>
<button
type="button"
onClick={() => {
const products = [...module.data.products];
products[pIdx].badgeAlign = 'right';
updateData({ products });
}}
className={`py-1 rounded transition-all ${
(p.badgeAlign || 'center') === 'right'
? 'bg-white text-gray-900 shadow-xs'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Vpravo
</button>
</div>
</div>
{/* Padding zhora a zdola */}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[9px] text-gray-600 font-semibold block">Odsadenie zhora (px)</label>
<input
type="number"
min="0"
max="40"
value={p.badgePaddingTop ?? (p.badgeCustomTemplate?.paddingTop ?? 4)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgePaddingTop = Number(e.target.value);
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1 bg-white font-medium"
/>
</div>
<div>
<label className="text-[9px] text-gray-600 font-semibold block">Odsadenie zdola (px)</label>
<input
type="number"
min="0"
max="40"
value={p.badgePaddingBottom ?? (p.badgeCustomTemplate?.paddingBottom ?? 4)}
onChange={(e) => {
const products = [...module.data.products];
products[pIdx].badgePaddingBottom = Number(e.target.value);
updateData({ products });
}}
className="w-full text-xs border border-gray-300 rounded-lg p-1 bg-white font-medium"
/>
</div>
</div>
</div>
<button
onClick={() => onOpenBadgeManager && onOpenBadgeManager(p.id)}
className="text-xs font-bold text-red-600 hover:text-white hover:bg-red-600 border border-red-200 px-2.5 py-1 rounded-lg transition-all"
>
{p.customBadgePngUrl ? 'Upraviť v editore' : 'Otvoriť Editor cenoviek'}
</button>
</div>
</div>
))}