feat: jednoduchy HTML editor pre hlavicku a paticku (uloha 13)
Prepinac Vlastne HTML predvyplni aktualne vygenerovane HTML, ktore sa da upravit v textovom poli; nahlad aj export pouziju vlastne HTML. Tlacidlo Nacitat z formulara obnovi vygenerovane HTML, po vypnuti sa vrati formular. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3bd93fde20
commit
d72293a2e2
2 files changed
+86
-3
No files matched your search
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter';
|
import { NewsletterModule, ProductItem, BadgeTemplate } from '@/types/newsletter';
|
||||||
import { Plus, Trash2, Image as ImageIcon, Tag, Store, Globe, Palette, Check } from 'lucide-react';
|
import { Plus, Trash2, Image as ImageIcon, Tag, Store, Globe, Palette, Check, Code } from 'lucide-react';
|
||||||
import { SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
|
import { SUPABASE_ASSETS_BASE } from '@/lib/defaultCampaign';
|
||||||
import { ImageUploadInput } from './ImageUploadInput';
|
import { ImageUploadInput } from './ImageUploadInput';
|
||||||
import { pickAlt } from '@/lib/altText';
|
import { pickAlt } from '@/lib/altText';
|
||||||
|
import { renderGeneratedModuleHtml, CUSTOM_HTML_MODULE_TYPES } from '@/lib/emailRenderer';
|
||||||
|
|
||||||
const PRODUCT_BG_PRESETS = [
|
const PRODUCT_BG_PRESETS = [
|
||||||
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
|
{ label: 'Červená', sub: 'Fantasticky', color: '#feeaea', border: '#f5d4d4' },
|
||||||
@@ -26,6 +27,69 @@ interface ConfigPanelProps {
|
|||||||
availableBadges?: BadgeTemplate[];
|
availableBadges?: BadgeTemplate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Jednoduchý HTML editor: vlastné HTML nahradí modul vygenerovaný z formulára
|
||||||
|
const CustomHtmlEditor: React.FC<{
|
||||||
|
module: NewsletterModule;
|
||||||
|
updateData: (patch: Record<string, unknown>) => void;
|
||||||
|
}> = ({ module, updateData }) => {
|
||||||
|
const enabled = !!module.data.useCustomHtml;
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
if (enabled) {
|
||||||
|
// Neupravené HTML nedržíme, aby sa pri ďalšom zapnutí predvyplnilo aktuálne z formulára
|
||||||
|
const unchanged = (module.data.customHtml || '').trim() === renderGeneratedModuleHtml(module);
|
||||||
|
updateData({ useCustomHtml: false, ...(unchanged ? { customHtml: undefined } : {}) });
|
||||||
|
} else {
|
||||||
|
// Pri prvom zapnutí predvyplníme aktuálne vygenerované HTML
|
||||||
|
updateData({
|
||||||
|
useCustomHtml: true,
|
||||||
|
customHtml: module.data.customHtml || renderGeneratedModuleHtml(module)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const reloadFromForm = () => {
|
||||||
|
if (confirm('Prepísať vlastné HTML aktuálnym HTML vygenerovaným z formulára?')) {
|
||||||
|
updateData({ customHtml: renderGeneratedModuleHtml(module) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-5 p-3 rounded-xl border border-gray-200 bg-gray-50 space-y-2">
|
||||||
|
<label className="flex items-center justify-between gap-2 cursor-pointer">
|
||||||
|
<span className="text-xs font-bold text-gray-800 flex items-center gap-1.5">
|
||||||
|
<Code className="w-3.5 h-3.5 text-gray-600" /> Vlastné HTML
|
||||||
|
</span>
|
||||||
|
<input type="checkbox" checked={enabled} onChange={toggle} className="w-4 h-4 accent-red-600" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{enabled ? (
|
||||||
|
<>
|
||||||
|
<textarea
|
||||||
|
value={module.data.customHtml || ''}
|
||||||
|
onChange={(e) => updateData({ customHtml: e.target.value })}
|
||||||
|
spellCheck={false}
|
||||||
|
className="w-full h-96 text-[11px] leading-snug font-mono border border-gray-300 rounded-lg p-2 bg-white whitespace-pre"
|
||||||
|
/>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<p className="text-[10px] text-gray-500">Náhľad sa aktualizuje priebežne. Používajte tabuľky a inline štýly.</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={reloadFromForm}
|
||||||
|
className="shrink-0 text-[10px] font-bold text-gray-700 bg-white hover:bg-gray-100 border border-gray-300 px-2 py-1 rounded-lg"
|
||||||
|
title="Nahradiť obsah HTML vygenerovaným z formulára"
|
||||||
|
>
|
||||||
|
Načítať z formulára
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="text-[10px] text-gray-500">Zapnite pre úpravu HTML kódu. Formulár ostane uložený a po vypnutí sa vráti.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Nahratie celej cenovky ako PNG pre výnimočný produkt (prekryje šablónu)
|
// Nahratie celej cenovky ako PNG pre výnimočný produkt (prekryje šablónu)
|
||||||
const CustomBadgePngInput: React.FC<{ value?: string; onChange: (url: string) => void }> = ({ value, onChange }) => (
|
const CustomBadgePngInput: React.FC<{ value?: string; onChange: (url: string) => void }> = ({ value, onChange }) => (
|
||||||
<div className="p-2.5 rounded-xl border border-dashed border-amber-300 bg-amber-50/60 space-y-1.5">
|
<div className="p-2.5 rounded-xl border border-dashed border-amber-300 bg-amber-50/60 space-y-1.5">
|
||||||
@@ -150,8 +214,13 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Vlastné HTML pre hlavičku a pätičku */}
|
||||||
|
{CUSTOM_HTML_MODULE_TYPES.includes(module.type) && (
|
||||||
|
<CustomHtmlEditor module={module} updateData={updateData} />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* HEADER */}
|
{/* HEADER */}
|
||||||
{module.type === 'header' && (() => {
|
{module.type === 'header' && !module.data.useCustomHtml && (() => {
|
||||||
const navLinks = module.data.navLinks || [
|
const navLinks = module.data.navLinks || [
|
||||||
{ label: 'Letáky', url: 'https://www.coop.sk/sk/letaky', variant: 'outline' },
|
{ label: 'Letáky', url: 'https://www.coop.sk/sk/letaky', variant: 'outline' },
|
||||||
{ label: 'COOP Klub', url: 'https://www.coopklub.sk', variant: 'filled' }
|
{ label: 'COOP Klub', url: 'https://www.coopklub.sk', variant: 'filled' }
|
||||||
@@ -1861,7 +1930,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* FOOTER */}
|
{/* FOOTER */}
|
||||||
{module.type === 'footer' && (
|
{module.type === 'footer' && !module.data.useCustomHtml && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold text-gray-700 mb-1">Právny text (Disclaimer)</label>
|
<label className="block text-xs font-semibold text-gray-700 mb-1">Právny text (Disclaimer)</label>
|
||||||
|
|||||||
@@ -203,7 +203,21 @@ export function renderSingleModuleHtml(m: any, campaign: NewsletterCampaign): st
|
|||||||
return renderModule(m, campaign);
|
return renderModule(m, campaign);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moduly, pri ktorých sa dá vygenerované HTML nahradiť vlastným (HTML editor)
|
||||||
|
export const CUSTOM_HTML_MODULE_TYPES = ['header', 'footer'];
|
||||||
|
|
||||||
|
// HTML vygenerované z formulára (ignoruje vlastné HTML) – predvyplnenie HTML editora
|
||||||
|
export function renderGeneratedModuleHtml(m: any): string {
|
||||||
|
// Hlavička ani pätička nepoužívajú údaje kampane, stačí prázdny objekt
|
||||||
|
return renderModule({ ...m, data: { ...m.data, useCustomHtml: false } }, {} as NewsletterCampaign).trim();
|
||||||
|
}
|
||||||
|
|
||||||
function renderModule(m: any, campaign: NewsletterCampaign): string {
|
function renderModule(m: any, campaign: NewsletterCampaign): string {
|
||||||
|
// Vlastné HTML hlavičky / pätičky má prednosť pred vygenerovaným
|
||||||
|
if (CUSTOM_HTML_MODULE_TYPES.includes(m.type) && m.data?.useCustomHtml && m.data?.customHtml) {
|
||||||
|
return m.data.customHtml;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m.type) {
|
switch (m.type) {
|
||||||
case 'announcement':
|
case 'announcement':
|
||||||
if (m.data?.enabled === false) {
|
if (m.data?.enabled === false) {
|
||||||
|
|||||||
Reference in new issue
Block a user