feat: drag and drop text layers in badge editor, anchor flyers/recipes above footer, and editable titles in flyers section
This commit is contained in:
1 parent
99dd0f272e
commit
f9aa2297ba
3 files changed
+205
-43
No files matched your search
+56
-29
@@ -27,15 +27,31 @@ export default function Home() {
|
|||||||
const [isSavedCampaignsOpen, setIsSavedCampaignsOpen] = useState(false);
|
const [isSavedCampaignsOpen, setIsSavedCampaignsOpen] = useState(false);
|
||||||
const [targetProductIdForBadge, setTargetProductIdForBadge] = useState<string | null>(null);
|
const [targetProductIdForBadge, setTargetProductIdForBadge] = useState<string | null>(null);
|
||||||
|
|
||||||
// Pomocné funkcie pre fixné ukotvenie hlavičky a pätičky
|
// Pomocné funkcie pre fixné ukotvenie hlavičky, spodných letákov, receptov a pätičky
|
||||||
const isTopModule = (type: string) => type === 'header' || type === 'announcement';
|
const isTopModule = (m: NewsletterModule) => m.type === 'header' || m.type === 'announcement';
|
||||||
const isBottomModule = (type: string) => type === 'footer';
|
|
||||||
const isAnchoredModule = (type: string) => isTopModule(type) || isBottomModule(type);
|
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 enforceAnchors = (mods: NewsletterModule[]): NewsletterModule[] => {
|
||||||
const topMods = mods.filter((m) => isTopModule(m.type));
|
const topMods = mods.filter((m) => isTopModule(m));
|
||||||
const bottomMods = mods.filter((m) => isBottomModule(m.type));
|
const flyerMods = mods.filter((m) => isFlyersModule(m));
|
||||||
const middleMods = mods.filter((m) => !isAnchoredModule(m.type));
|
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
|
// announcement je pred headerom, ak existujú oba
|
||||||
topMods.sort((a, b) => {
|
topMods.sort((a, b) => {
|
||||||
@@ -44,7 +60,7 @@ export default function Home() {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return [...topMods, ...middleMods, ...bottomMods];
|
return [...topMods, ...middleMods, ...flyerMods, ...recipeMods, ...footerMods];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Načítanie existujúceho draftu z databázy pri štarte
|
// Načítanie existujúceho draftu z databázy pri štarte
|
||||||
@@ -135,14 +151,14 @@ export default function Home() {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Posun modulu: Hlavička a pätička sú ukotvené a bežné moduly sa nemôžu dostať pred hlavičku ani za pätičku
|
// 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 moveModule = (index: number, direction: 'up' | 'down') => {
|
||||||
const currentMod = campaign.modules[index];
|
const currentMod = campaign.modules[index];
|
||||||
if (!currentMod || isAnchoredModule(currentMod.type)) return;
|
if (!currentMod || isAnchoredModule(currentMod)) return;
|
||||||
|
|
||||||
const topCount = campaign.modules.filter((m) => isTopModule(m.type)).length;
|
const topCount = campaign.modules.filter((m) => isTopModule(m)).length;
|
||||||
const footerIndex = campaign.modules.findIndex((m) => isBottomModule(m.type));
|
const firstBottomIndex = campaign.modules.findIndex((m) => isBottomModule(m));
|
||||||
const maxAllowedIndex = footerIndex !== -1 ? footerIndex - 1 : campaign.modules.length - 1;
|
const maxAllowedIndex = firstBottomIndex !== -1 ? firstBottomIndex - 1 : campaign.modules.length - 1;
|
||||||
|
|
||||||
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
const targetIndex = direction === 'up' ? index - 1 : index + 1;
|
||||||
if (targetIndex < topCount || targetIndex > maxAllowedIndex) return;
|
if (targetIndex < topCount || targetIndex > maxAllowedIndex) return;
|
||||||
@@ -155,10 +171,10 @@ export default function Home() {
|
|||||||
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
|
setCampaign((prev) => ({ ...prev, modules: enforceAnchors(newModules) }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Duplikácia: Hlavička a pätička sa nedajú duplikovať
|
// Duplikácia: Ukotvené moduly sa nedajú duplikovať
|
||||||
const duplicateModule = (index: number) => {
|
const duplicateModule = (index: number) => {
|
||||||
const original = campaign.modules[index];
|
const original = campaign.modules[index];
|
||||||
if (isAnchoredModule(original.type)) return;
|
if (isAnchoredModule(original)) return;
|
||||||
|
|
||||||
const clone: NewsletterModule = {
|
const clone: NewsletterModule = {
|
||||||
...original,
|
...original,
|
||||||
@@ -173,10 +189,13 @@ export default function Home() {
|
|||||||
setSelectedModuleId(clone.id);
|
setSelectedModuleId(clone.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zmazanie: Hlavička a pätička sa nedajú vymazať
|
// Zmazanie: Ukotvené moduly (hlavička, letáky, články/recepty, pätička) sa nedajú vymazať
|
||||||
const deleteModule = (index: number) => {
|
const deleteModule = (index: number) => {
|
||||||
const mod = campaign.modules[index];
|
const mod = campaign.modules[index];
|
||||||
if (isAnchoredModule(mod.type)) return;
|
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;
|
if (campaign.modules.length <= 1) return;
|
||||||
|
|
||||||
const newModules = campaign.modules.filter((_, i) => i !== index);
|
const newModules = campaign.modules.filter((_, i) => i !== index);
|
||||||
@@ -184,7 +203,7 @@ export default function Home() {
|
|||||||
setSelectedModuleId(newModules[0]?.id || null);
|
setSelectedModuleId(newModules[0]?.id || null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pridanie modulu: Vloží sa vždy PRED pätičku (pätička ostáva pevne na spodku)
|
// Pridanie modulu: Vloží sa vždy PRED spodné bloky (letáky, recepty a pätičku)
|
||||||
const handleAddModule = (type: any) => {
|
const handleAddModule = (type: any) => {
|
||||||
let newMod: NewsletterModule;
|
let newMod: NewsletterModule;
|
||||||
if (type === 'banner') {
|
if (type === 'banner') {
|
||||||
@@ -236,10 +255,10 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setCampaign((prev) => {
|
setCampaign((prev) => {
|
||||||
const footerIdx = prev.modules.findIndex((m) => isBottomModule(m.type));
|
const firstBottomIdx = prev.modules.findIndex((m) => isBottomModule(m));
|
||||||
const newModules = [...prev.modules];
|
const newModules = [...prev.modules];
|
||||||
if (footerIdx !== -1) {
|
if (firstBottomIdx !== -1) {
|
||||||
newModules.splice(footerIdx, 0, newMod);
|
newModules.splice(firstBottomIdx, 0, newMod);
|
||||||
} else {
|
} else {
|
||||||
newModules.push(newMod);
|
newModules.push(newMod);
|
||||||
}
|
}
|
||||||
@@ -471,8 +490,8 @@ export default function Home() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const topCount = campaign.modules.filter((m) => isTopModule(m.type)).length;
|
const topCount = campaign.modules.filter((m) => isTopModule(m)).length;
|
||||||
const footerIdx = campaign.modules.findIndex((m) => isBottomModule(m.type));
|
const firstBottomIdx = campaign.modules.findIndex((m) => isBottomModule(m));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen overflow-hidden bg-slate-950 font-sans">
|
<div className="flex flex-col h-screen overflow-hidden bg-slate-950 font-sans">
|
||||||
@@ -507,12 +526,14 @@ export default function Home() {
|
|||||||
<div className="w-full max-w-[600px] my-4">
|
<div className="w-full max-w-[600px] my-4">
|
||||||
{campaign.modules.map((mod, idx) => {
|
{campaign.modules.map((mod, idx) => {
|
||||||
const isSelected = mod.id === selectedModuleId;
|
const isSelected = mod.id === selectedModuleId;
|
||||||
const isAnchored = isAnchoredModule(mod.type);
|
const isAnchored = isAnchoredModule(mod);
|
||||||
const isTop = isTopModule(mod.type);
|
const isTop = isTopModule(mod);
|
||||||
const isBottom = isBottomModule(mod.type);
|
const isFlyers = isFlyersModule(mod);
|
||||||
|
const isRecipes = isRecipesModule(mod);
|
||||||
|
const isFooter = isFooterModule(mod);
|
||||||
|
|
||||||
const canMoveUp = !isAnchored && idx > topCount;
|
const canMoveUp = !isAnchored && idx > topCount;
|
||||||
const canMoveDown = !isAnchored && idx < (footerIdx !== -1 ? footerIdx - 1 : campaign.modules.length - 1);
|
const canMoveDown = !isAnchored && idx < (firstBottomIdx !== -1 ? firstBottomIdx - 1 : campaign.modules.length - 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -531,11 +552,17 @@ export default function Home() {
|
|||||||
{mod.title}
|
{mod.title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Ak je modul ukotvený (Hlavička / Pätička), nezobrazujeme tlačidlá na presun a zmazanie */}
|
{/* Ak je modul ukotvený (Hlavička / Letáky / Recepty / Pätička), nezobrazujeme tlačidlá na presun a zmazanie */}
|
||||||
{isAnchored ? (
|
{isAnchored ? (
|
||||||
<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">
|
<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" />
|
<Lock className="w-2.5 h-2.5" />
|
||||||
{isBottom ? 'Ukotvená pätička' : 'Ukotvená hlavička'}
|
{isFooter
|
||||||
|
? 'Ukotvená pätička'
|
||||||
|
: isFlyers
|
||||||
|
? 'Ukotvené letáky'
|
||||||
|
: isRecipes
|
||||||
|
? 'Ukotvené recepty'
|
||||||
|
: 'Ukotvená hlavička'}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React, { useState, useRef, useEffect } from 'react';
|
|||||||
import { BadgeTemplate, BadgeLayer } from '@/types/newsletter';
|
import { BadgeTemplate, BadgeLayer } from '@/types/newsletter';
|
||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
import { toPng } from 'html-to-image';
|
import { toPng } from 'html-to-image';
|
||||||
import { Tag, Plus, Check, Trash2, X, Upload, Eye, FileCode } from 'lucide-react';
|
import { Tag, Plus, Check, Trash2, X, Upload, Eye, FileCode, Move } from 'lucide-react';
|
||||||
|
|
||||||
interface BadgeEditorModalProps {
|
interface BadgeEditorModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -72,6 +72,98 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
const [isExportingPng, setIsExportingPng] = useState(false);
|
const [isExportingPng, setIsExportingPng] = useState(false);
|
||||||
const [isSavingDb, setIsSavingDb] = useState(false);
|
const [isSavingDb, setIsSavingDb] = useState(false);
|
||||||
|
|
||||||
|
// Stav pre priamy Drag & Drop vrstiev na plátne
|
||||||
|
const [dragState, setDragState] = useState<{
|
||||||
|
layerId: string;
|
||||||
|
startX: number;
|
||||||
|
startY: number;
|
||||||
|
layerStartX: number;
|
||||||
|
layerStartY: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleLayerMouseDown = (e: React.MouseEvent, layer: BadgeLayer) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
setSelectedLayerId(layer.id);
|
||||||
|
setDragState({
|
||||||
|
layerId: layer.id,
|
||||||
|
startX: e.clientX,
|
||||||
|
startY: e.clientY,
|
||||||
|
layerStartX: layer.x,
|
||||||
|
layerStartY: layer.y
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLayerTouchStart = (e: React.TouchEvent, layer: BadgeLayer) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const touch = e.touches[0];
|
||||||
|
if (!touch) return;
|
||||||
|
setSelectedLayerId(layer.id);
|
||||||
|
setDragState({
|
||||||
|
layerId: layer.id,
|
||||||
|
startX: touch.clientX,
|
||||||
|
startY: touch.clientY,
|
||||||
|
layerStartX: layer.x,
|
||||||
|
layerStartY: layer.y
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dragState) return;
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
const deltaX = e.clientX - dragState.startX;
|
||||||
|
const deltaY = e.clientY - dragState.startY;
|
||||||
|
const newX = Math.round(dragState.layerStartX + deltaX);
|
||||||
|
const newY = Math.round(dragState.layerStartY + deltaY);
|
||||||
|
|
||||||
|
setActiveBadge((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
layersConfig: prev.layersConfig.map((l) =>
|
||||||
|
l.id === dragState.layerId ? { ...l, x: newX, y: newY } : l
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchMove = (e: TouchEvent) => {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
if (!touch) return;
|
||||||
|
const deltaX = touch.clientX - dragState.startX;
|
||||||
|
const deltaY = touch.clientY - dragState.startY;
|
||||||
|
const newX = Math.round(dragState.layerStartX + deltaX);
|
||||||
|
const newY = Math.round(dragState.layerStartY + deltaY);
|
||||||
|
|
||||||
|
setActiveBadge((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
layersConfig: prev.layersConfig.map((l) =>
|
||||||
|
l.id === dragState.layerId ? { ...l, x: newX, y: newY } : l
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
setDragState(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', handleMouseMove);
|
||||||
|
window.addEventListener('mouseup', handleDragEnd);
|
||||||
|
window.addEventListener('touchmove', handleTouchMove);
|
||||||
|
window.addEventListener('touchend', handleDragEnd);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
window.removeEventListener('mouseup', handleDragEnd);
|
||||||
|
window.removeEventListener('touchmove', handleTouchMove);
|
||||||
|
window.removeEventListener('touchend', handleDragEnd);
|
||||||
|
};
|
||||||
|
}, [dragState]);
|
||||||
|
|
||||||
// Načítanie písiem pre všetky vrstvy aktívnej cenovky
|
// Načítanie písiem pre všetky vrstvy aktívnej cenovky
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeBadge?.layersConfig) {
|
if (activeBadge?.layersConfig) {
|
||||||
@@ -388,6 +480,9 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
<span className="text-xs font-semibold text-slate-400 flex items-center gap-1.5">
|
<span className="text-xs font-semibold text-slate-400 flex items-center gap-1.5">
|
||||||
<Eye className="w-3.5 h-3.5" /> Náhľad cenovky
|
<Eye className="w-3.5 h-3.5" /> Náhľad cenovky
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-[10px] text-blue-400 bg-blue-950/60 border border-blue-800/60 px-2.5 py-0.5 rounded-full flex items-center gap-1">
|
||||||
|
<Move className="w-3 h-3" /> Drag & Drop textových polí
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeBadge ? (
|
{activeBadge ? (
|
||||||
@@ -395,22 +490,27 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
{/* Vektorové plátno pre export do PNG */}
|
{/* Vektorové plátno pre export do PNG */}
|
||||||
<div
|
<div
|
||||||
ref={previewRef}
|
ref={previewRef}
|
||||||
className="relative flex items-center justify-center p-8 select-none"
|
className="relative inline-block select-none"
|
||||||
style={{ minWidth: 160, minHeight: 120 }}
|
|
||||||
>
|
>
|
||||||
{/* SVG Podklad */}
|
{/* SVG Podklad */}
|
||||||
<div
|
<div
|
||||||
className="relative"
|
className="block pointer-events-none"
|
||||||
dangerouslySetInnerHTML={{ __html: activeBadge.svgRaw || '' }}
|
dangerouslySetInnerHTML={{ __html: activeBadge.svgRaw || '' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Dynamické textové vrstvy nad SVG podkladom */}
|
{/* Dynamické textové vrstvy nad SVG podkladom (Drag & Drop) */}
|
||||||
{activeBadge.layersConfig.map((layer) => {
|
{activeBadge.layersConfig.map((layer) => {
|
||||||
const isSelected = layer.id === selectedLayerId;
|
const isSelected = layer.id === selectedLayerId;
|
||||||
|
const isDraggingThis = dragState?.layerId === layer.id;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={layer.id}
|
key={layer.id}
|
||||||
onClick={() => setSelectedLayerId(layer.id)}
|
onMouseDown={(e) => handleLayerMouseDown(e, layer)}
|
||||||
|
onTouchStart={(e) => handleLayerTouchStart(e, layer)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setSelectedLayerId(layer.id);
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: `${layer.x}px`,
|
left: `${layer.x}px`,
|
||||||
@@ -423,15 +523,24 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
? `'${layer.fontFamily}', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`
|
? `'${layer.fontFamily}', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`
|
||||||
: 'Poppins, Arial, sans-serif',
|
: 'Poppins, Arial, sans-serif',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
cursor: 'pointer'
|
cursor: isDraggingThis ? 'grabbing' : 'grab',
|
||||||
|
userSelect: 'none'
|
||||||
}}
|
}}
|
||||||
className={`px-1.5 py-0.5 rounded transition-all ${
|
className={`px-1.5 py-0.5 rounded transition-all select-none ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'ring-2 ring-blue-400 ring-offset-2 ring-offset-transparent bg-blue-500/10'
|
? 'ring-2 ring-blue-400 ring-offset-2 ring-offset-slate-900 bg-blue-500/20 shadow-lg'
|
||||||
: 'hover:outline hover:outline-1 hover:outline-white/40'
|
: 'hover:outline hover:outline-1 hover:outline-white/60 hover:bg-white/10'
|
||||||
}`}
|
}`}
|
||||||
|
title="Kliknite a ťahajte myšou pre zmenu pozície (Drag & Drop)"
|
||||||
>
|
>
|
||||||
{replaceVariables(layer.text || '')}
|
{replaceVariables(layer.text || '')}
|
||||||
|
|
||||||
|
{/* Indikátor pozície a uchopenia */}
|
||||||
|
{isSelected && (
|
||||||
|
<span className="absolute -top-5 left-1/2 -translate-x-1/2 text-[9px] font-mono font-bold bg-slate-900 text-blue-300 px-1.5 py-0.5 rounded shadow border border-slate-700 pointer-events-none whitespace-nowrap flex items-center gap-1 z-20">
|
||||||
|
<Move className="w-2.5 h-2.5" /> {layer.x}px, {layer.y}px
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -838,10 +838,22 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="p-3 bg-blue-50/50 rounded-xl border border-blue-100">
|
<div className="p-3 bg-blue-50/50 rounded-xl border border-blue-100">
|
||||||
<span className="text-xs font-bold text-blue-900 block mb-1">
|
<span className="text-xs font-bold text-blue-900 block mb-1">
|
||||||
Leták 1: {module.data.item1?.title || 'Aktuálne letáky'}
|
Leták 1 (Ľavý stĺpec)
|
||||||
</span>
|
</span>
|
||||||
<p className="text-[11px] text-gray-500 mb-3">Farby, texty a rozloženie sú fixne podľa šablóny. Môžete upraviť odkaz a náhľadový obrázok.</p>
|
<p className="text-[11px] text-gray-500 mb-3">Môžete upraviť nadpis letáku, cieľový odkaz a náhľadový obrázok.</p>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Nadpis letáku</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={module.data.item1?.title || ''}
|
||||||
|
onChange={(e) => updateData({
|
||||||
|
item1: { ...module.data.item1, title: e.target.value }
|
||||||
|
})}
|
||||||
|
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold"
|
||||||
|
placeholder="Aktuálne letáky"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (kam to smeruje)</label>
|
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (kam to smeruje)</label>
|
||||||
<input
|
<input
|
||||||
@@ -851,6 +863,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
item1: { ...module.data.item1, linkUrl: e.target.value }
|
item1: { ...module.data.item1, linkUrl: e.target.value }
|
||||||
})}
|
})}
|
||||||
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
|
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
|
||||||
|
placeholder="https://www.coop.sk/sk/letaky"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ImageUploadInput
|
<ImageUploadInput
|
||||||
@@ -865,10 +878,22 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
|
|
||||||
<div className="p-3 bg-amber-50/50 rounded-xl border border-amber-100">
|
<div className="p-3 bg-amber-50/50 rounded-xl border border-amber-100">
|
||||||
<span className="text-xs font-bold text-amber-900 block mb-1">
|
<span className="text-xs font-bold text-amber-900 block mb-1">
|
||||||
Leták 2: {module.data.item2?.title || 'Rozšírený leták'}
|
Leták 2 (Pravý stĺpec)
|
||||||
</span>
|
</span>
|
||||||
<p className="text-[11px] text-gray-500 mb-3">Farby, texty a rozloženie sú fixne podľa šablóny. Môžete upraviť odkaz a náhľadový obrázok.</p>
|
<p className="text-[11px] text-gray-500 mb-3">Môžete upraviť nadpis letáku, cieľový odkaz a náhľadový obrázok.</p>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Nadpis letáku</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={module.data.item2?.title || ''}
|
||||||
|
onChange={(e) => updateData({
|
||||||
|
item2: { ...module.data.item2, title: e.target.value }
|
||||||
|
})}
|
||||||
|
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white font-bold"
|
||||||
|
placeholder="Rozšírený leták"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (kam to smeruje)</label>
|
<label className="text-[10px] text-gray-600 font-semibold block mb-0.5">Cieľová URL (kam to smeruje)</label>
|
||||||
<input
|
<input
|
||||||
@@ -878,6 +903,7 @@ export const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
|||||||
item2: { ...module.data.item2, linkUrl: e.target.value }
|
item2: { ...module.data.item2, linkUrl: e.target.value }
|
||||||
})}
|
})}
|
||||||
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
|
className="w-full text-xs border border-gray-300 rounded p-1.5 bg-white"
|
||||||
|
placeholder="https://www.coop.sk/sk/letaky"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ImageUploadInput
|
<ImageUploadInput
|
||||||
|
|||||||
Reference in new issue
Block a user