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

+73 -1
View File
@@ -5,6 +5,7 @@ import { NewsletterCampaign, NewsletterModule, ProductItem, BadgeTemplate } from
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';
@@ -26,6 +27,64 @@ export default function Home() {
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';
@@ -475,7 +534,12 @@ export default function Home() {
return {
...p,
badgeId: badge.id,
customBadgePngUrl: renderedPngUrl || p.customBadgePngUrl
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;
@@ -488,6 +552,13 @@ export default function Home() {
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];
});
};
const topCount = campaign.modules.filter((m) => isTopModule(m)).length;
@@ -651,6 +722,7 @@ export default function Home() {
<ConfigPanel
module={selectedModule}
onUpdateModule={handleUpdateModule}
availableBadges={availableBadges}
onOpenBadgeManager={(pId) => {
setTargetProductIdForBadge(pId);
setIsBadgeEditorOpen(true);