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
+56
-10
No files matched your search
+30
-1
@@ -71,7 +71,8 @@ export default function Home() {
|
|||||||
if (Array.isArray(parsed)) {
|
if (Array.isArray(parsed)) {
|
||||||
const existingIds = new Set(loaded.map((b) => b.id));
|
const existingIds = new Set(loaded.map((b) => b.id));
|
||||||
for (const b of parsed) {
|
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);
|
loaded.push(b);
|
||||||
existingIds.add(b.id);
|
existingIds.add(b.id);
|
||||||
}
|
}
|
||||||
@@ -92,6 +93,34 @@ export default function Home() {
|
|||||||
fetchBadges();
|
fetchBadges();
|
||||||
}, [isBadgeEditorOpen]);
|
}, [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
|
// 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';
|
const isTopModule = (m: NewsletterModule) => m.type === 'header' || m.type === 'announcement';
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ function toBadgeDbPayload(badge: BadgeTemplate) {
|
|||||||
align: badge.align || 'center',
|
align: badge.align || 'center',
|
||||||
paddingTop: badge.paddingTop ?? 4,
|
paddingTop: badge.paddingTop ?? 4,
|
||||||
paddingBottom: badge.paddingBottom ?? 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) || '[]');
|
const local = JSON.parse(localStorage.getItem(LOCAL_KEY) || '[]');
|
||||||
if (Array.isArray(local)) {
|
if (Array.isArray(local)) {
|
||||||
const ids = new Set(loaded.map((b) => b.id));
|
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 {
|
} catch {
|
||||||
// localStorage nemusí byť dostupný
|
// localStorage nemusí byť dostupný
|
||||||
@@ -553,7 +555,7 @@ export const BadgeEditorModal: React.FC<BadgeEditorModalProps> = ({
|
|||||||
|
|
||||||
const duplicateTemplate = async (b: BadgeTemplate) => {
|
const duplicateTemplate = async (b: BadgeTemplate) => {
|
||||||
if (!confirmDiscard()) return;
|
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;
|
let saved = copy;
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase.from('badges').insert([toBadgeDbPayload(copy)]).select();
|
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) => {
|
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 (!confirm(`Zmazať šablónu „${b.name}“? Produkty, ktoré ju už používajú, si ju ponechajú.`)) return;
|
||||||
if (!b.id.startsWith('badge-')) {
|
if (!b.id.startsWith('badge-')) {
|
||||||
try {
|
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">
|
<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" />
|
<Copy className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
<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">
|
{b.isDefault ? (
|
||||||
<Trash2 className="w-3.5 h-3.5" />
|
<span className="text-[9px] font-bold text-gray-500 bg-gray-100 rounded px-1" title="Predvolená cenovka produktov – nedá sa zmazať">
|
||||||
</button>
|
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>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -503,11 +503,12 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
|||||||
<BadgeFilterBar filter={badgeFilter} onChange={setBadgeFilter} shown={filteredBadges.length} total={availableBadges.length} />
|
<BadgeFilterBar filter={badgeFilter} onChange={setBadgeFilter} shown={filteredBadges.length} total={availableBadges.length} />
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<select
|
<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)}
|
onChange={(e) => selectBadge(e.target.value)}
|
||||||
className={`${inputCls} font-medium`}
|
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á */}
|
{/* 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) && (
|
{p.badgeCustomTemplate && !p.customBadgePngUrl && !filteredBadges.some((b) => b.id === p.badgeCustomTemplate!.id) && (
|
||||||
<option value={p.badgeCustomTemplate.id}>
|
<option value={p.badgeCustomTemplate.id}>
|
||||||
@@ -518,6 +519,7 @@ export const ProductEditor: React.FC<ProductEditorProps> = ({
|
|||||||
{filteredBadges.map((b) => (
|
{filteredBadges.map((b) => (
|
||||||
<option key={b.id} value={b.id}>
|
<option key={b.id} value={b.id}>
|
||||||
{b.name}
|
{b.name}
|
||||||
|
{b.isDefault ? ' (predvolená)' : ''}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
{p.customBadgePngUrl && <option value="png">Vlastná PNG cenovka</option>}
|
{p.customBadgePngUrl && <option value="png">Vlastná PNG cenovka</option>}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface BadgeRow {
|
|||||||
paddingTop?: number;
|
paddingTop?: number;
|
||||||
paddingBottom?: number;
|
paddingBottom?: number;
|
||||||
importTags?: BadgeImportTags;
|
importTags?: BadgeImportTags;
|
||||||
|
isDefault?: boolean;
|
||||||
}
|
}
|
||||||
| null;
|
| null;
|
||||||
}
|
}
|
||||||
@@ -36,7 +37,8 @@ export function badgeFromRow(d: BadgeRow): BadgeTemplate {
|
|||||||
align: cfg?.align || 'center',
|
align: cfg?.align || 'center',
|
||||||
paddingTop: cfg?.paddingTop ?? 4,
|
paddingTop: cfg?.paddingTop ?? 4,
|
||||||
paddingBottom: cfg?.paddingBottom ?? 4,
|
paddingBottom: cfg?.paddingBottom ?? 4,
|
||||||
importTags: cfg?.importTags
|
importTags: cfg?.importTags,
|
||||||
|
isDefault: cfg?.isDefault || undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ export interface BadgeTemplate {
|
|||||||
paddingTop?: number;
|
paddingTop?: number;
|
||||||
paddingBottom?: number;
|
paddingBottom?: number;
|
||||||
importTags?: BadgeImportTags; // podľa čoho sa šablóna vyberie pri importe podkladov (ZIP)
|
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
|
// 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