diff --git a/src/app/page.tsx b/src/app/page.tsx index bf2c251..bb4731c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -12,6 +12,7 @@ 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 { NameDialog } from '@/components/editor/NameDialog'; import JSZip from 'jszip'; import { prepareCardImages } from '@/lib/cardImages'; import { useCampaignReview } from '@/components/review/useCampaignReview'; @@ -42,6 +43,7 @@ export default function Home() { const [isImportOpen, setIsImportOpen] = useState(false); const [isBadgeEditorOpen, setIsBadgeEditorOpen] = useState(false); const [isSavedCampaignsOpen, setIsSavedCampaignsOpen] = useState(false); + const [isDuplicateOpen, setIsDuplicateOpen] = useState(false); const [targetProductIdForBadge, setTargetProductIdForBadge] = useState(null); const [availableBadges, setAvailableBadges] = useState(DEFAULT_BADGE_TEMPLATES); @@ -412,10 +414,9 @@ export default function Home() { }; // Duplikácia aktuálnej kampane v Supabase - const handleDuplicateCurrentCampaign = async () => { + const handleDuplicateCurrentCampaign = async (newTitle: string) => { setIsSaving(true); try { - const newTitle = `${campaign.title} (kópia)`; const { data, error } = await supabase .from('templates') .insert([ @@ -439,7 +440,7 @@ export default function Home() { status: 'draft', modules: campaign.modules }); - alert(`Kampaň bola zduplikovaná ako "${newTitle}"!`); + setIsDuplicateOpen(false); } } catch (err: any) { alert(`Chyba pri duplikovaní: ${err.message}`); @@ -662,7 +663,7 @@ export default function Home() { onSave={handleManualSave} onUpdateTitle={(title) => setCampaign((prev) => ({ ...prev, title }))} onOpenSavedCampaigns={() => setIsSavedCampaignsOpen(true)} - onDuplicateCampaign={handleDuplicateCurrentCampaign} + onDuplicateCampaign={() => setIsDuplicateOpen(true)} onExportZip={handleExportZip} onExportBrevo={handleExportBrevo} isExportingBrevo={isExportingBrevo} @@ -1012,8 +1013,18 @@ export default function Home() { /> {/* Modál Moje newslettre (Ukladanie, duplikácia, vytváranie a prepínanie) */} + setIsDuplicateOpen(false)} + /> + setCampaign((prev) => ({ ...prev, title }))} onClose={() => setIsSavedCampaignsOpen(false)} currentCampaign={campaign} onLoadCampaign={(selected: NewsletterCampaign) => { diff --git a/src/components/editor/NameDialog.tsx b/src/components/editor/NameDialog.tsx new file mode 100644 index 0000000..3b9ffac --- /dev/null +++ b/src/components/editor/NameDialog.tsx @@ -0,0 +1,72 @@ +'use client'; + +import React, { useEffect, useRef, useState } from 'react'; + +interface NameDialogProps { + open: boolean; + title: string; + initial: string; + confirmText: string; + onConfirm: (name: string) => void | Promise; + onCancel: () => void; +} + +// Malé okno na zadanie názvu (premenovanie, duplikovanie newslettera). +// Pri každom otvorení sa obsah pripojí nanovo, takže začína s predvyplneným názvom. +export const NameDialog: React.FC = ({ open, ...props }) => (open ? : null); + +const NameDialogBody: React.FC> = ({ title, initial, confirmText, onConfirm, onCancel }) => { + const [value, setValue] = useState(initial); + const [busy, setBusy] = useState(false); + const inputRef = useRef(null); + + // predvyplnený názov označíme, aby sa dal hneď prepísať alebo len doplniť + useEffect(() => { + const t = setTimeout(() => inputRef.current?.select(), 30); + return () => clearTimeout(t); + }, []); + + const submit = async () => { + const name = value.trim(); + if (!name || busy) return; + setBusy(true); + try { + await onConfirm(name); + } finally { + setBusy(false); + } + }; + + return ( +
+
e.stopPropagation()}> +

