feat: duplikovanie sablon cenoviek v editore (uloha 11)
Ikona Duplikovat pri sablone vytvori kopiu "(kopia)", hned ju ulozi a otvori na upravu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
21320e792b
commit
23ac4dc830
1 file changed
+59
-13
@@ -4,7 +4,22 @@ 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 { DEFAULT_BADGE_TEMPLATES, renderCleanBadgeSvg, svgToCleanPng } from '@/lib/badgeUtils';
|
import { DEFAULT_BADGE_TEMPLATES, renderCleanBadgeSvg, svgToCleanPng } from '@/lib/badgeUtils';
|
||||||
import { Tag, Plus, Check, Trash2, X, Upload, Eye, Move, AlignLeft, AlignCenter, AlignRight, ZoomIn, ZoomOut } from 'lucide-react';
|
import { Tag, Plus, Check, Trash2, X, Upload, Eye, Move, AlignLeft, AlignCenter, AlignRight, ZoomIn, ZoomOut, Copy } from 'lucide-react';
|
||||||
|
|
||||||
|
// Riadok tabuľky badges v Supabase pre danú šablónu
|
||||||
|
function toBadgeDbPayload(badge: BadgeTemplate) {
|
||||||
|
return {
|
||||||
|
name: badge.name,
|
||||||
|
svg_raw: badge.svgRaw,
|
||||||
|
layers_config: {
|
||||||
|
layers: badge.layersConfig,
|
||||||
|
width: badge.width || 110,
|
||||||
|
align: badge.align || 'center',
|
||||||
|
paddingTop: badge.paddingTop ?? 4,
|
||||||
|
paddingBottom: badge.paddingBottom ?? 4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Priblíženie plátna v editore: rozsah posuvníka a kroky tlačidiel +/−
|
// Priblíženie plátna v editore: rozsah posuvníka a kroky tlačidiel +/−
|
||||||
const CANVAS_ZOOM_MIN = 0.5;
|
const CANVAS_ZOOM_MIN = 0.5;
|
||||||
@@ -401,6 +416,35 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Duplikovanie šablóny: kópia sa hneď uloží (Supabase, inak localStorage) a otvorí na úpravu
|
||||||
|
const duplicateBadgeTemplate = async (badge: BadgeTemplate) => {
|
||||||
|
const copy: BadgeTemplate = {
|
||||||
|
...JSON.parse(JSON.stringify(badge)),
|
||||||
|
id: `badge-${Date.now()}`,
|
||||||
|
name: `${badge.name} (kópia)`
|
||||||
|
};
|
||||||
|
let saved = copy;
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('badges')
|
||||||
|
.insert([toBadgeDbPayload(copy)])
|
||||||
|
.select();
|
||||||
|
if (!error && data && data[0]) {
|
||||||
|
saved = { ...copy, id: data[0].id };
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
const list = [...badges];
|
||||||
|
const idx = list.findIndex((b) => b.id === badge.id);
|
||||||
|
list.splice(idx + 1, 0, saved);
|
||||||
|
setBadges(list);
|
||||||
|
setActiveBadge(saved);
|
||||||
|
setSelectedLayerId(saved.layersConfig?.[0]?.id ?? null);
|
||||||
|
try {
|
||||||
|
localStorage.setItem('coop_custom_badges', JSON.stringify(list));
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
|
||||||
const deleteBadgeTemplate = async (badgeId: string) => {
|
const deleteBadgeTemplate = async (badgeId: string) => {
|
||||||
if (confirm('Naozaj chcete zmazať túto šablónu cenovky?')) {
|
if (confirm('Naozaj chcete zmazať túto šablónu cenovky?')) {
|
||||||
// Predvolené šablóny existujú len v prehliadači (localStorage), v databáze nie sú
|
// Predvolené šablóny existujú len v prehliadači (localStorage), v databáze nie sú
|
||||||
@@ -423,17 +467,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
if (!activeBadge) return;
|
if (!activeBadge) return;
|
||||||
setIsSavingDb(true);
|
setIsSavingDb(true);
|
||||||
try {
|
try {
|
||||||
const payload: any = {
|
const payload = toBadgeDbPayload(activeBadge);
|
||||||
name: activeBadge.name,
|
|
||||||
svg_raw: activeBadge.svgRaw,
|
|
||||||
layers_config: {
|
|
||||||
layers: activeBadge.layersConfig,
|
|
||||||
width: activeBadge.width || 110,
|
|
||||||
align: activeBadge.align || 'center',
|
|
||||||
paddingTop: activeBadge.paddingTop ?? 4,
|
|
||||||
paddingBottom: activeBadge.paddingBottom ?? 4
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let savedBadge: BadgeTemplate | null = null;
|
let savedBadge: BadgeTemplate | null = null;
|
||||||
const isExistingUuid = activeBadge.id && !activeBadge.id.startsWith('badge-');
|
const isExistingUuid = activeBadge.id && !activeBadge.id.startsWith('badge-');
|
||||||
@@ -646,18 +680,30 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<p className="text-xs font-bold text-gray-800 truncate">{b.name}</p>
|
<p className="text-xs font-bold text-gray-800 truncate">{b.name}</p>
|
||||||
|
<div className="flex items-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
duplicateBadgeTemplate(b);
|
||||||
|
}}
|
||||||
|
className="text-gray-300 hover:text-blue-600 p-1 rounded transition-colors"
|
||||||
|
title="Duplikovať šablónu"
|
||||||
|
>
|
||||||
|
<Copy className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
{/* Zmazať sa dá každá šablóna, aj staršie predvolené (badge-preset-*) uložené v prehliadači */}
|
{/* Zmazať sa dá každá šablóna, aj staršie predvolené (badge-preset-*) uložené v prehliadači */}
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
deleteBadgeTemplate(b.id);
|
deleteBadgeTemplate(b.id);
|
||||||
}}
|
}}
|
||||||
className="text-gray-300 hover:text-red-600 p-1 rounded transition-colors opacity-0 group-hover:opacity-100"
|
className="text-gray-300 hover:text-red-600 p-1 rounded transition-colors"
|
||||||
title="Zmazať šablónu"
|
title="Zmazať šablónu"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-3.5 h-3.5" />
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="w-full h-12 flex items-center justify-center transform scale-75 origin-center overflow-hidden pointer-events-none"
|
className="w-full h-12 flex items-center justify-center transform scale-75 origin-center overflow-hidden pointer-events-none"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
|
|||||||
Reference in new issue
Block a user