Files
Coop-NL/src/app/page.tsx
T
Jakub_HostacnyandClaude Opus 5.5 61c8c2fb76 feat: hromadne pouzitie cenovky aj s polohou, rovnaka vyska clankov a receptov
- tlacidla "Pouzit v celom newsletteri" a "Pouzit pre vsetky produkty v tejto sekcii" kopiruju aj polohu cenovky (pod / vedla obrazka)
- sekcia Clanky a Recepty (aj letaky) ma karty v riadku rovnako vysoke

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 09:56:22 +02:00

869 lines
33 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import React, { useState, useEffect } from 'react';
import { NewsletterCampaign, NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter';
import { DEFAULT_CAMPAIGN, SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
import { renderEmailHtml, renderSingleModuleHtml } from '@/lib/emailRenderer';
import { supabase } from '@/lib/supabase';
import { DEFAULT_BADGE_TEMPLATES } from '@/lib/badgeUtils';
import { TopBar } from '@/components/editor/TopBar';
import { ConfigPanel } from '@/components/editor/ConfigPanel';
import { ProductImportModal } from '@/components/editor/ProductImportModal';
import { BadgeEditorModal } from '@/components/editor/BadgeEditorModal';
import { SavedCampaignsModal } from '@/components/editor/SavedCampaignsModal';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import { ChevronUp, ChevronDown, Copy, Trash2, Anchor, Lock } from 'lucide-react';
export default function Home() {
const [campaign, setCampaign] = useState<NewsletterCampaign>(DEFAULT_CAMPAIGN);
const [selectedModuleId, setSelectedModuleId] = useState<string | null>('mod-products-fantasticky');
const [isSaving, setIsSaving] = useState(false);
const [isExportingBrevo, setIsExportingBrevo] = useState(false);
const [brevoUrl, setBrevoUrl] = useState<string | null>(null);
// Modály
const [isImportOpen, setIsImportOpen] = useState(false);
const [isBadgeEditorOpen, setIsBadgeEditorOpen] = useState(false);
const [isSavedCampaignsOpen, setIsSavedCampaignsOpen] = useState(false);
const [targetProductIdForBadge, setTargetProductIdForBadge] = useState<string | null>(null);
const [availableBadges, setAvailableBadges] = useState<BadgeTemplate[]>(DEFAULT_BADGE_TEMPLATES);
// Načítanie dostupných šablón cenoviek pre opakované použitie
useEffect(() => {
const fetchBadges = async () => {
let loaded: BadgeTemplate[] = [];
try {
const { data, error } = await supabase
.from('badges')
.select('*')
.order('created_at', { ascending: false });
if (data && !error && data.length > 0) {
loaded = data.map((d: any) => {
const isObj = d.layers_config && !Array.isArray(d.layers_config);
return {
id: d.id,
name: d.name,
svgRaw: d.svg_raw || '',
layersConfig: isObj ? (d.layers_config.layers || []) : (d.layers_config || []),
width: isObj ? (d.layers_config.width || 110) : 110,
align: isObj ? (d.layers_config.align || 'center') : 'center',
paddingTop: isObj ? (d.layers_config.paddingTop ?? 4) : 4,
paddingBottom: isObj ? (d.layers_config.paddingBottom ?? 4) : 4
};
});
}
} catch (e) {
console.warn('Error fetching badges in page:', e);
}
try {
const local = localStorage.getItem('coop_custom_badges');
if (local) {
const parsed = JSON.parse(local);
if (Array.isArray(parsed)) {
const existingIds = new Set(loaded.map((b) => b.id));
for (const b of parsed) {
if (!existingIds.has(b.id)) {
loaded.push(b);
existingIds.add(b.id);
}
}
}
}
} catch (e) {}
const all = [...loaded];
const existingIds = new Set(all.map((b) => b.id));
for (const preset of DEFAULT_BADGE_TEMPLATES) {
if (!existingIds.has(preset.id)) {
all.push(preset);
}
}
setAvailableBadges(all);
};
fetchBadges();
}, [isBadgeEditorOpen]);
// Pomocné funkcie pre fixné ukotvenie hlavičky, spodných letákov, receptov a pätičky
const isTopModule = (m: NewsletterModule) => m.type === 'header' || m.type === 'announcement';
const isFlyersModule = (m: NewsletterModule) =>
m.id === 'mod-flyers-row' ||
(m.type === 'articles_row' && m.title?.toLowerCase().includes('leták'));
const isRecipesModule = (m: NewsletterModule) =>
m.id === 'mod-recipes-row' ||
(m.type === 'articles_row' &&
(m.title?.toLowerCase().includes('recept') || m.title?.toLowerCase().includes('článk')));
const isFooterModule = (m: NewsletterModule) => m.type === 'footer';
const isBottomModule = (m: NewsletterModule) =>
isFlyersModule(m) || isRecipesModule(m) || isFooterModule(m);
const isAnchoredModule = (m: NewsletterModule) => isTopModule(m) || isBottomModule(m);
const enforceAnchors = (mods: NewsletterModule[]): NewsletterModule[] => {
const topMods = mods.filter((m) => isTopModule(m));
const flyerMods = mods.filter((m) => isFlyersModule(m));
const recipeMods = mods.filter((m) => isRecipesModule(m));
const footerMods = mods.filter((m) => isFooterModule(m));
const middleMods = mods.filter((m) => !isAnchoredModule(m));
// announcement je pred headerom, ak existujú oba
topMods.sort((a, b) => {
if (a.type === 'announcement' && b.type === 'header') return -1;
if (a.type === 'header' && b.type === 'announcement') return 1;
return 0;
});
return [...topMods, ...middleMods, ...flyerMods, ...recipeMods, ...footerMods];
};
// Načítanie existujúceho draftu z databázy pri štarte
useEffect(() => {
async function loadInitial() {
try {
const { data } = await supabase
.from('templates')
.select('*')
.order('updated_at', { ascending: false })
.limit(1);
if (data && data.length > 0) {
const dbItem = data[0];
if (dbItem.data && dbItem.data.modules && dbItem.data.modules.length >= 7) {
setCampaign({
id: dbItem.id,
title: dbItem.title,
preheader: dbItem.data.preheader || DEFAULT_CAMPAIGN.preheader,
status: dbItem.status,
brevoCampaignId: dbItem.brevo_campaign_id,
modules: enforceAnchors(dbItem.data.modules)
});
if (dbItem.brevo_campaign_id) {
setBrevoUrl('https://app.brevo.com/campaigns/listing');
}
}
}
} catch (err) {
console.error('Error loading template from Supabase:', err);
}
}
loadInitial();
}, []);
// Auto-save s debounce (1 sekunda)
useEffect(() => {
const timer = setTimeout(async () => {
setIsSaving(true);
try {
if (!campaign.id) {
const { data } = await supabase
.from('templates')
.insert([
{
title: campaign.title,
status: campaign.status,
data: {
preheader: campaign.preheader,
modules: campaign.modules
}
}
])
.select();
if (data && data.length > 0) {
setCampaign((prev) => ({ ...prev, id: data[0].id }));
}
} else {
await supabase
.from('templates')
.update({
title: campaign.title,
data: {
preheader: campaign.preheader,
modules: campaign.modules
},
updated_at: new Date().toISOString()
})
.eq('id', campaign.id);
}
} catch (err) {
console.error('Auto-save error:', err);
} finally {
setIsSaving(false);
}
}, 1000);
return () => clearTimeout(timer);
}, [campaign.title, campaign.preheader, campaign.modules, campaign.id]);
const selectedModule = campaign.modules.find((m) => m.id === selectedModuleId) || null;
const handleUpdateModule = (updated: NewsletterModule) => {
setCampaign((prev) => ({
...prev,
modules: prev.modules.map((m) => (m.id === updated.id ? updated : m))
}));
};
// Posun modulu: Hlavička, letáky, recepty a pätička sú ukotvené a bežné moduly sa nemôžu dostať mimo vyhradený stred
const moveModule = (index: number, direction: 'up' | 'down') => {
const currentMod = campaign.modules[index];
if (!currentMod || isAnchoredModule(currentMod)) return;
const topCount = campaign.modules.filter((m) => isTopModule(m)).length;
const firstBottomIndex = campaign.modules.findIndex((m) => isBottomModule(m));
const maxAllowedIndex = firstBottomIndex !== -1 ? firstBottomIndex - 1 : campaign.modules.length - 1;
const targetIndex = direction === 'up' ? index - 1 : index + 1;
if (targetIndex < topCount || targetIndex > maxAllowedIndex) return;
const newModules = [...campaign.modules];
const temp = newModules[index];
newModules[index] = newModules[targetIndex];
newModules[targetIndex] = temp;
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
};
// Duplikácia: Ukotvené moduly sa nedajú duplikovať
const duplicateModule = (index: number) => {
const original = campaign.modules[index];
if (isAnchoredModule(original)) return;
const clone: NewsletterModule = {
...original,
id: `mod-${Date.now()}`,
title: `${original.title} (kópia)`,
data: JSON.parse(JSON.stringify(original.data))
};
const newModules = [...campaign.modules];
newModules.splice(index + 1, 0, clone);
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
setSelectedModuleId(clone.id);
};
// Zmazanie: Ukotvené moduly (hlavička, letáky, články/recepty, pätička) sa nedajú vymazať
const deleteModule = (index: number) => {
const mod = campaign.modules[index];
if (isAnchoredModule(mod)) {
alert('Hlavičku, letáky, články/recepty ani pätičku nie je možné vymazať.');
return;
}
if (campaign.modules.length <= 1) return;
const newModules = campaign.modules.filter((_, i) => i !== index);
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
setSelectedModuleId(newModules[0]?.id || null);
};
// Pridanie modulu: Vloží sa vždy PRED spodné bloky (letáky, recepty a pätičku)
const handleAddModule = (type: any) => {
let newMod: NewsletterModule;
if (type === 'banner') {
newMod = {
id: `mod-banner-${Date.now()}`,
type: 'banner',
title: 'Nový Banner',
data: {
imageUrl: `${SUPABASE_ASSETS_BASE}aktualne_letaky.jpg`,
linkUrl: '#',
altText: 'Aktuálne letáky',
title: 'Prelistujte si naše najnovšie letáky online',
description: 'Všetky akcie a zľavy na jednom mieste priamo vo vašom mobile.',
buttonText: 'Pozrieť letáky',
buttonUrl: '#',
buttonBgColor: '#e2001a'
}
};
} else {
newMod = {
id: `mod-products-${Date.now()}`,
type: 'products_grid',
title: 'Nová sekcia produktov',
data: {
sectionTitle: 'AKČNÁ PONUKA',
globalValidity: '2. 5. – 15. 5. 2026',
cardBgColor: '#feeaea',
cardBorderColor: '#f5d4d4',
badgeHeaderBg: '#fefefe',
badgeHeaderColor: '#000000',
badgePriceBg: '#e2001a',
badgePriceColor: '#fefefe',
products: [
{
id: `p-${Date.now()}-1`,
title: 'Nový produkt 1',
imageUrl: `${SUPABASE_ASSETS_BASE}produkt_5.png`,
priceMain: '2',
priceSub: '49',
unitPrice: '1kg = 9,90 €',
discountText: '15%',
badgeText: 'zľava',
cardBgColor: '#feeaea',
stores: { potraviny: true, supermarket: true, tempo: true }
}
]
}
};
}
setCampaign((prev) => {
const firstBottomIdx = prev.modules.findIndex((m) => isBottomModule(m));
const newModules = [...prev.modules];
if (firstBottomIdx !== -1) {
newModules.splice(firstBottomIdx, 0, newMod);
} else {
newModules.push(newMod);
}
return { ...prev, modules: enforceAnchors(newModules) };
});
setSelectedModuleId(newMod.id);
};
const handleImportProducts = (imported: ProductItem[]) => {
if (!selectedModuleId) return;
const target = campaign.modules.find((m) => m.id === selectedModuleId);
if (!target || target.type !== 'products_grid') {
alert('Pre import prosím najskôr označte produktovú sekciu.');
return;
}
const updated = {
...target,
data: {
...target.data,
products: [...(target.data.products || []), ...imported]
}
};
handleUpdateModule(updated);
};
// Manuálne uloženie kampane
const handleManualSave = async () => {
setIsSaving(true);
try {
if (!campaign.id) {
const { data, error } = await supabase
.from('templates')
.insert([
{
title: campaign.title,
status: campaign.status,
data: {
preheader: campaign.preheader,
modules: campaign.modules
}
}
])
.select();
if (error) throw error;
if (data && data.length > 0) {
setCampaign((prev) => ({ ...prev, id: data[0].id }));
}
} else {
const { error } = await supabase
.from('templates')
.update({
title: campaign.title,
data: {
preheader: campaign.preheader,
modules: campaign.modules
},
updated_at: new Date().toISOString()
})
.eq('id', campaign.id);
if (error) throw error;
}
alert('Kampaň bola úspešne uložená do databázy!');
} catch (err: any) {
alert(`Chyba pri ukladaní: ${err.message}`);
} finally {
setIsSaving(false);
}
};
// Duplikácia aktuálnej kampane v Supabase
const handleDuplicateCurrentCampaign = async () => {
setIsSaving(true);
try {
const newTitle = `${campaign.title} (kópia)`;
const { data, error } = await supabase
.from('templates')
.insert([
{
title: newTitle,
status: 'draft',
data: {
preheader: campaign.preheader,
modules: campaign.modules
}
}
])
.select();
if (error) throw error;
if (data && data.length > 0) {
setCampaign({
id: data[0].id,
title: newTitle,
preheader: campaign.preheader,
status: 'draft',
modules: campaign.modules
});
alert(`Kampaň bola zduplikovaná ako "${newTitle}"!`);
}
} catch (err: any) {
alert(`Chyba pri duplikovaní: ${err.message}`);
} finally {
setIsSaving(false);
}
};
const handleExportZip = async () => {
const rawHtml = renderEmailHtml(campaign);
let finalHtml = rawHtml;
try {
const res = await fetch('/api/inline-css', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: rawHtml })
});
const data = await res.json();
if (data.html) finalHtml = data.html;
} catch (e) {
console.warn('Inliner fallback:', e);
}
const zip = new JSZip();
zip.file('index.html', finalHtml);
const blob = await zip.generateAsync({ type: 'blob' });
saveAs(blob, `coop-newsletter-${new Date().toISOString().split('T')[0]}.zip`);
};
const handleExportBrevo = async (updateExisting = false) => {
setIsExportingBrevo(true);
try {
const rawHtml = renderEmailHtml(campaign);
let finalHtml = rawHtml;
try {
const res = await fetch('/api/inline-css', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: rawHtml })
});
const data = await res.json();
if (data.html) finalHtml = data.html;
} catch (e) {}
const response = await fetch('/api/brevo/export', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
campaignName: `${campaign.title} - ${new Date().toLocaleDateString('sk-SK')}`,
subject: campaign.title,
htmlContent: finalHtml,
updateExisting: updateExisting,
existingCampaignId: campaign.brevoCampaignId
})
});
const resData = await response.json();
if (resData.success && resData.campaignId) {
setCampaign((prev) => ({
...prev,
brevoCampaignId: resData.campaignId,
status: 'exported'
}));
setBrevoUrl(resData.brevoUrl);
if (campaign.id) {
await supabase
.from('templates')
.update({
status: 'exported',
brevo_campaign_id: resData.campaignId
})
.eq('id', campaign.id);
}
alert(
resData.message ||
(updateExisting
? 'Kampaň bola v Brevo úspešne aktualizovaná!'
: 'Kampaň bola úspešne odoslaná do Brevo!')
);
} else {
alert(`Chyba pri exporte do Brevo: ${resData.error || 'Neznáma chyba'}`);
}
} catch (err: any) {
alert(`Chyba: ${err.message}`);
} finally {
setIsExportingBrevo(false);
}
};
const handleClearBrevoId = async () => {
setCampaign((prev) => ({ ...prev, brevoCampaignId: undefined }));
setBrevoUrl(null);
if (campaign.id) {
await supabase
.from('templates')
.update({ brevo_campaign_id: null })
.eq('id', campaign.id);
}
};
// Vybraný produkt pre editor cenoviek
let targetProduct: any = null;
if (targetProductIdForBadge && selectedModule?.data?.products) {
targetProduct = selectedModule.data.products.find((p: any) => p.id === targetProductIdForBadge) || null;
}
const handleApplyBadgeToProduct = (badge: BadgeTemplate, renderedPngUrl?: string) => {
if (!targetProductIdForBadge || !selectedModule) return;
const updatedProducts = (selectedModule.data.products || []).map((p: any) => {
if (p.id === targetProductIdForBadge) {
return {
...p,
badgeId: badge.id,
badgeCustomTemplate: badge,
badgeWidth: badge.width ?? p.badgeWidth ?? 110,
badgeAlign: badge.align ?? p.badgeAlign ?? 'center',
badgePaddingTop: badge.paddingTop ?? p.badgePaddingTop ?? 4,
badgePaddingBottom: badge.paddingBottom ?? p.badgePaddingBottom ?? 4,
customBadgePngUrl: undefined // Používame živý dynamický SVG vektor, žiadna statická fotka
};
}
return p;
});
handleUpdateModule({
...selectedModule,
data: {
...selectedModule.data,
products: updatedProducts
}
});
setAvailableBadges((prev) => {
if (prev.some((b) => b.id === badge.id)) {
return prev.map((b) => (b.id === badge.id ? badge : b));
}
return [badge, ...prev];
});
};
// Hromadná aplikácia cenovky na všetky produkty v celej kampani (všetky sekcie + vernostné zľavy)
const handleApplyBadgeToAll = (badge: BadgeTemplate, badgeLayout?: 'below' | 'side') => {
setCampaign((prev) => ({
...prev,
modules: prev.modules.map((m) => {
if (m.type === 'products_grid' || m.type === 'loyalty_club') {
return {
...m,
data: {
...m.data,
products: (m.data.products || []).map((prod: any) => ({
...prod,
badgeId: badge.id,
badgeCustomTemplate: badge,
badgeWidth: badge.width ?? prod.badgeWidth ?? 110,
badgeAlign: badge.align ?? prod.badgeAlign ?? 'center',
badgePaddingTop: badge.paddingTop ?? prod.badgePaddingTop ?? 4,
badgePaddingBottom: badge.paddingBottom ?? prod.badgePaddingBottom ?? 4,
badgeLayout: badgeLayout ?? prod.badgeLayout,
customBadgePngUrl: undefined
}))
}
};
}
return m;
})
}));
};
const topCount = campaign.modules.filter((m) => isTopModule(m)).length;
const firstBottomIdx = campaign.modules.findIndex((m) => isBottomModule(m));
return (
<div className="flex flex-col h-screen overflow-hidden bg-slate-950 font-sans">
{/* 1. Horná lišta */}
<TopBar
campaign={campaign}
isSaving={isSaving}
onSave={handleManualSave}
onUpdateTitle={(title) => setCampaign((prev) => ({ ...prev, title }))}
onOpenSavedCampaigns={() => setIsSavedCampaignsOpen(true)}
onDuplicateCampaign={handleDuplicateCurrentCampaign}
onExportZip={handleExportZip}
onExportBrevo={handleExportBrevo}
isExportingBrevo={isExportingBrevo}
brevoCampaignUrl={brevoUrl}
brevoListingUrl="https://app.brevo.com/campaigns/listing"
onClearBrevoId={handleClearBrevoId}
onAddModule={handleAddModule}
/>
{/* 2. Hlavný pracovný priestor */}
<div className="flex flex-1 overflow-hidden">
{/* Stredné plátno - Presný originálny gradient a čisté rozloženie */}
<main
className="flex-1 overflow-y-auto p-4 md:p-8 flex items-start justify-center"
style={{
backgroundColor: '#001a33',
backgroundImage: 'linear-gradient(135deg, #005bb5 0%, #000d1a 100%)'
}}
>
{/* E-mail Container 1:1 k originálu */}
<div className="w-full max-w-[600px] my-4">
{campaign.modules.map((mod, idx) => {
const isSelected = mod.id === selectedModuleId;
const isAnchored = isAnchoredModule(mod);
const isTop = isTopModule(mod);
const isFlyers = isFlyersModule(mod);
const isRecipes = isRecipesModule(mod);
const isFooter = isFooterModule(mod);
const canMoveUp = !isAnchored && idx > topCount;
const canMoveDown = !isAnchored && idx < (firstBottomIdx !== -1 ? firstBottomIdx - 1 : campaign.modules.length - 1);
return (
<div
key={mod.id}
onClick={() => setSelectedModuleId(mod.id)}
className={`relative cursor-pointer transition-all duration-150 rounded-2xl ${
isSelected
? 'ring-2 ring-offset-4 ring-offset-[#001a33] ring-red-500 shadow-2xl'
: 'hover:ring-1 hover:ring-offset-2 hover:ring-offset-[#001a33] hover:ring-white/30'
}`}
>
{/* Lišta s nástrojmi pre označený modul */}
{isSelected && (
<div className="absolute -top-3 right-4 z-40 flex items-center gap-1.5 bg-[#001a33] text-white px-3 py-1.5 rounded-full text-xs shadow-2xl border border-white/20 backdrop-blur-md">
<span className="text-[10px] font-bold text-gray-300 uppercase tracking-wider">
{mod.title}
</span>
{/* Ak je modul ukotvený (Hlavička / Letáky / Recepty / Pätička / Oznamovacia lišta) */}
{isAnchored ? (
<div className="flex items-center gap-1.5">
{mod.type === 'announcement' && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleUpdateModule({
...mod,
data: { ...mod.data, enabled: mod.data.enabled === false ? true : false }
});
}}
className={`text-[9px] font-bold px-2 py-0.5 rounded-full border transition-all flex items-center gap-1 ${
mod.data.enabled === false
? 'bg-red-500/20 text-red-300 border-red-400/30 hover:bg-red-500/30'
: 'bg-emerald-500/20 text-emerald-300 border-emerald-400/30 hover:bg-emerald-500/30'
}`}
title={mod.data.enabled === false ? 'Aktivovať lištu v e-maile' : 'Skryť lištu v e-maile'}
>
{mod.data.enabled === false ? 'Skrytá (zapnúť)' : 'Zobrazená'}
</button>
)}
<span className="text-[9px] font-bold bg-white/10 text-amber-300 px-2 py-0.5 rounded-full border border-white/10 uppercase flex items-center gap-1">
<Lock className="w-2.5 h-2.5" />
{isFooter
? 'Ukotvená pätička'
: isFlyers
? 'Ukotvené letáky'
: isRecipes
? 'Ukotvené recepty'
: mod.type === 'announcement'
? 'Oznamovacia lišta'
: 'Ukotvená hlavička'}
</span>
</div>
) : (
<>
<button
onClick={(e) => {
e.stopPropagation();
moveModule(idx, 'up');
}}
disabled={!canMoveUp}
className="hover:text-red-400 disabled:opacity-30 p-1"
title={canMoveUp ? 'Posunúť vyššie' : 'Nemožno posunúť nad hlavičku'}
>
<ChevronUp className="w-3.5 h-3.5" />
</button>
<button
onClick={(e) => {
e.stopPropagation();
moveModule(idx, 'down');
}}
disabled={!canMoveDown}
className="hover:text-red-400 disabled:opacity-30 p-1"
title={canMoveDown ? 'Posunúť nižšie' : 'Nemožno posunúť pod pätičku'}
>
<ChevronDown className="w-3.5 h-3.5" />
</button>
<button
onClick={(e) => {
e.stopPropagation();
duplicateModule(idx);
}}
className="hover:text-blue-400 p-1 ml-1"
title="Duplikovať sekciu"
>
<Copy className="w-3.5 h-3.5" />
</button>
<button
onClick={(e) => {
e.stopPropagation();
deleteModule(idx);
}}
className="hover:text-red-400 p-1 ml-1"
title="Zmazať sekciu"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</>
)}
</div>
)}
{/* Vykreslenie modulu: ak je loyalty_club alebo announcement vypnutý, zobrazíme prívetivý placeholder */}
{mod.type === 'loyalty_club' && mod.data.enabled === false ? (
<div className="p-8 my-4 border-2 border-dashed border-red-500/40 bg-red-950/30 rounded-2xl text-center">
<p className="text-xs font-bold text-red-300 uppercase tracking-wider">
Sekcia Vernostné zľavy (Vypnutá)
</p>
<p className="text-[11px] text-gray-300 mt-1">
Táto sekcia je v odosielanom e-maile skrytá. Kliknutím sem ju môžete aktivovať.
</p>
<button
onClick={(e) => {
e.stopPropagation();
handleUpdateModule({ ...mod, data: { ...mod.data, enabled: true } });
}}
className="mt-3 px-4 py-1.5 bg-red-600 hover:bg-red-700 text-white text-xs font-bold rounded-lg transition-colors shadow-sm"
>
Aktivovať vernostné zľavy
</button>
</div>
) : mod.type === 'announcement' && mod.data.enabled === false ? (
<div className="p-4 my-2 border-2 border-dashed border-red-500/40 bg-red-950/30 rounded-xl text-center">
<p className="text-xs font-bold text-red-300 uppercase tracking-wider">
Top oznamovacia lišta (Vypnutá / Skrytá)
</p>
<p className="text-[11px] text-gray-300 mt-0.5">
Táto lišta je v e-maile skrytá. Kliknutím sem ju môžete aktivovať.
</p>
<button
onClick={(e) => {
e.stopPropagation();
handleUpdateModule({ ...mod, data: { ...mod.data, enabled: true } });
}}
className="mt-2 px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-xs font-bold rounded-lg transition-colors shadow-sm"
>
Aktivovať oznamovaciu lištu
</button>
</div>
) : (
<div
dangerouslySetInnerHTML={{
__html: renderSingleModuleHtml(mod, campaign)
}}
className="pointer-events-none"
/>
)}
</div>
);
})}
</div>
</main>
{/* Pravý konfiguračný panel */}
<aside className="w-96 flex-shrink-0 h-full bg-white shadow-2xl z-20">
<ConfigPanel
module={selectedModule}
onUpdateModule={handleUpdateModule}
onApplyBadgeToAll={handleApplyBadgeToAll}
availableBadges={availableBadges}
onOpenBadgeManager={(pId) => {
setTargetProductIdForBadge(pId);
setIsBadgeEditorOpen(true);
}}
onOpenImport={() => setIsImportOpen(true)}
/>
</aside>
</div>
{/* Modál Hromadného importu */}
<ProductImportModal
isOpen={isImportOpen}
onClose={() => setIsImportOpen(false)}
onImportProducts={handleImportProducts}
/>
{/* Modál Editora cenoviek (SVG -> PNG) */}
<BadgeEditorModal
isOpen={isBadgeEditorOpen}
onClose={() => setIsBadgeEditorOpen(false)}
onSelectBadge={handleApplyBadgeToProduct}
currentDiscount={targetProduct?.discountText || targetProduct?.cardDiscount || '25%'}
currentPriceMain={targetProduct?.priceMain || targetProduct?.cardPriceMain || '1'}
currentPriceSub={targetProduct?.priceSub || targetProduct?.cardPriceSub || '99'}
currentUnitPrice={targetProduct?.unitPrice || targetProduct?.cardUnitPrice || '1kg = 4,91 €'}
/>
{/* Modál Moje newslettre (Ukladanie, duplikácia, vytváranie a prepínanie) */}
<SavedCampaignsModal
isOpen={isSavedCampaignsOpen}
onClose={() => setIsSavedCampaignsOpen(false)}
currentCampaign={campaign}
onLoadCampaign={(selected: NewsletterCampaign) => {
setCampaign({ ...selected, modules: enforceAnchors(selected.modules) });
setSelectedModuleId(selected.modules[1]?.id || selected.modules[0]?.id || null);
if (selected.brevoCampaignId) {
setBrevoUrl('https://app.brevo.com/campaigns');
} else {
setBrevoUrl(null);
}
}}
onSaveCurrentAsCopy={async (newTitle: string) => {
const { data, error } = await supabase
.from('templates')
.insert([
{
title: newTitle,
status: 'draft',
data: {
preheader: campaign.preheader,
modules: campaign.modules
}
}
])
.select();
if (error) throw error;
if (data && data[0]) {
setCampaign({
id: data[0].id,
title: newTitle,
preheader: campaign.preheader,
status: 'draft',
modules: campaign.modules
});
}
}}
/>
</div>
);
}