{title}

+ setValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') submit(); + if (e.key === 'Escape') onCancel(); + }} + placeholder="Názov newslettera" + className="w-full text-sm border border-gray-300 rounded-xl px-3 py-2 outline-none focus:border-red-500 focus:ring-2 focus:ring-red-500/15" + /> +
+ + +
+
+
+ ); +}; diff --git a/src/components/editor/SavedCampaignsModal.tsx b/src/components/editor/SavedCampaignsModal.tsx index 3522e41..399ba77 100644 --- a/src/components/editor/SavedCampaignsModal.tsx +++ b/src/components/editor/SavedCampaignsModal.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect } from 'react'; import { supabase } from '@/lib/supabase'; import { NewsletterCampaign } from '@/types/newsletter'; import { DEFAULT_CAMPAIGN } from '@/lib/defaultCampaign'; +import { NameDialog } from './NameDialog'; import { FolderOpen, Plus, @@ -15,7 +16,8 @@ import { CheckCircle2, Layers, Search, - Sparkles + Sparkles, + Pencil } from 'lucide-react'; interface SavedCampaignsModalProps { @@ -24,6 +26,7 @@ interface SavedCampaignsModalProps { currentCampaign: NewsletterCampaign; onLoadCampaign: (campaign: NewsletterCampaign) => void; onSaveCurrentAsCopy: (newTitle: string) => Promise; + onRenameCurrent: (title: string) => void; } export const SavedCampaignsModal: React.FC = ({ @@ -31,13 +34,16 @@ export const SavedCampaignsModal: React.FC = ({ onClose, currentCampaign, onLoadCampaign, - onSaveCurrentAsCopy + onSaveCurrentAsCopy, + onRenameCurrent }) => { const [campaigns, setCampaigns] = useState([]); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(''); const [isCreatingNew, setIsCreatingNew] = useState(false); const [newTitle, setNewTitle] = useState(''); + // okno na názov: premenovanie alebo duplikovanie vybraného newslettera + const [nameAction, setNameAction] = useState<{ kind: 'rename' | 'duplicate'; camp: { id: string; title: string; data?: unknown } } | null>(null); const fetchCampaigns = async () => { setLoading(true); @@ -66,9 +72,20 @@ export const SavedCampaignsModal: React.FC = ({ if (!isOpen) return null; - const handleDuplicate = async (camp: any) => { + const handleRename = async (camp: { id: string }, title: string) => { + try { + const { error } = await supabase.from('templates').update({ title }).eq('id', camp.id); + if (error) throw error; + setCampaigns((prev) => prev.map((c) => (c.id === camp.id ? { ...c, title } : c))); + if (camp.id === currentCampaign.id) onRenameCurrent(title); + setNameAction(null); + } catch (err) { + alert(`Chyba pri premenovaní: ${(err as Error).message}`); + } + }; + + const handleDuplicate = async (camp: any, clonedTitle: string) => { try { - const clonedTitle = `Kópia - ${camp.title}`; const { data, error } = await supabase .from('templates') .insert([ @@ -90,6 +107,7 @@ export const SavedCampaignsModal: React.FC = ({ status: 'draft', modules: data[0].data?.modules || [] }); + setNameAction(null); onClose(); } } catch (err: any) { @@ -326,7 +344,7 @@ export const SavedCampaignsModal: React.FC = ({ + + + (nameAction?.kind === 'rename' ? handleRename(nameAction.camp, title) : handleDuplicate(nameAction!.camp, title))} + onCancel={() => setNameAction(null)} + /> ); }; diff --git a/src/components/editor/TopBar.tsx b/src/components/editor/TopBar.tsx index c59e444..4ae4518 100644 --- a/src/components/editor/TopBar.tsx +++ b/src/components/editor/TopBar.tsx @@ -142,7 +142,7 @@ export const TopBar: React.FC = ({

{campaign.title || 'COOP Jednota Newsletter'}

- + )}