feat: zmena farby pozadia produktov a editovatelne nazvy modulov

This commit is contained in:
Jakub_Hostacny committed 2026-10-08 09:49:33 +02:00
1 parent 4e4e5e5af3
commit 00d681bec2
7 files changed
+325 -87

No files matched your search

+209 -7
View File
@@ -2,10 +2,19 @@
import React from 'react';
import { NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter';
import { Plus, Trash2, Image as ImageIcon, Tag, Store, Globe } from 'lucide-react';
import { Plus, Trash2, Image as ImageIcon, Tag, Store, Globe, Palette, Check } from 'lucide-react';
import { SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
import { ImageUploadInput } from './ImageUploadInput';
const PRODUCT_BG_PRESETS = [
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
{ label: 'Žltá', sub: 'Domáce', color: '#fef0c7', border: '#e6d49a' },
{ label: 'Hnedá', sub: 'Gril', color: '#dcb085', border: '#c49a6c' },
{ label: 'Sivá', sub: 'Neutrálna', color: '#e6e1d9', border: '#d5cfc5' },
{ label: 'Zelená', sub: 'Bio', color: '#e8f5e9', border: '#c8e6c9' },
{ label: 'Biela', sub: 'Čistá', color: '#ffffff', border: '#e5e7eb' },
];
interface ConfigPanelProps {
module: NewsletterModule | null;
onUpdateModule: (updated: NewsletterModule) => void;
@@ -40,11 +49,20 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
return (
<div className="h-full overflow-y-auto p-5 space-y-6 bg-white border-l border-gray-200">
<div className="border-b border-gray-100 pb-3">
<span className="text-[10px] font-bold uppercase tracking-wider text-red-600 bg-red-50 px-2 py-0.5 rounded">
{module.type}
</span>
<h2 className="text-base font-bold text-gray-900 mt-1">{module.title}</h2>
<div className="border-b border-gray-100 pb-3 space-y-1.5">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-wider text-red-600 bg-red-50 px-2 py-0.5 rounded">
{module.type}
</span>
<span className="text-[10px] text-gray-400 font-medium">Názov modulu v editore</span>
</div>
<input
type="text"
value={module.title}
onChange={(e) => onUpdateModule({ ...module, title: e.target.value })}
placeholder="Názov modulu..."
className="w-full text-base font-bold text-gray-900 border border-gray-200 hover:border-gray-300 focus:border-red-500 rounded-lg px-2.5 py-1.5 bg-gray-50/50 hover:bg-white focus:bg-white focus:outline-none focus:ring-1 focus:ring-red-500 transition-all"
/>
</div>
{/* TOP ANNOUNCEMENT */}
@@ -307,6 +325,99 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
/>
</div>
{/* Farba pozadia skupiny produktov */}
<div className="bg-gray-50 p-3.5 rounded-xl border border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Palette className="w-3.5 h-3.5 text-gray-700" />
<label className="text-xs font-bold text-gray-800">Farba pozadia produktov</label>
</div>
<span className="text-[10px] font-mono font-bold px-1.5 py-0.5 bg-white border border-gray-200 rounded text-gray-700 uppercase">
{module.data.cardBgColor || '#feeaea'}
</span>
</div>
{/* Rýchle predvoľby */}
<div className="grid grid-cols-3 gap-1.5">
{PRODUCT_BG_PRESETS.map((preset) => {
const currentBg = (module.data.cardBgColor || '#feeaea').toLowerCase();
const isSelected = currentBg === preset.color.toLowerCase();
return (
<button
key={preset.color}
type="button"
onClick={() => {
const newColor = preset.color;
const newBorder = preset.border;
const updatedProducts = (module.data.products || []).map((p: ProductItem) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
cardBorderColor: newBorder,
products: updatedProducts,
});
}}
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg border text-[11px] font-medium transition-all text-left ${
isSelected
? 'border-red-600 bg-white shadow-xs ring-1 ring-red-500 font-bold'
: 'border-gray-200 bg-white hover:border-gray-300 text-gray-700'
}`}
>
<span
className="w-3.5 h-3.5 rounded-full border border-black/15 shrink-0 flex items-center justify-center"
style={{ backgroundColor: preset.color }}
>
{isSelected && <Check className="w-2.5 h-2.5 text-gray-800" />}
</span>
<span className="truncate">{preset.label}</span>
</button>
);
})}
</div>
{/* Vlastná farba (Color Picker + HEX input) */}
<div className="flex items-center gap-2 pt-2 border-t border-gray-200">
<span className="text-[10px] text-gray-500 font-semibold shrink-0">Vlastná:</span>
<div className="flex items-center gap-1.5 flex-1">
<input
type="color"
value={module.data.cardBgColor || '#feeaea'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: ProductItem) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
className="w-7 h-7 rounded border border-gray-300 cursor-pointer p-0.5 bg-white shrink-0"
/>
<input
type="text"
value={module.data.cardBgColor || '#feeaea'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: ProductItem) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
placeholder="#feeaea"
className="w-full text-xs font-mono border border-gray-300 rounded px-2 py-1 bg-white uppercase text-gray-800 font-bold"
/>
</div>
</div>
</div>
{/* Zoznam produktov */}
<div className="space-y-4 pt-2">
<div className="flex items-center justify-between">
@@ -324,7 +435,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
unitPrice: '1kg = 4,99 €',
discountText: '20%',
badgeText: 'zľava',
cardBgColor: '#feeaea',
cardBgColor: module.data.cardBgColor || '#feeaea',
stores: { potraviny: true, supermarket: true, tempo: true }
};
updateData({ products: [...(module.data.products || []), newP] });
@@ -791,6 +902,97 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
/>
</div>
{/* Farba pozadia vernostných kariet */}
<div className="bg-gray-50 p-3.5 rounded-xl border border-gray-200 space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Palette className="w-3.5 h-3.5 text-gray-700" />
<label className="text-xs font-bold text-gray-800">Farba pozadia produktov</label>
</div>
<span className="text-[10px] font-mono font-bold px-1.5 py-0.5 bg-white border border-gray-200 rounded text-gray-700 uppercase">
{module.data.cardBgColor || '#e6e1d9'}
</span>
</div>
{/* Rýchle predvoľby */}
<div className="grid grid-cols-3 gap-1.5">
{PRODUCT_BG_PRESETS.map((preset) => {
const currentBg = (module.data.cardBgColor || '#e6e1d9').toLowerCase();
const isSelected = currentBg === preset.color.toLowerCase();
return (
<button
key={preset.color}
type="button"
onClick={() => {
const newColor = preset.color;
const updatedProducts = (module.data.products || []).map((p: any) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg border text-[11px] font-medium transition-all text-left ${
isSelected
? 'border-red-600 bg-white shadow-xs ring-1 ring-red-500 font-bold'
: 'border-gray-200 bg-white hover:border-gray-300 text-gray-700'
}`}
>
<span
className="w-3.5 h-3.5 rounded-full border border-black/15 shrink-0 flex items-center justify-center"
style={{ backgroundColor: preset.color }}
>
{isSelected && <Check className="w-2.5 h-2.5 text-gray-800" />}
</span>
<span className="truncate">{preset.label}</span>
</button>
);
})}
</div>
{/* Vlastná farba (Color Picker + HEX input) */}
<div className="flex items-center gap-2 pt-2 border-t border-gray-200">
<span className="text-[10px] text-gray-500 font-semibold shrink-0">Vlastná:</span>
<div className="flex items-center gap-1.5 flex-1">
<input
type="color"
value={module.data.cardBgColor || '#e6e1d9'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: any) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
className="w-7 h-7 rounded border border-gray-300 cursor-pointer p-0.5 bg-white shrink-0"
/>
<input
type="text"
value={module.data.cardBgColor || '#e6e1d9'}
onChange={(e) => {
const newColor = e.target.value;
const updatedProducts = (module.data.products || []).map((p: any) => ({
...p,
cardBgColor: newColor,
}));
updateData({
cardBgColor: newColor,
products: updatedProducts,
});
}}
placeholder="#e6e1d9"
className="w-full text-xs font-mono border border-gray-300 rounded px-2 py-1 bg-white uppercase text-gray-800 font-bold"
/>
</div>
</div>
</div>
{/* 2 Vernostné produkty */}
<div className="space-y-4 pt-2">
<span className="text-xs font-bold text-gray-800 uppercase tracking-wider block">
+13 -2
View File
@@ -212,9 +212,9 @@ export const TopBar: React.FC<TopBarProps> = ({
) : isVerifiedInBrevo && campaign.brevoCampaignId ? (
/* Kampaň je overená priamo v Brevo: Ponúkame úpravu, aktualizáciu alebo odoslanie ako novú */
<div className="flex items-center gap-1.5 bg-emerald-50 border border-emerald-200 p-1 rounded-xl">
{/* Odkaz na úpravu v Brevo */}
{/* Odkaz na zoznam / úpravu v Brevo */}
<a
href="https://app.brevo.com/campaigns"
href={brevoCampaignUrl || brevoListingUrl || 'https://app.brevo.com/campaigns/listing'}
target="_blank"
rel="noreferrer"
className="flex items-center gap-1 text-xs font-bold text-white bg-emerald-600 hover:bg-emerald-700 px-3 py-1.2 rounded-lg transition-all shadow-xs"
@@ -235,6 +235,17 @@ export const TopBar: React.FC<TopBarProps> = ({
<span className="hidden xl:inline">{isExportingBrevo ? 'Ukladám...' : 'Aktualizovať'}</span>
</button>
{/* Tlačidlo na odoslanie ako úplne novej kampane */}
<button
onClick={() => onExportBrevo(false)}
disabled={isExportingBrevo}
className="flex items-center gap-1 text-xs font-bold text-emerald-800 hover:bg-emerald-100 px-2 py-1.2 rounded-lg transition-all"
title="Vytvoriť a odoslať túto kampaň do Brevo ako novú kópiu"
>
<Send className="w-3 h-3 text-emerald-700" />
<span className="hidden 2xl:inline">Ako novú</span>
</button>
{/* Tlačidlo na overenie stavu */}
<button
onClick={() => verifyCampaignInBrevo()}