diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 47c77a8..58c333c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1179,6 +1179,12 @@ function App() { const timer = setTimeout(() => setDraftNotice(''), 4500) return () => clearTimeout(timer) }, [draftNotice]) + const [editSaveError, setEditSaveError] = useState('') + useEffect(() => { + if (!editSaveError) return undefined + const timer = setTimeout(() => setEditSaveError(''), 6000) + return () => clearTimeout(timer) + }, [editSaveError]) // Soumission des modifications const handleEditSubmit = useCallback(async () => { @@ -1196,6 +1202,7 @@ function App() { const treeId = tree.treeContext.treeId const draft = editMode.restoreDraft() + let failedPersonSaves = 0 if (token && treeId) { // Appliquer les modifications de personnes depuis le draft const modifiedPersons = draft.modifiedPersons || {} @@ -1219,6 +1226,7 @@ function App() { try { await updatePerson(treeId, personId, payload, token) } catch (err) { + failedPersonSaves += 1 console.error('Failed to update person:', personId, err) } } @@ -1266,6 +1274,11 @@ function App() { } await tree.refreshCurrentTreeGraph({ relayout: false }) + if (failedPersonSaves > 0) { + // Brouillon gardé : rien n'est perdu, l'admin peut réessayer + setEditSaveError(t("Certaines modifications n'ont pas pu être enregistrées. Vérifiez puis réessayez.")) + return + } editMode.clearDraft() editMode.deactivate() } else { @@ -1309,6 +1322,16 @@ function App() { return } + // Comme un nom modifié, la place envoyée reste à l'écran jusqu'à la relecture + for (const [personId, changes] of Object.entries(draft.modifiedPersons || {})) { + if (!Number.isFinite(changes?.layoutX) || !Number.isFinite(changes?.layoutY)) continue + const localPerson = persons.find((candidate) => String(candidate.id) === String(personId)) + if (localPerson) { + localPerson.layoutX = changes.layoutX + localPerson.layoutY = changes.layoutY + } + } + editMode.clearDraft() editMode.deactivate() setShowContribModal(false) @@ -2532,6 +2555,22 @@ function App() { } } + // Admin : efface les positions manuelles de l'arbre, y compris celles restées dans son brouillon + const handleResetTreeLayout = useCallback(async () => { + const draft = editMode.restoreDraft() + if (draft.modifiedPersons) { + const modifiedPersons = {} + for (const [personId, changes] of Object.entries(draft.modifiedPersons)) { + const rest = { ...changes } + delete rest.layoutX + delete rest.layoutY + if (Object.keys(rest).length > 0) modifiedPersons[personId] = rest + } + editMode.updateDraft({ modifiedPersons }) + } + await refreshTreeAndKeepSelection(null, { relayout: true }) + }, [editMode.restoreDraft, editMode.updateDraft, refreshTreeAndKeepSelection]) + const handleGedcomImported = useCallback(async (result, treeIdOverride = null) => { await refreshTreeAndKeepSelection(null, { relayout: true }) const refs = Array.isArray(result?.photoRefs) ? result.photoRefs : [] @@ -2803,6 +2842,9 @@ function App() { {draftNotice && !contribSuccess && !showContribModal && (
{draftNotice}
)} + {editSaveError && ( +
{editSaveError}
+ )} {contribSuccess && (
{t("Merci, c'est envoyé ! Ça apparaîtra dans l'arbre une fois relu.")} @@ -2942,6 +2984,8 @@ function App() { onGedcomImported={handleGedcomImported} onOpenPhotoStart={() => { setAdminPanelVisible(false); openPhotoStart() }} onClose={() => setAdminPanelVisible(false)} + hasManualPositions={persons.some((person) => Number.isFinite(person.layoutX) && Number.isFinite(person.layoutY))} + onLayoutReset={handleResetTreeLayout} />