From 68ac2fa8f84b453625bff7ca9a32a717dfa0858d Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Mon, 19 Oct 2026 01:22:07 +0200 Subject: [PATCH] fix(vie): une union sans date a sa place logique, plus apres le deces du conjoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un mariage sans date tombait tout en bas de l'onglet Vie, apres le deces du conjoint, ce qui ne voulait rien dire. Il se place maintenant juste avant le premier enfant du couple, sinon avant la fin de l'union ou le deces du conjoint. Aucune annee n'est inventee : colonne vide et « date inconnue ». Seul ce qui n'a aucun repere reste en bas, et tout moment sans date le dit. Co-Authored-By: Claude Opus 5.5 --- frontend/src/components/PersonCard.jsx | 2 ++ frontend/src/utils/lifeTimeline.js | 22 +++++++++++++++------- frontend/src/utils/lifeTimeline.test.js | 24 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/PersonCard.jsx b/frontend/src/components/PersonCard.jsx index b4cd7c0..1b8e4ba 100644 --- a/frontend/src/components/PersonCard.jsx +++ b/frontend/src/components/PersonCard.jsx @@ -436,6 +436,8 @@ const PersonCard = ({ unions: personUnions.map((union) => ({ ...union, partner: persons.find((p) => p.id === (union.partner1Id === person.id ? union.partner2Id : union.partner1Id)) || null, + // Enfants du couple : placent dans la frise une union dont la date manque + childIds: filiations.filter((f) => f.unionId && String(f.unionId) === String(union.id)).map((f) => f.childId), })), } diff --git a/frontend/src/utils/lifeTimeline.js b/frontend/src/utils/lifeTimeline.js index ed684f9..5bb3078 100644 --- a/frontend/src/utils/lifeTimeline.js +++ b/frontend/src/utils/lifeTimeline.js @@ -107,7 +107,7 @@ const siblingWord = (p) => { /** * @param person personne de la fiche (birthDate, deathDate, birthPlace, birthYear, deathYear) - * @param family { parents, children, siblings, unions: [{ id, partner, startYear, endYear, unionType }] } + * @param family { parents, children, siblings, unions: [{ id, partner, startYear, endYear, unionType, childIds }] } * @param life réponse de GET /trees/:id/persons/:personId/life, ou null tant qu'elle n'est pas là * @returns entrées triées { key, year, kind: 'mile' | 'minor' | 'fam', title, meta, note, sources, mediaIds, * event (moment modifiable), milestone ({ type, unionId } d'une naissance, d'un mariage, d'un décès) } @@ -144,10 +144,18 @@ export function buildLifeTimeline(person, family = {}, life = null) { const partnerName = union.partner ? fullName(union.partner) : t('Conjoint inconnu') const place = formatPlace(detail.startPlace || '') const extra = sourcesOf('marriage', union.id) + // Union sans date : à sa place logique, juste avant le premier enfant du couple, sinon avant la fin de + // l'union ou le décès du conjoint. Aucune année n'est inventée, la colonne reste vide. + const childIds = new Set((union.childIds || []).map(String)) + const childYears = (family.children || []).filter((c) => childIds.has(String(c.id))).map((c) => yearOf(c.birthDate) ?? c.birthYear).filter(within) + const partnerDeath = union.partner ? yearOf(union.partner.deathDate) ?? union.partner.deathYear : null + const anchor = year ? null : (childYears.length ? Math.min(...childYears) : null) ?? union.endYear ?? (within(partnerDeath) ? partnerDeath : null) out.push({ - key: `union-${union.id}`, year, order: 2, kind: 'mile', + key: `union-${union.id}`, year, sortYear: anchor, order: year ? 2 : 1, kind: 'mile', title: union.unionType === 'union' ? t('Union avec {name}', { name: partnerName }) : t('Épouse {name}', { name: partnerName }), - meta: joinMeta(place, detail.startDatePrecision ? describeDate(detail.startDate, null, detail.startDatePrecision) : '', ageAt(birthYear, year)), + meta: year + ? joinMeta(place, detail.startDatePrecision ? describeDate(detail.startDate, null, detail.startDatePrecision) : '', ageAt(birthYear, year)) + : joinMeta(place, t('date inconnue')), note: extra?.notes || '', sources: extra?.sources || '', ...milestoneFields(extra, { type: 'marriage', unionId: union.id }), }) @@ -171,7 +179,7 @@ export function buildLifeTimeline(person, family = {}, life = null) { } out.push({ key: `event-${event.id}`, year, order: 1, kind: 'minor', title, - meta: joinMeta(formatPlace(event.place || ''), describeDate(event.date, event.dateEnd, event.datePrecision), ageAt(birthYear, year)), + meta: joinMeta(formatPlace(event.place || ''), year ? describeDate(event.date, event.dateEnd, event.datePrecision) : t('date inconnue'), ageAt(birthYear, year)), note: event.notes || '', sources: event.sources || '', mediaIds: event.mediaIds || [], // Seuls les moments propres à la personne se modifient depuis l'onglet (pas ceux d'une union) @@ -206,9 +214,9 @@ export function buildLifeTimeline(person, family = {}, life = null) { }) } - // Les entrées sans date restent en bas, dans l'ordre où elles sont arrivées + // Une union sans date se range à sa place logique (sortYear) ; le reste sans date va en bas, dans l'ordre d'arrivée return out .map((entry, index) => ({ ...entry, index })) - .sort((a, b) => (a.year ?? Infinity) - (b.year ?? Infinity) || a.order - b.order || a.index - b.index) - .map(({ order, index, ...entry }) => entry) // eslint-disable-line no-unused-vars + .sort((a, b) => (a.year ?? a.sortYear ?? Infinity) - (b.year ?? b.sortYear ?? Infinity) || a.order - b.order || a.index - b.index) + .map(({ order, index, sortYear, ...entry }) => entry) // eslint-disable-line no-unused-vars } diff --git a/frontend/src/utils/lifeTimeline.test.js b/frontend/src/utils/lifeTimeline.test.js index a4b0105..d99a320 100644 --- a/frontend/src/utils/lifeTimeline.test.js +++ b/frontend/src/utils/lifeTimeline.test.js @@ -66,6 +66,30 @@ describe('frise de vie', () => { expect(entries.map((e) => [e.title, e.year, e.meta])).toEqual([['Naissance', 1900, ''], ['Décès', null, 'date inconnue']]) }) + it('union sans date : avant le premier enfant du couple, sinon avant le décès du conjoint, sinon en bas', () => { + const lucie = { id: 1, firstName: 'Lucie', birthYear: 1940 } + const entries = buildLifeTimeline(lucie, { + children: [{ id: 7, firstName: 'Hugo', birthYear: 1966 }, { id: 8, firstName: 'Léa', birthYear: 1981 }], + unions: [ + { id: 'u1', partner: { id: 2, firstName: 'Marc', lastName: 'Roy', deathYear: 2013 }, unionType: 'mariage', childIds: [7] }, + { id: 'u2', partner: { id: 3, firstName: 'Paul', lastName: 'Vidal', deathYear: 2016 }, unionType: 'mariage', childIds: [] }, + { id: 'u3', partner: { id: 4, firstName: 'Yves', lastName: 'Mallet' }, unionType: 'mariage' }, + ], + }, null) + expect(entries.map((e) => e.title)).toEqual([ + 'Naissance', 'Épouse Marc Roy', 'Naissance de Hugo', 'Naissance de Léa', 'Décès de Marc', 'Épouse Paul Vidal', 'Décès de Paul', 'Épouse Yves Mallet', + ]) + // Aucune année inventée : colonne vide, « date inconnue » dit pourquoi + for (const key of ['union-u1', 'union-u2', 'union-u3']) { + expect(entries.find((e) => e.key === key)).toMatchObject({ year: undefined, meta: 'date inconnue' }) + } + expect(entries.find((e) => e.key === 'union-u1')).not.toHaveProperty('sortYear') + }) + + it('un moment sans date dit « date inconnue »', () => { + expect(entries.find((e) => e.title === 'Résidence').meta).toBe('Rue des Écoles · date inconnue') + }) + it('dit les dates comme on les dit', () => { expect(describeDate('1879-01-01', null, 'about')).toBe('vers 1879') expect(describeDate('1879-01-01', null, 'before')).toBe('avant 1879')