fix(arbre): place envoyée visible jusqu'à la relecture, erreurs d'enregistrement affichées

- Contributeur : après l'envoi, la place proposée reste à l'écran comme un
  nom modifié, au lieu de revenir à l'ancienne place jusqu'à l'approbation.
- Admin : un échec d'enregistrement d'une personne est signalé par un toast
  d'erreur, et le brouillon est conservé au lieu d'être vidé.
- Gestionnaire handleResetTreeLayout (le bouton arrive dans le commit suivant).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
IliesAllaliandClaude Opus 5.5 committed 2026-10-16 18:13:46 +02:00
1 parent 697c0e3897
commit 6ff99337d2
2 files changed
+45

No files matched your search

+44
View File
@@ -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 && (
<div className="contrib-success-toast" role="status">{draftNotice}</div>
)}
{editSaveError && (
<div className="contrib-success-toast" role="alert">{editSaveError}</div>
)}
{contribSuccess && (
<div className="contrib-success-toast" role="status">
{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}
/>
<PhotoStartPanel
visible={photoStart.visible}
+1
View File
@@ -97,4 +97,5 @@ export default {
'Ressource introuvable.': 'Not found.',
'Conflit détecté (email ou slug déjà utilisé).': 'Conflict detected (email or slug already in use).',
"Erreur backend. Vérifiez que l'API et la base sont lancées.": 'Server error. Check that the API and database are running.',
"Certaines modifications n'ont pas pu être enregistrées. Vérifiez puis réessayez.": "Some changes couldn't be saved. Check them and try again.",
}