From 1ccf855913d46ba902ff4404d163cc20a02625b1 Mon Sep 17 00:00:00 2001 From: bonamin Date: Sun, 22 Feb 2026 18:52:38 +0200 Subject: [PATCH] Fixed duplicate melody creating --- frontend/src/melodies/MelodyForm.jsx | 9 ++++ .../src/melodies/archetypes/ArchetypeList.jsx | 52 +++++++++++++------ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/frontend/src/melodies/MelodyForm.jsx b/frontend/src/melodies/MelodyForm.jsx index 304142d..b4cf00f 100644 --- a/frontend/src/melodies/MelodyForm.jsx +++ b/frontend/src/melodies/MelodyForm.jsx @@ -225,6 +225,15 @@ export default function MelodyForm() { if (isEdit) { const body = buildBody({ dateEdited: now, lastEditedBy: userName, adminNotes }); await api.put(`/melodies/${id}`, body); + } else if (savedMelodyId) { + // Melody was already created (e.g. after using Select Archetype / Build on the Fly) + // Update it instead of creating a duplicate + melodyId = savedMelodyId; + const body = buildBody({ dateEdited: now, lastEditedBy: userName, adminNotes }); + await api.put(`/melodies/${savedMelodyId}`, body); + if (publish) { + await api.post(`/melodies/${savedMelodyId}/publish`); + } } else { const body = buildBody({ dateCreated: now, dateEdited: now, createdBy: userName, lastEditedBy: userName, adminNotes }); const created = await api.post(`/melodies?publish=${publish}`, body); diff --git a/frontend/src/melodies/archetypes/ArchetypeList.jsx b/frontend/src/melodies/archetypes/ArchetypeList.jsx index 66932eb..b3bb876 100644 --- a/frontend/src/melodies/archetypes/ArchetypeList.jsx +++ b/frontend/src/melodies/archetypes/ArchetypeList.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import { useNavigate, Link } from "react-router-dom"; import api from "../../api/client"; import ConfirmDialog from "../../components/ConfirmDialog"; +import { getLocalizedValue } from "../melodyUtils"; function fallbackCopy(text, onSuccess) { const ta = document.createElement("textarea"); @@ -67,7 +68,7 @@ function CodeSnippetModal({ melody, onClose }) { } // Modal to show melodies that use this archetype -function AssignedMelodiesModal({ archetype, melodyDetails, onClose }) { +function AssignedMelodiesModal({ archetype, melodyDetails, primaryLang, onClose }) { if (!archetype) return null; return (
- {m.name || m.id} + {getLocalizedValue(m.nameRaw, primaryLang, m.id)} View → )) @@ -125,17 +126,45 @@ export default function ArchetypeList() { const [codeSnippetMelody, setCodeSnippetMelody] = useState(null); const [assignedModal, setAssignedModal] = useState(null); // { archetype, melodyDetails } const [loadingAssigned, setLoadingAssigned] = useState(false); + const [primaryLang, setPrimaryLang] = useState("en"); useEffect(() => { - loadArchetypes(); + api.get("/settings/melody").then((ms) => setPrimaryLang(ms.primary_language || "en")).catch(() => {}); + loadArchetypes({ verify: true }); }, []); - const loadArchetypes = async () => { + const loadArchetypes = async ({ verify = false } = {}) => { setLoading(true); setError(""); try { const data = await api.get("/builder/melodies"); - setArchetypes(data.melodies || []); + const list = data.melodies || []; + setArchetypes(list); + + if (verify) { + // Check all assigned_melody_ids across all archetypes and prune stale ones + let didPrune = false; + await Promise.all( + list.map(async (archetype) => { + const ids = archetype.assigned_melody_ids || []; + if (ids.length === 0) return; + const results = await Promise.allSettled(ids.map((mid) => api.get(`/melodies/${mid}`))); + const missingIds = results + .map((r, i) => (!r.value || r.status === "rejected" ? ids[i] : null)) + .filter(Boolean); + for (const mid of missingIds) { + try { + await api.post(`/builder/melodies/${archetype.id}/unassign?firestore_melody_id=${mid}`); + didPrune = true; + } catch { /* best-effort */ } + } + }) + ); + if (didPrune) { + const refreshed = await api.get("/builder/melodies"); + setArchetypes(refreshed.melodies || []); + } + } } catch (err) { setError(err.message); } finally { @@ -196,18 +225,8 @@ export default function ArchetypeList() { .filter((r) => r.status === "fulfilled" && r.value) .map((r) => { const m = r.value; - // Extract a display name - const nameRaw = m.information?.name; - let name = ""; - if (typeof nameRaw === "string") name = nameRaw; - else if (nameRaw && typeof nameRaw === "object") name = Object.values(nameRaw)[0] || m.id; - return { id: m.id, name: name || m.pid || m.id }; + return { id: m.id, nameRaw: m.information?.name }; }); - // IDs that were NOT found (melody deleted) - const foundIds = results - .filter((r, i) => r.status === "fulfilled" && r.value) - .map((_, i) => ids[results.findIndex((r, j) => j === i && r.status === "fulfilled")]); - // Unassign any IDs that no longer exist const missingIds = results .map((r, i) => r.status === "rejected" || !r.value ? ids[i] : null) @@ -426,6 +445,7 @@ export default function ArchetypeList() { setAssignedModal(null)} />