mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-06 23:32:08 +00:00
Plusieurs prénoms dans le champ Prénom : « Paul, Marie, Jean » crée trois enfants (#203)
Pour un enfant ou un frère, une virgule sépare les prénoms : une personne par prénom, mêmes liens, nom et dates pour chacune (vides et doublons ignorés, 20 au plus). Parent et conjoint gardent un seul prénom. Vaut pour le propriétaire, la famille (brouillons) et la démo ; la bulle dit « Paul, Marie et Jean … sont sur l'arbre ». Sur téléphone, aucune fiche ne s'ouvre et la caméra cadre toute la fratrie (focusPersons dans Galaxy, qui attend la remise en place de l'arbre). person_added porte batch. Le corps de l'ajout passe dans addOne : relire avec git diff -w. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e596887a44
commit
1565552141
5 files changed
+269
-162
No files matched your search
+192
-162
@@ -67,6 +67,7 @@ import { useEditMode } from './hooks/useEditMode'
|
||||
import { useAnnotations } from './hooks/useAnnotations'
|
||||
import AnnotationToolbar from './components/annotations/AnnotationToolbar'
|
||||
import TextInputOverlay from './components/annotations/TextInputOverlay'
|
||||
import { joinFirstNames, splitFirstNames } from './utils/firstNames.js'
|
||||
import { filiations, getChildren, getParents, getPersonUnions, medias, persons, replaceGraph, unions } from './data/mockData'
|
||||
import { readGedcomFile } from './utils/gedcomText'
|
||||
import { readGedcomBundle, zipErrorMessage } from './utils/gedcomBundle.js'
|
||||
@@ -1242,6 +1243,9 @@ function App() {
|
||||
siblingParentLinks.push(...links)
|
||||
}
|
||||
}
|
||||
// « Paul, Marie, Jean » pour un enfant ou un frère : une personne par prénom, mêmes liens pour chacune
|
||||
const firstNames = splitFirstNames(formData.firstName, formData.relations || [])
|
||||
const batch = firstNames.length > 1 ? { batch: firstNames.length } : {}
|
||||
// Un enfant ajouté à une personne qui n'a qu'un conjoint est l'enfant du couple : il passe par les liens
|
||||
// du couple, comme un frère ou une sœur. Une adoption reste au seul parent choisi.
|
||||
const relationCount = (formData.relations || []).length
|
||||
@@ -1256,191 +1260,217 @@ function App() {
|
||||
}),
|
||||
}
|
||||
|
||||
if (!tree.canEditCurrentTree) {
|
||||
if (isDemoMode) {
|
||||
const personId = makeLocalId('local-person')
|
||||
const birthYear = extractYearFromIsoDate(formData.birthDate)
|
||||
const deathYear = extractYearFromIsoDate(formData.deathDate)
|
||||
const newPerson = {
|
||||
id: personId,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName || '',
|
||||
birthName: formData.birthName || null,
|
||||
nickname: formData.nickname || null,
|
||||
sex: formData.sex || null,
|
||||
birthDate: formData.birthDate || null,
|
||||
deathDate: formData.deathDate || null,
|
||||
birthYear,
|
||||
deathYear,
|
||||
isAlive: !deathYear,
|
||||
photo: null,
|
||||
frameType: 'round',
|
||||
}
|
||||
const addOne = async (formData) => {
|
||||
if (!tree.canEditCurrentTree) {
|
||||
if (isDemoMode) {
|
||||
const personId = makeLocalId('local-person')
|
||||
const birthYear = extractYearFromIsoDate(formData.birthDate)
|
||||
const deathYear = extractYearFromIsoDate(formData.deathDate)
|
||||
const newPerson = {
|
||||
id: personId,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName || '',
|
||||
birthName: formData.birthName || null,
|
||||
nickname: formData.nickname || null,
|
||||
sex: formData.sex || null,
|
||||
birthDate: formData.birthDate || null,
|
||||
deathDate: formData.deathDate || null,
|
||||
birthYear,
|
||||
deathYear,
|
||||
isAlive: !deathYear,
|
||||
photo: null,
|
||||
frameType: 'round',
|
||||
}
|
||||
|
||||
replaceGraph({ persons: [...persons, newPerson] })
|
||||
replaceGraph({ persons: [...persons, newPerson] })
|
||||
|
||||
for (const rel of formData.relations) {
|
||||
if (rel.type === 'spouse') {
|
||||
const existsUnion = unions.some((union) => (
|
||||
(String(union.partner1Id) === String(rel.personId) && String(union.partner2Id) === String(personId))
|
||||
|| (String(union.partner1Id) === String(personId) && String(union.partner2Id) === String(rel.personId))
|
||||
))
|
||||
if (!existsUnion) {
|
||||
unions.push({
|
||||
id: makeLocalId('local-union'),
|
||||
partner1Id: rel.personId,
|
||||
partner2Id: personId,
|
||||
unionType: 'mariage',
|
||||
startYear: null,
|
||||
endYear: null,
|
||||
displayOrder: unions.length + 1,
|
||||
for (const rel of formData.relations) {
|
||||
if (rel.type === 'spouse') {
|
||||
const existsUnion = unions.some((union) => (
|
||||
(String(union.partner1Id) === String(rel.personId) && String(union.partner2Id) === String(personId))
|
||||
|| (String(union.partner1Id) === String(personId) && String(union.partner2Id) === String(rel.personId))
|
||||
))
|
||||
if (!existsUnion) {
|
||||
unions.push({
|
||||
id: makeLocalId('local-union'),
|
||||
partner1Id: rel.personId,
|
||||
partner2Id: personId,
|
||||
unionType: 'mariage',
|
||||
startYear: null,
|
||||
endYear: null,
|
||||
displayOrder: unions.length + 1,
|
||||
})
|
||||
}
|
||||
} else if (rel.type === 'parent') {
|
||||
filiations.push({
|
||||
id: makeLocalId('local-filiation'),
|
||||
childId: rel.personId,
|
||||
parentId: personId,
|
||||
unionId: null,
|
||||
parentageType: rel.adopted ? 'adoption' : 'biologique',
|
||||
displayOrder: filiations.length + 1,
|
||||
})
|
||||
} else if (rel.type === 'child') {
|
||||
filiations.push({
|
||||
id: makeLocalId('local-filiation'),
|
||||
childId: personId,
|
||||
parentId: rel.personId,
|
||||
unionId: null,
|
||||
parentageType: rel.adopted ? 'adoption' : 'biologique',
|
||||
displayOrder: filiations.length + 1,
|
||||
})
|
||||
}
|
||||
} else if (rel.type === 'parent') {
|
||||
filiations.push({
|
||||
id: makeLocalId('local-filiation'),
|
||||
childId: rel.personId,
|
||||
parentId: personId,
|
||||
unionId: null,
|
||||
parentageType: rel.adopted ? 'adoption' : 'biologique',
|
||||
displayOrder: filiations.length + 1,
|
||||
})
|
||||
} else if (rel.type === 'child') {
|
||||
}
|
||||
|
||||
for (const link of siblingParentLinks) {
|
||||
const alreadyLinked = filiations.some((filiation) => (
|
||||
String(filiation.childId) === String(personId)
|
||||
&& (link.unionId
|
||||
? String(filiation.unionId) === link.unionId
|
||||
: String(filiation.parentId) === link.parentId)
|
||||
))
|
||||
if (alreadyLinked) continue
|
||||
filiations.push({
|
||||
id: makeLocalId('local-filiation'),
|
||||
childId: personId,
|
||||
parentId: rel.personId,
|
||||
unionId: null,
|
||||
parentageType: rel.adopted ? 'adoption' : 'biologique',
|
||||
parentId: link.unionId ? null : link.parentId,
|
||||
unionId: link.unionId,
|
||||
parentageType: 'biologique',
|
||||
displayOrder: filiations.length + 1,
|
||||
})
|
||||
}
|
||||
|
||||
tree.bumpGraphRevision()
|
||||
tree.setSelectedPerson({ ...newPerson })
|
||||
}
|
||||
|
||||
for (const link of siblingParentLinks) {
|
||||
const alreadyLinked = filiations.some((filiation) => (
|
||||
String(filiation.childId) === String(personId)
|
||||
&& (link.unionId
|
||||
? String(filiation.unionId) === link.unionId
|
||||
: String(filiation.parentId) === link.parentId)
|
||||
))
|
||||
if (alreadyLinked) continue
|
||||
filiations.push({
|
||||
id: makeLocalId('local-filiation'),
|
||||
childId: personId,
|
||||
parentId: link.unionId ? null : link.parentId,
|
||||
unionId: link.unionId,
|
||||
parentageType: 'biologique',
|
||||
displayOrder: filiations.length + 1,
|
||||
})
|
||||
}
|
||||
|
||||
tree.bumpGraphRevision()
|
||||
tree.setSelectedPerson({ ...newPerson })
|
||||
const draft = editMode.restoreDraft()
|
||||
const ref = `tmp:${makeLocalId('personne')}`
|
||||
const addedPersons = [
|
||||
...(draft.addedPersons || []),
|
||||
{
|
||||
ref,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
birthName: formData.birthName || null,
|
||||
nickname: formData.nickname || null,
|
||||
sex: formData.sex || null,
|
||||
birthDate: formData.birthDate || null,
|
||||
deathDate: formData.deathDate || null,
|
||||
isDeceased: Boolean(formData.isDeceased),
|
||||
birthPlace: formData.birthPlace || null,
|
||||
deathPlace: formData.deathPlace || null,
|
||||
toVerify: Boolean(formData.toVerify),
|
||||
notes: formData.notes || null,
|
||||
relations: (formData.relations || []).map((rel) => ({ type: rel.type, personId: rel.personId, adopted: Boolean(rel.adopted) })),
|
||||
siblingParentLinks,
|
||||
},
|
||||
]
|
||||
editMode.updateDraft({ addedPersons })
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'family', relations: relationCount, tree_id: opaqueTreeId, ...batch })
|
||||
// La personne apparaît en fantôme « Brouillon » : la famille peut en ajouter d'autres, puis envoyer une
|
||||
// fois avec le bouton « Envoyer » de la barre (Patch, 25/10). Avant, la fenêtre d'envoi s'ouvrait d'office,
|
||||
// seule façon de montrer qu'un ajout invisible comptait.
|
||||
setAddPersonPanelVisible(false)
|
||||
setDraftNotice(t("{name} est sur l'arbre, en brouillon. Envoyez quand vous avez fini.", { name: [formData.firstName, formData.lastName].filter(Boolean).join(' ') || t('Nouvelle personne') }))
|
||||
return { proposed: true, id: isDemoMode ? null : `ghost:brouillon:${ref}`, demoPerson: isDemoMode ? persons[persons.length - 1] : null }
|
||||
}
|
||||
|
||||
const draft = editMode.restoreDraft()
|
||||
const addedPersons = [
|
||||
...(draft.addedPersons || []),
|
||||
{
|
||||
ref: `tmp:${makeLocalId('personne')}`,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
birthName: formData.birthName || null,
|
||||
nickname: formData.nickname || null,
|
||||
sex: formData.sex || null,
|
||||
birthDate: formData.birthDate || null,
|
||||
deathDate: formData.deathDate || null,
|
||||
isDeceased: Boolean(formData.isDeceased),
|
||||
birthPlace: formData.birthPlace || null,
|
||||
deathPlace: formData.deathPlace || null,
|
||||
toVerify: Boolean(formData.toVerify),
|
||||
notes: formData.notes || null,
|
||||
relations: (formData.relations || []).map((rel) => ({ type: rel.type, personId: rel.personId, adopted: Boolean(rel.adopted) })),
|
||||
siblingParentLinks,
|
||||
},
|
||||
]
|
||||
editMode.updateDraft({ addedPersons })
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'family', relations: relationCount, tree_id: opaqueTreeId })
|
||||
// La personne apparaît en fantôme « Brouillon » : la famille peut en ajouter d'autres, puis envoyer une
|
||||
// fois avec le bouton « Envoyer » de la barre (Patch, 25/10). Avant, la fenêtre d'envoi s'ouvrait d'office,
|
||||
// seule façon de montrer qu'un ajout invisible comptait.
|
||||
setAddPersonPanelVisible(false)
|
||||
setDraftNotice(t("{name} est sur l'arbre, en brouillon. Envoyez quand vous avez fini.", { name: [formData.firstName, formData.lastName].filter(Boolean).join(' ') || t('Nouvelle personne') }))
|
||||
return { proposed: true }
|
||||
}
|
||||
if (!auth.userAuth.token) return
|
||||
|
||||
if (!auth.userAuth.token) return
|
||||
|
||||
const newPerson = await createPerson(
|
||||
tree.treeContext.treeId,
|
||||
{
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
birthName: formData.birthName,
|
||||
nickname: formData.nickname,
|
||||
sex: formData.sex || null,
|
||||
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
|
||||
)
|
||||
|
||||
// Créer les liens de relation
|
||||
const linkedParentIds = new Set()
|
||||
for (const rel of formData.relations) {
|
||||
if (rel.type === 'child') {
|
||||
linkedParentIds.add(String(rel.personId))
|
||||
await createParentChildLink(
|
||||
tree.treeContext.treeId,
|
||||
{ parentPersonId: rel.personId, childPersonId: newPerson.id, parentageType: rel.adopted ? 'adoption' : 'biologique', displayOrder: 1 },
|
||||
auth.userAuth.token
|
||||
)
|
||||
} else if (rel.type === 'parent') {
|
||||
await createParentChildLink(
|
||||
tree.treeContext.treeId,
|
||||
{ parentPersonId: newPerson.id, childPersonId: rel.personId, parentageType: rel.adopted ? 'adoption' : 'biologique', displayOrder: 1 },
|
||||
auth.userAuth.token
|
||||
)
|
||||
} else if (rel.type === 'spouse') {
|
||||
await createUnion(
|
||||
tree.treeContext.treeId,
|
||||
{ partner1PersonId: rel.personId, partner2PersonId: newPerson.id, unionType: 'mariage', displayOrder: 1 },
|
||||
auth.userAuth.token
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const link of siblingParentLinks) {
|
||||
if (linkedParentIds.has(link.parentId)) continue
|
||||
linkedParentIds.add(link.parentId)
|
||||
await createParentChildLink(
|
||||
const newPerson = await createPerson(
|
||||
tree.treeContext.treeId,
|
||||
{
|
||||
parentPersonId: link.parentId,
|
||||
childPersonId: newPerson.id,
|
||||
viaUnionId: link.unionId,
|
||||
parentageType: 'biologique',
|
||||
displayOrder: 1,
|
||||
firstName: formData.firstName,
|
||||
lastName: formData.lastName,
|
||||
birthName: formData.birthName,
|
||||
nickname: formData.nickname,
|
||||
sex: formData.sex || null,
|
||||
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
|
||||
)
|
||||
|
||||
// Créer les liens de relation
|
||||
const linkedParentIds = new Set()
|
||||
for (const rel of formData.relations) {
|
||||
if (rel.type === 'child') {
|
||||
linkedParentIds.add(String(rel.personId))
|
||||
await createParentChildLink(
|
||||
tree.treeContext.treeId,
|
||||
{ parentPersonId: rel.personId, childPersonId: newPerson.id, parentageType: rel.adopted ? 'adoption' : 'biologique', displayOrder: 1 },
|
||||
auth.userAuth.token
|
||||
)
|
||||
} else if (rel.type === 'parent') {
|
||||
await createParentChildLink(
|
||||
tree.treeContext.treeId,
|
||||
{ parentPersonId: newPerson.id, childPersonId: rel.personId, parentageType: rel.adopted ? 'adoption' : 'biologique', displayOrder: 1 },
|
||||
auth.userAuth.token
|
||||
)
|
||||
} else if (rel.type === 'spouse') {
|
||||
await createUnion(
|
||||
tree.treeContext.treeId,
|
||||
{ partner1PersonId: rel.personId, partner2PersonId: newPerson.id, unionType: 'mariage', displayOrder: 1 },
|
||||
auth.userAuth.token
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const link of siblingParentLinks) {
|
||||
if (linkedParentIds.has(link.parentId)) continue
|
||||
linkedParentIds.add(link.parentId)
|
||||
await createParentChildLink(
|
||||
tree.treeContext.treeId,
|
||||
{
|
||||
parentPersonId: link.parentId,
|
||||
childPersonId: newPerson.id,
|
||||
viaUnionId: link.unionId,
|
||||
parentageType: 'biologique',
|
||||
displayOrder: 1,
|
||||
},
|
||||
auth.userAuth.token
|
||||
)
|
||||
}
|
||||
|
||||
// Mettre à jour le draft avec la nouvelle personne
|
||||
editMode.updateDraft({
|
||||
addedPersons: [...(editMode.restoreDraft().addedPersons || []), newPerson.id],
|
||||
})
|
||||
|
||||
await tree.refreshCurrentTreeGraph()
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'manager', relations: relationCount, tree_id: opaqueTreeId, ...batch })
|
||||
return newPerson
|
||||
}
|
||||
|
||||
// Mettre à jour le draft avec la nouvelle personne
|
||||
editMode.updateDraft({
|
||||
addedPersons: [...(editMode.restoreDraft().addedPersons || []), newPerson.id],
|
||||
})
|
||||
if (firstNames.length < 2) return await addOne(formData)
|
||||
|
||||
await tree.refreshCurrentTreeGraph()
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'manager', relations: relationCount, tree_id: opaqueTreeId })
|
||||
return newPerson
|
||||
const added = []
|
||||
for (const firstName of firstNames) {
|
||||
const result = await addOne({ ...formData, firstName })
|
||||
if (!result) break
|
||||
added.push(result)
|
||||
}
|
||||
if (!added.length) return null
|
||||
const addedIds = added.map((result) => result.demoPerson?.id ?? result.id).filter((id) => id != null)
|
||||
if (added[0].proposed) {
|
||||
const names = joinFirstNames(firstNames.slice(0, added.length), t('et'))
|
||||
setDraftNotice(t("{names} sont sur l'arbre, en brouillon. Envoyez quand vous avez fini.", { names: [names, formData.lastName].filter(Boolean).join(' ') }))
|
||||
}
|
||||
// Sur téléphone, une fiche ouverte couvrirait la fratrie : pas de fiche, la caméra les cadre tous
|
||||
if (window.matchMedia?.('(max-width: 768px)').matches) {
|
||||
tree.setSelectedPerson(null)
|
||||
if (addedIds.length > 1) requestAnimationFrame(() => galaxyZoomApiRef.current?.focusPersons?.(addedIds))
|
||||
} else if (added[0].demoPerson) {
|
||||
tree.setSelectedPerson({ ...added[0].demoPerson })
|
||||
}
|
||||
return added[0]
|
||||
} catch (err) {
|
||||
setAddPersonError(err.message || t("Erreur lors de l'ajout de la personne"))
|
||||
} finally {
|
||||
|
||||
@@ -96,6 +96,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
}, [])
|
||||
useEffect(() => cancelTabsRequest, [cancelTabsRequest])
|
||||
const pendingFocusRef = useRef(null) // personne cherchée dans un autre onglet, visée après le changement
|
||||
const pendingFrameRef = useRef(null) // personnes à cadrer une fois l'arbre reposé (ajout de plusieurs prénoms)
|
||||
// Lien d'un onglet (?famille=<charnière>) : ouvert dès que l'arbre et ses onglets sont connus, et de nouveau si
|
||||
// la personne de départ arrive après (visiteur), tant qu'on n'a pas soi-même changé d'onglet
|
||||
const linkFamilyRef = useRef(initialFamilyKey != null ? String(initialFamilyKey) : null)
|
||||
@@ -516,6 +517,26 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
const screenY = phone ? rect.height * 0.22 : rect.height / 2
|
||||
targetTransformRef.current = { scale, x: screenX - pos.cx * scale, y: screenY - pos.cy * scale }
|
||||
},
|
||||
// Plusieurs personnes venant d'être ajoutées : toutes dans le champ, au-dessus de la barre du bas. Si l'arbre
|
||||
// n'est pas encore reposé avec elles, le cadrage attend la prochaine mise en place.
|
||||
focusPersons: (personIds) => {
|
||||
const canvas = canvasRef.current
|
||||
const wanted = new Set(personIds.map((id) => `p-${id}`))
|
||||
const nodes = (layoutDataRef.current?.children || []).filter((n) => wanted.has(n.id))
|
||||
if (!canvas || nodes.length < personIds.length) { pendingFrameRef.current = personIds; return }
|
||||
pendingFrameRef.current = null
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const now = performance.now()
|
||||
const centers = nodes.map((n) => getAnimatedPos(n, now))
|
||||
const xs = centers.map((p) => p.cx)
|
||||
const ys = centers.map((p) => p.cy)
|
||||
const margin = 140 // un portrait et son prénom
|
||||
const fit = Math.min((rect.width - 32) / (Math.max(...xs) - Math.min(...xs) + margin), (rect.height * 0.6) / (Math.max(...ys) - Math.min(...ys) + margin))
|
||||
const scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, Math.min(Math.max(targetTransformRef.current.scale, 1), fit)))
|
||||
const cx = (Math.min(...xs) + Math.max(...xs)) / 2
|
||||
const cy = (Math.min(...ys) + Math.max(...ys)) / 2
|
||||
targetTransformRef.current = { scale, x: rect.width / 2 - cx * scale, y: rect.height * 0.42 - cy * scale }
|
||||
},
|
||||
getScale: () => transformRef.current.scale,
|
||||
moveSibling,
|
||||
getSiblingPlace,
|
||||
@@ -849,6 +870,11 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
pendingFocusRef.current = null
|
||||
requestAnimationFrame(() => zoomApiRef?.current?.focusPerson?.(id))
|
||||
}
|
||||
if (pendingFrameRef.current) {
|
||||
const ids = pendingFrameRef.current
|
||||
pendingFrameRef.current = null
|
||||
requestAnimationFrame(() => zoomApiRef?.current?.focusPersons?.(ids))
|
||||
}
|
||||
}, [graphRevision, activeKey, familyTabs, selfPersonId, ghosts, requestTabs, cancelTabsRequest])
|
||||
|
||||
// Onglet ouvert, remonté pour l'adresse de la page et le lien de partage ; null pour la famille de départ
|
||||
|
||||
@@ -44,6 +44,9 @@ export default {
|
||||
'Votre famille': 'Your family',
|
||||
'Faire le mien': 'Make my own',
|
||||
'un arbre pour votre famille, gratuit': 'a tree for your family, free',
|
||||
// Plusieurs prénoms dans le champ Prénom
|
||||
'et': 'and',
|
||||
"{names} sont sur l'arbre, en brouillon. Envoyez quand vous avez fini.": '{names} are on the tree, as a draft. Send when you are done.',
|
||||
'Lien ajouté à la contribution': 'Link added to your contribution',
|
||||
'Lien familial ajouté': 'Family link added',
|
||||
'Lien modifié (démo locale)': 'Link changed (local demo)',
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// « Paul, Marie, Jean » dans le champ Prénom d'un enfant ou d'un frère : une personne par prénom.
|
||||
// Sans virgule, ou pour un parent ou un conjoint, le champ reste un seul prénom.
|
||||
const MAX_NAMES = 20
|
||||
|
||||
export function splitFirstNames(value, relations = []) {
|
||||
const text = String(value || '')
|
||||
if (!text.includes(',')) return [text]
|
||||
if (!relations.some((rel) => rel.type === 'child' || rel.type === 'sibling')) return [text]
|
||||
const names = []
|
||||
for (const part of text.split(',')) {
|
||||
const name = part.trim()
|
||||
if (name && !names.includes(name)) names.push(name)
|
||||
}
|
||||
return names.length > 1 ? names.slice(0, MAX_NAMES) : [names[0] || text]
|
||||
}
|
||||
|
||||
// « Paul, Marie et Jean »
|
||||
export function joinFirstNames(names, and) {
|
||||
if (names.length < 2) return names[0] || ''
|
||||
return `${names.slice(0, -1).join(', ')} ${and} ${names[names.length - 1]}`
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { joinFirstNames, splitFirstNames } from './firstNames.js'
|
||||
|
||||
const child = [{ type: 'child', personId: 1 }]
|
||||
|
||||
describe('plusieurs prénoms dans le champ Prénom', () => {
|
||||
it("découpe pour un enfant ou un frère", () => {
|
||||
expect(splitFirstNames('Paul, Marie, Jean', child)).toEqual(['Paul', 'Marie', 'Jean'])
|
||||
expect(splitFirstNames('Paul,Marie', [{ type: 'sibling', personId: 2 }])).toEqual(['Paul', 'Marie'])
|
||||
})
|
||||
|
||||
it('laisse un seul prénom sans virgule, ou pour un parent ou un conjoint', () => {
|
||||
expect(splitFirstNames('Jean-Pierre', child)).toEqual(['Jean-Pierre'])
|
||||
expect(splitFirstNames('Paul, Marie', [{ type: 'parent', personId: 1 }])).toEqual(['Paul, Marie'])
|
||||
expect(splitFirstNames('Paul, Marie', [])).toEqual(['Paul, Marie'])
|
||||
})
|
||||
|
||||
it('ignore les vides et les doublons', () => {
|
||||
expect(splitFirstNames(' Paul , , Marie, Paul ', child)).toEqual(['Paul', 'Marie'])
|
||||
expect(splitFirstNames('Paul,', child)).toEqual(['Paul'])
|
||||
})
|
||||
|
||||
it('les relie pour le message', () => {
|
||||
expect(joinFirstNames(['Paul', 'Marie', 'Jean'], 'et')).toBe('Paul, Marie et Jean')
|
||||
expect(joinFirstNames(['Paul'], 'et')).toBe('Paul')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user