Fixed duplicate melody creating
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
@@ -97,7 +98,7 @@ function AssignedMelodiesModal({ archetype, melodyDetails, onClose }) {
|
||||
className="flex items-center justify-between px-3 py-2 rounded-lg border transition-colors hover:bg-[var(--bg-card-hover)]"
|
||||
style={{ borderColor: "var(--border-primary)", color: "var(--text-heading)", textDecoration: "none" }}
|
||||
>
|
||||
<span className="text-sm font-medium">{m.name || m.id}</span>
|
||||
<span className="text-sm font-medium">{getLocalizedValue(m.nameRaw, primaryLang, m.id)}</span>
|
||||
<span className="text-xs" style={{ color: "var(--accent)" }}>View →</span>
|
||||
</Link>
|
||||
))
|
||||
@@ -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() {
|
||||
<AssignedMelodiesModal
|
||||
archetype={assignedModal?.archetype}
|
||||
melodyDetails={assignedModal?.melodyDetails || []}
|
||||
primaryLang={primaryLang}
|
||||
onClose={() => setAssignedModal(null)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user