Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views. - Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts. - Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface. - Added loading and error states in ContactsListView for better user feedback during data fetching. - Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list. - Updated package.json to include @formkit/auto-animate for improved UI animations.
173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
import { buildVCardFromFullContact } from '@/lib/api/adapters'
|
|
import type { ApiEnrichedContactData } from '@/lib/contacts/discovery-types'
|
|
import type { FullContact } from '@/lib/contacts/types'
|
|
|
|
const REVERSE_LABEL_MAP: Record<string, string> = {
|
|
travail: 'work',
|
|
work: 'work',
|
|
domicile: 'home',
|
|
home: 'home',
|
|
mobile: 'mobile',
|
|
autre: 'other',
|
|
other: 'other',
|
|
personal: 'home',
|
|
}
|
|
|
|
const LABEL_MAP: Record<string, string> = {
|
|
work: 'Travail',
|
|
home: 'Domicile',
|
|
mobile: 'Mobile',
|
|
other: 'Autre',
|
|
linkedin: 'LinkedIn',
|
|
twitter: 'X / Twitter',
|
|
facebook: 'Facebook',
|
|
instagram: 'Instagram',
|
|
github: 'GitHub',
|
|
}
|
|
|
|
function toApiLabel(label?: string): string {
|
|
if (!label) return 'other'
|
|
const key = label.trim().toLowerCase()
|
|
return REVERSE_LABEL_MAP[key] ?? key
|
|
}
|
|
|
|
function fromApiLabel(label?: string): string {
|
|
if (!label) return 'Autre'
|
|
return LABEL_MAP[label.toLowerCase()] ?? label
|
|
}
|
|
|
|
function formatBirthdayForPayload(b?: FullContact['birthday']): string | undefined {
|
|
if (!b?.month && !b?.day && !b?.year) return undefined
|
|
const y = b.year ? String(b.year).padStart(4, '0') : ''
|
|
const m = b.month ? String(b.month).padStart(2, '0') : ''
|
|
const d = b.day ? String(b.day).padStart(2, '0') : ''
|
|
if (y && m && d) return `${y}-${m}-${d}`
|
|
if (m && d) return `--${m}-${d}`
|
|
return undefined
|
|
}
|
|
|
|
export interface ImproveContactPayload {
|
|
first_name: string
|
|
last_name: string
|
|
middle_name?: string
|
|
company?: string
|
|
department?: string
|
|
job_title?: string
|
|
website?: string
|
|
notes?: string
|
|
emails: { value: string; label: string }[]
|
|
phones: { value: string; label: string }[]
|
|
social_profiles?: { value: string; label: string }[]
|
|
addresses?: {
|
|
street?: string
|
|
city?: string
|
|
region?: string
|
|
postal_code?: string
|
|
country?: string
|
|
label: string
|
|
}[]
|
|
birthday?: string
|
|
raw_vcard?: string
|
|
}
|
|
|
|
export function fullContactToImprovePayload(contact: FullContact): ImproveContactPayload {
|
|
return {
|
|
first_name: contact.firstName,
|
|
last_name: contact.lastName,
|
|
middle_name: contact.middleName,
|
|
company: contact.company,
|
|
department: contact.department,
|
|
job_title: contact.jobTitle,
|
|
website: contact.website,
|
|
notes: contact.notes,
|
|
social_profiles: contact.socialProfiles?.map((p) => ({
|
|
value: p.value,
|
|
label: toApiLabel(p.label),
|
|
})),
|
|
emails: contact.emails.map((e) => ({ value: e.value, label: toApiLabel(e.label) })),
|
|
phones: contact.phones.map((p) => ({ value: p.value, label: toApiLabel(p.label) })),
|
|
addresses: contact.addresses?.map((a) => ({
|
|
street: a.street,
|
|
city: a.city,
|
|
region: a.region,
|
|
postal_code: a.postalCode,
|
|
country: a.country,
|
|
label: toApiLabel(a.label),
|
|
})),
|
|
birthday: formatBirthdayForPayload(contact.birthday),
|
|
raw_vcard: buildVCardFromFullContact(contact),
|
|
}
|
|
}
|
|
|
|
export function mergeImprovedContact(
|
|
contact: FullContact,
|
|
data: ApiEnrichedContactData,
|
|
): FullContact {
|
|
const emails = data.emails?.length
|
|
? data.emails.map((e) => ({ value: e.value, label: fromApiLabel(e.label) }))
|
|
: contact.emails
|
|
|
|
const phones = data.phones?.length
|
|
? data.phones.map((p) => ({ value: p.value, label: fromApiLabel(p.label) }))
|
|
: contact.phones
|
|
|
|
const addresses = data.addresses?.length
|
|
? data.addresses.map((a) => ({
|
|
street: a.street,
|
|
city: a.city,
|
|
region: a.region,
|
|
postalCode: a.postal_code,
|
|
country: a.country,
|
|
label: fromApiLabel(a.label),
|
|
}))
|
|
: contact.addresses
|
|
|
|
const socialProfiles = data.social_profiles?.length
|
|
? data.social_profiles.map((p) => ({ value: p.value, label: fromApiLabel(p.label) }))
|
|
: contact.socialProfiles
|
|
|
|
return {
|
|
...contact,
|
|
firstName: data.first_name?.trim() || contact.firstName,
|
|
lastName: data.last_name?.trim() || contact.lastName,
|
|
company: data.company?.trim() || contact.company,
|
|
department: data.department?.trim() || contact.department,
|
|
jobTitle: data.job_title?.trim() || contact.jobTitle,
|
|
website: data.website?.trim() || contact.website,
|
|
notes: data.notes?.trim() || contact.notes,
|
|
emails,
|
|
phones,
|
|
addresses,
|
|
socialProfiles,
|
|
updatedAt: Date.now(),
|
|
}
|
|
}
|
|
|
|
export function improvedDataToDraftFields(data: ApiEnrichedContactData) {
|
|
const items: { id: string; fieldKey: string; value: string }[] = []
|
|
let i = 0
|
|
const push = (fieldKey: string, value: string | undefined) => {
|
|
const v = value?.trim()
|
|
if (!v) return
|
|
items.push({ id: `${fieldKey}-${i++}`, fieldKey, value: v })
|
|
}
|
|
|
|
push('first_name', data.first_name)
|
|
push('last_name', data.last_name)
|
|
push('company', data.company)
|
|
push('department', data.department)
|
|
push('job_title', data.job_title)
|
|
push('website', data.website)
|
|
push('notes', data.notes)
|
|
for (const p of data.social_profiles ?? []) push('social_profiles', p.value)
|
|
for (const e of data.emails ?? []) push('emails', e.value)
|
|
for (const p of data.phones ?? []) push('phones', p.value)
|
|
for (const a of data.addresses ?? []) {
|
|
const line = [a.street, [a.postal_code, a.city].filter(Boolean).join(' '), a.region, a.country]
|
|
.filter(Boolean)
|
|
.join(', ')
|
|
push('addresses', line)
|
|
}
|
|
return items
|
|
}
|