diff --git a/frontend/src/components/PersonCard.jsx b/frontend/src/components/PersonCard.jsx index 8efd1d5..f0a04de 100644 --- a/frontend/src/components/PersonCard.jsx +++ b/frontend/src/components/PersonCard.jsx @@ -21,6 +21,7 @@ import DateField from './DateField.jsx' import { runOnce } from '../utils/saveOnLeave.js' import { normalizeSex, SEX_CHOICES } from '../utils/personSex.js' import { ProposalBanner } from './ProposalCard.jsx' +import { initialsOf } from '../utils/initial.js' // Aucune modification en cours (référence stable) const NO_EDITS = Object.freeze({}) @@ -602,19 +603,20 @@ const PersonCard = ({ const visualMedias = personMedias.filter((m) => m.type === 'photo' || m.type === 'video') const otherMedias = personMedias.filter((m) => !['citation', 'audio', 'photo', 'video'].includes(m.type) || (m.type === 'citation' && m !== quoteMedia)) - const initials = `${person.firstName?.[0] || ''}${person.lastName?.[0] || ''}` || '?' + const initials = initialsOf(person) const lifeLine = (() => { const born = person.birthYear || (person.birthDate ? String(person.birthDate).slice(0, 4) : '') const died = person.deathYear || (person.deathDate ? String(person.deathDate).slice(0, 4) : '') const parts = [] - if (born) parts.push(`${born}${person.birthPlace ? `, ${formatPlace(person.birthPlace)}` : ''}`) + // : un lieu en arabe ou en hébreu ne retourne pas l'ordre de la ligne (« 1980, الدار البيضاء · 46 ans ») + if (born) parts.push(person.birthPlace ? <>{born}, {formatPlace(person.birthPlace)} : born) if (!person.isAlive) parts.push(died || '?') - return parts.join(' – ') + return parts.length ? parts.reduce((line, part) => <>{line} – {part}) : null })() const ageLabel = age === null ? null : person.isAlive ? t('{age} ans', { age }) : t('{age} ans au décès', { age }) // Une seule ligne sous le nom : surnom, repères de vie, âge, séparés par un point médian const metaItems = [ - person.nickname && { key: 'nick', node: {person.nickname}, nowrap: true }, + person.nickname && { key: 'nick', node: {person.nickname}, nowrap: true }, lifeLine && { key: 'life', node: lifeLine }, ageLabel && { key: 'age', node: ageLabel, nowrap: true }, ].filter(Boolean) diff --git a/frontend/src/components/PhotoStartPanel.jsx b/frontend/src/components/PhotoStartPanel.jsx index ed3d82e..7df1f2d 100644 --- a/frontend/src/components/PhotoStartPanel.jsx +++ b/frontend/src/components/PhotoStartPanel.jsx @@ -5,6 +5,7 @@ import PzBusy from './PzBusy.jsx' import { t } from '../i18n/index.js' import { matchPhotoFiles } from '../utils/photoImport.js' import { materializeFile } from '../utils/zipReader.js' +import { initialsOf } from '../utils/initial.js' function withEmphasis(text) { const [before, emphasized = '', after = ''] = text.split(/<\/?em>/) @@ -21,10 +22,6 @@ function plural(n, one, many, vars = {}) { return n === 1 ? t(one, vars) : t(many, { n, ...vars }) } -function initialsOf(person) { - return `${person.firstName?.[0] || ''}${person.lastName?.[0] || ''}`.toUpperCase() || '?' -} - // Dossier où le logiciel d'origine rangeait la première photo citée, pour aider à le retrouver function folderOf(filePath) { const parts = String(filePath || '').split(/([\\/])/) diff --git a/frontend/src/components/PhotoStartPanel.test.jsx b/frontend/src/components/PhotoStartPanel.test.jsx new file mode 100644 index 0000000..82ca878 --- /dev/null +++ b/frontend/src/components/PhotoStartPanel.test.jsx @@ -0,0 +1,22 @@ +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it } from 'vitest' +import PhotoStartPanel from './PhotoStartPanel.jsx' + +// Les visages à compléter, sans aperçu : chaque portrait vide montre les initiales de la personne +describe('PhotoStartPanel, visages sans photo', () => { + it('affiche les initiales, une seule pour un nom chinois', () => { + const html = renderToStaticMarkup( + {}} + />, + ) + expect(html).toContain('>JD') + expect(html).toContain('>王') + }) +}) diff --git a/frontend/src/components/ProposalCard.jsx b/frontend/src/components/ProposalCard.jsx index 3288eea..8cc8c23 100644 --- a/frontend/src/components/ProposalCard.jsx +++ b/frontend/src/components/ProposalCard.jsx @@ -4,6 +4,7 @@ import { t, dateLocale } from '../i18n/index.js' import { firstNameLabel } from '../utils/personName.js' import PzBusy from './PzBusy' import './ProposalCard.css' +import { initialsOf } from '../utils/initial.js' // Proposition pas encore relue, ouverte depuis son fantôme sur l'arbre (1.5.1). Qui gère l'arbre l'accepte ou la // refuse sur place ; la famille y lit qu'elle attend. Une proposition se relit en entier : « Voir le détail » @@ -73,7 +74,7 @@ export default function ProposalCard({ person, summary = '', review = null, onCl const ghost = person?.ghost const years = [person?.birthYear, person?.deathYear].filter(Boolean).join(' – ') const name = [firstNameLabel(person), person?.lastName].filter(Boolean).join(' ') - const initials = `${(person?.firstName || '?')[0]}${(person?.lastName || '')[0] || ''}`.toUpperCase() + const initials = initialsOf(person) return (