ultisuite-client/lib/contacts/improve-contact.ts
R3D347HR4Y 07d57f13a8
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
Add Contact Avatar Features and Improve UI Components
- 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.
2026-06-06 20:26:51 +02:00

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
}