feat: predvolena cenovka je sablona Super vyhodne | Cervena (neda sa zmazat)
- volba Predvolena COOP cenovka v produkte zrusena; produkty bez sablony dostanu predvolenu sablonu automaticky - sablony z prehliadaca (localStorage) sa pridavaju len ak este nie su ulozene v databaze, zmazane sablony sa uz nevracaju Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
184e55ad5b
commit
b747a3ba74
5 files changed
+53
-7
No files matched your search
+30
-1
@@ -71,7 +71,8 @@ export default function Home() {
|
||||
if (Array.isArray(parsed)) {
|
||||
const existingIds = new Set(loaded.map((b) => b.id));
|
||||
for (const b of parsed) {
|
||||
if (!existingIds.has(b.id)) {
|
||||
// len šablóny neuložené do databázy – zmazané šablóny sa z prehliadača nesmú vrátiť
|
||||
if (!existingIds.has(b.id) && String(b.id).startsWith('badge-')) {
|
||||
loaded.push(b);
|
||||
existingIds.add(b.id);
|
||||
}
|
||||
@@ -92,6 +93,34 @@ export default function Home() {
|
||||
fetchBadges();
|
||||
}, [isBadgeEditorOpen]);
|
||||
|
||||
// Predvolená cenovka: produkty bez šablóny (nové, importované bez zhody, staršie s pôvodnou COOP cenovkou)
|
||||
// dostanú predvolenú šablónu z databázy
|
||||
const defaultBadge = availableBadges.find((b) => b.isDefault);
|
||||
useEffect(() => {
|
||||
if (!defaultBadge) return;
|
||||
// jednorazové doplnenie chýbajúcej cenovky do dát (zmení stav len keď nejaký produkt šablónu nemá)
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setCampaign((prev) => {
|
||||
let changed = false;
|
||||
const modules = prev.modules.map((m) => {
|
||||
if (m.type !== 'products_grid') return m;
|
||||
const products = m.data.products || [];
|
||||
if (!products.some((p: ProductItem) => !p.badgeCustomTemplate && !p.customBadgePngUrl)) return m;
|
||||
changed = true;
|
||||
return {
|
||||
...m,
|
||||
data: {
|
||||
...m.data,
|
||||
products: products.map((p: ProductItem) =>
|
||||
p.badgeCustomTemplate || p.customBadgePngUrl ? p : { ...p, badgeCustomTemplate: defaultBadge, badgeId: defaultBadge.id }
|
||||
)
|
||||
}
|
||||
};
|
||||
});
|
||||
return changed ? { ...prev, modules } : prev;
|
||||
});
|
||||
}, [campaign.modules, defaultBadge]);
|
||||
|
||||
// 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';
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ function toBadgeDbPayload(badge: BadgeTemplate) {
|
||||
align: badge.align || 'center',
|
||||
paddingTop: badge.paddingTop ?? 4,
|
||||
paddingBottom: badge.paddingBottom ?? 4,
|
||||
importTags: badge.importTags
|
||||
importTags: badge.importTags,
|
||||
isDefault: badge.isDefault || undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -363,7 +364,8 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
const local = JSON.parse(localStorage.getItem(LOCAL_KEY) || '[]');
|
||||
if (Array.isArray(local)) {
|
||||
const ids = new Set(loaded.map((b) => b.id));
|
||||
for (const b of local as BadgeTemplate[]) if (!ids.has(b.id)) loaded.push(b);
|
||||
// z prehliadača len šablóny, ktoré ešte neboli uložené do databázy (zmazané sa nesmú vrátiť)
|
||||
for (const b of local as BadgeTemplate[]) if (!ids.has(b.id) && b.id.startsWith('badge-')) loaded.push(b);
|
||||
}
|
||||
} catch {
|
||||
// localStorage nemusí byť dostupný
|
||||
@@ -553,7 +555,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
|
||||
const duplicateTemplate = async (b: BadgeTemplate) => {
|
||||
if (!confirmDiscard()) return;
|
||||
const copy: BadgeTemplate = { ...JSON.parse(JSON.stringify(b)), id: newLocalBadgeId(), name: `${b.name} (kópia)` };
|
||||
const copy: BadgeTemplate = { ...JSON.parse(JSON.stringify(b)), id: newLocalBadgeId(), name: `${b.name} (kópia)`, isDefault: undefined };
|
||||
let saved = copy;
|
||||
try {
|
||||
const { data, error } = await supabase.from('badges').insert([toBadgeDbPayload(copy)]).select();
|
||||
@@ -569,6 +571,10 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
};
|
||||
|
||||
const deleteTemplate = async (b: BadgeTemplate) => {
|
||||
if (b.isDefault) {
|
||||
alert('Predvolenú cenovku nie je možné zmazať.');
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Zmazať šablónu „${b.name}“? Produkty, ktoré ju už používajú, si ju ponechajú.`)) return;
|
||||
if (!b.id.startsWith('badge-')) {
|
||||
try {
|
||||
@@ -743,9 +749,15 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
||||
<button type="button" onClick={(e) => { e.stopPropagation(); duplicateTemplate(b); }} className="p-0.5 text-gray-300 hover:text-blue-600 opacity-0 group-hover:opacity-100" title="Duplikovať šablónu">
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
{b.isDefault ? (
|
||||
<span className="text-[9px] font-bold text-gray-500 bg-gray-100 rounded px-1" title="Predvolená cenovka produktov – nedá sa zmazať">
|
||||
predvolená
|
||||
</span>
|
||||
) : (
|
||||
<button type="button" onClick={(e) => { e.stopPropagation(); deleteTemplate(b); }} className="p-0.5 text-gray-300 hover:text-red-600 opacity-0 group-hover:opacity-100" title="Zmazať šablónu">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -503,11 +503,12 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
||||
<BadgeFilterBar filter={badgeFilter} onChange={setBadgeFilter} shown={filteredBadges.length} total={availableBadges.length} />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<select
|
||||
value={p.customBadgePngUrl ? 'png' : p.badgeCustomTemplate?.id || 'default'}
|
||||
value={p.customBadgePngUrl ? 'png' : p.badgeCustomTemplate?.id || (isLoyalty ? 'default' : availableBadges.find((b) => b.isDefault)?.id || '')}
|
||||
onChange={(e) => selectBadge(e.target.value)}
|
||||
className={`${inputCls} font-medium`}
|
||||
>
|
||||
<option value="default">{isLoyalty ? 'Predvolená (bez karty / s kartou)' : 'Predvolená COOP cenovka'}</option>
|
||||
{/* bežné produkty: predvolená je šablóna označená ako predvolená (nastaví sa automaticky) */}
|
||||
{isLoyalty && <option value="default">Predvolená (bez karty / s kartou)</option>}
|
||||
{/* aktuálna šablóna ostáva v zozname, aj keď ju filter skryje alebo už bola zmazaná */}
|
||||
{p.badgeCustomTemplate && !p.customBadgePngUrl && !filteredBadges.some((b) => b.id === p.badgeCustomTemplate!.id) && (
|
||||
<option value={p.badgeCustomTemplate.id}>
|
||||
@@ -518,6 +519,7 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
||||
{filteredBadges.map((b) => (
|
||||
<option key={b.id} value={b.id}>
|
||||
{b.name}
|
||||
{b.isDefault ? ' (predvolená)' : ''}
|
||||
</option>
|
||||
))}
|
||||
{p.customBadgePngUrl && <option value="png">Vlastná PNG cenovka</option>}
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface BadgeRow {
|
||||
paddingTop?: number;
|
||||
paddingBottom?: number;
|
||||
importTags?: BadgeImportTags;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
| null;
|
||||
}
|
||||
@@ -36,7 +37,8 @@ export function badgeFromRow(d: BadgeRow): BadgeTemplate {
|
||||
align: cfg?.align || 'center',
|
||||
paddingTop: cfg?.paddingTop ?? 4,
|
||||
paddingBottom: cfg?.paddingBottom ?? 4,
|
||||
importTags: cfg?.importTags
|
||||
importTags: cfg?.importTags,
|
||||
isDefault: cfg?.isDefault || undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -245,6 +245,7 @@ export interface BadgeTemplate {
|
||||
paddingTop?: number;
|
||||
paddingBottom?: number;
|
||||
importTags?: BadgeImportTags; // podľa čoho sa šablóna vyberie pri importe podkladov (ZIP)
|
||||
isDefault?: boolean; // predvolená cenovka produktov – nedá sa zmazať
|
||||
}
|
||||
|
||||
// Druhy cenoviek v podkladoch: „Zľava grafická“, a keď je prázdna, základná cenovka s výkričníkom alebo bez
|
||||
|
||||
Reference in new issue
Block a user