diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c437f5c..8b48f87 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1213,6 +1213,11 @@ function App() { birthDate: formData.birthDate, deathDate: formData.deathDate, isDeceased: Boolean(formData.isDeceased), + // Personne venue d'une source publique (panneau « Chercher quelqu'un ») : lieux, « à vérifier » et source + ...(formData.birthPlace ? { birthPlace: formData.birthPlace } : {}), + ...(formData.deathPlace ? { deathPlace: formData.deathPlace } : {}), + ...(formData.toVerify ? { toVerify: true } : {}), + ...(formData.notes ? { notes: formData.notes } : {}), }, auth.userAuth.token ) @@ -1264,11 +1269,13 @@ function App() { }) await tree.refreshCurrentTreeGraph() + return newPerson } catch (err) { setAddPersonError(err.message || t("Erreur lors de l'ajout de la personne")) } finally { setAddPersonLoading(false) } + return null }, [auth.userAuth.token, tree.canEditCurrentTree, tree.treeContext.treeId, isDemoMode, makeLocalId]) // eslint-disable-line react-hooks/exhaustive-deps // Modal session contribution (contributeurs) @@ -3342,6 +3349,8 @@ function App() { persons={persons} canApply={tree.canEditCurrentTree} onApply={handlePersonSave} + onCreate={handleAddPersonSubmit} + createError={addPersonError} onClose={() => setSearchPanelVisible(false)} /> .pz-label { margin: 0; } diff --git a/frontend/src/components/SearchSourcesPanel.jsx b/frontend/src/components/SearchSourcesPanel.jsx index cac4d99..2ace036 100644 --- a/frontend/src/components/SearchSourcesPanel.jsx +++ b/frontend/src/components/SearchSourcesPanel.jsx @@ -6,7 +6,7 @@ import PzBusy from './PzBusy.jsx' import { t, dateLocale } from '../i18n/index.js' import { fetchSearchSources, searchSource } from '../api/searchSourcesApi.js' import { SEARCH_LINKS, SEARCH_LINK_REGIONS } from '../data/searchLinks.js' -import { changesFromResult } from '../utils/searchResultChanges.js' +import { changesFromResult, personFromResult } from '../utils/searchResultChanges.js' import { fullNameLabel } from '../utils/personName.js' // Ce que chaque source interrogée couvre et ce qui part vers elle : affiché avant qu'on coche @@ -90,9 +90,23 @@ function withEmphasis(text) { return <>{before}{emphasized}{after} } +// Mêmes libellés que « Ajouter quelqu'un » : la nouvelle personne est parent, enfant… de quelqu'un de l'arbre +const RELATION_TABS = { + parent: t('Parent'), + child: t('Enfant'), + spouse: t('Conjoint·e'), + sibling: t('Frère·Sœur'), +} +const RELATION_LABELS = { + parent: t('Parent de'), + child: t('Enfant de'), + spouse: t('Conjoint·e de'), + sibling: t('Frère·Sœur de'), +} + const plain = (value) => String(value || '').normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase().trim() -function SearchSourcesPanel({ visible, treeId, token, persons = [], canApply = false, onApply, onClose }) { +function SearchSourcesPanel({ visible, treeId, token, persons = [], canApply = false, onApply, onCreate, createError = '', onClose }) { const [lastName, setLastName] = useState('') const [firstName, setFirstName] = useState('') const [birthYear, setBirthYear] = useState('') @@ -179,7 +193,25 @@ function SearchSourcesPanel({ visible, treeId, token, persons = [], canApply = f .slice(0, 6) }, [picker, persons]) + // Le plus souvent, la personne trouvée n'est pas encore dans l'arbre : on la crée, rattachée à un proche + const handleCreate = async () => { + const { key, result, sourceId, relationType, relative } = picker + if (!relative) return + setPicker((prev) => ({ ...prev, saving: true, failed: false })) + const created = await onCreate?.(personFromResult(result, SOURCE_INFO[sourceId].name, { personId: relative.id, type: relationType })) + if (created) { + setApplied((prev) => ({ ...prev, [key]: { name: `${result.firstName} ${result.lastName}`.trim(), created: true } })) + setPicker(null) + } else { + setPicker((prev) => ({ ...prev, saving: false, failed: true })) + } + } + const handlePick = async (person) => { + if (picker.mode === 'new') { + setPicker((prev) => ({ ...prev, relative: person })) + return + } const { key, result, sourceId } = picker const changes = changesFromResult(person, result, SOURCE_INFO[sourceId].name) if (!changes) { @@ -189,7 +221,7 @@ function SearchSourcesPanel({ visible, treeId, token, persons = [], canApply = f setPicker((prev) => ({ ...prev, saving: true })) try { await onApply?.(person.id, changes) - setApplied((prev) => ({ ...prev, [key]: fullNameLabel(person) })) + setApplied((prev) => ({ ...prev, [key]: { name: fullNameLabel(person), created: false } })) setPicker(null) } catch { setPicker((prev) => ({ ...prev, saving: false, failed: true })) @@ -314,16 +346,33 @@ function SearchSourcesPanel({ visible, treeId, token, persons = [], canApply = f {t('Voir la source')}