"use client"; import { useTranslations } from "next-intl"; import { Mail, Phone, Building, MapPin, StickyNote, Pencil, Trash2, BookUser, Copy, Send, Globe, Cake, Tag, KeyRound, Link, Users, Briefcase, Heart, Languages, MessageCircle, User, Calendar, UserCircle } from "lucide-react"; import { Avatar } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import type { ContactCard } from "@/lib/jmap/types"; import { getContactDisplayName, getContactPrimaryEmail } from "@/stores/contact-store"; import { toast } from "@/stores/toast-store"; interface ContactDetailProps { contact: ContactCard | null; onEdit: () => void; onDelete: () => void; isMobile?: boolean; className?: string; } function formatPhoneFeatures(features?: Record): string { if (!features) return ""; return Object.keys(features).filter(k => features[k]).join(", "); } function formatDate(dateStr: string): string { // Handle both ISO dates and partial dates like 1990-01-15 or --01-15 if (dateStr.startsWith("--")) { // Partial date without year const parts = dateStr.substring(2).split("-"); const month = parseInt(parts[0], 10); const day = parts[1] ? parseInt(parts[1], 10) : undefined; const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; return day ? `${monthNames[month - 1]} ${day}` : monthNames[month - 1]; } try { const d = new Date(dateStr); if (!isNaN(d.getTime())) { return d.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" }); } } catch { /* fallback */ } return dateStr; } export function ContactDetail({ contact, onEdit, onDelete, isMobile, className }: ContactDetailProps) { const t = useTranslations("contacts"); if (!contact) { return (

{t("detail.no_contact_selected")}

); } const name = getContactDisplayName(contact); const email = getContactPrimaryEmail(contact); const emails = contact.emails ? Object.values(contact.emails) : []; const phones = contact.phones ? Object.values(contact.phones) : []; const orgs = contact.organizations ? Object.values(contact.organizations) : []; const addresses = contact.addresses ? Object.values(contact.addresses) : []; const notes = contact.notes ? Object.values(contact.notes) : []; const titles = contact.titles ? Object.values(contact.titles) : []; const jobTitles = titles.filter(t => t.kind !== "role"); const roles = titles.filter(t => t.kind === "role"); const onlineServices = contact.onlineServices ? Object.values(contact.onlineServices) : []; const anniversaries = contact.anniversaries ? Object.values(contact.anniversaries) : []; const keywords = contact.keywords ? Object.keys(contact.keywords).filter(k => contact.keywords![k]) : []; const cryptoKeys = contact.cryptoKeys ? Object.values(contact.cryptoKeys) : []; const relatedTo = contact.relatedTo ? Object.entries(contact.relatedTo) : []; const preferredLanguages = contact.preferredLanguages ? Object.values(contact.preferredLanguages) : []; const personalInfo = contact.personalInfo ? Object.values(contact.personalInfo) : []; const nicknames = contact.nicknames ? Object.values(contact.nicknames) : []; const hasNickname = nicknames.length > 0; const titleLine = jobTitles.length > 0 ? jobTitles.map(t => t.name).join(", ") : undefined; return (

{name || "—"}

{hasNickname && (

“{nicknames.map(n => n.name).join(", ")}”

)} {titleLine && (

{titleLine}

)} {orgs.length > 0 && orgs[0].name && (

{orgs[0].name}

)}
{/* Contact info */} {emails.length > 0 && (
{emails.map((e, i) => (
{e.address} {e.contexts && } {e.label && ({e.label})}
))}
)} {phones.length > 0 && (
{phones.map((p, i) => { const featureStr = formatPhoneFeatures(p.features); return (
{p.number} {p.contexts && } {featureStr && ( {featureStr} )}
); })}
)} {(roles.length > 0 || jobTitles.length > 1) && (
{titles.map((tl, i) => (
{tl.name} {tl.kind && ( {tl.kind} )}
))}
)} {orgs.length > 0 && (
{orgs.map((o, i) => (
{o.name} {o.units && o.units.length > 0 && ( — {o.units.map(u => u.name).join(", ")} )}
))}
)} {/* Addresses span full width */} {addresses.length > 0 && (
{addresses.map((a, i) => (
{a.fullAddress ? a.fullAddress : [a.street, a.locality, a.region, a.postcode, a.country].filter(Boolean).join(", ")} {a.contexts && }
{a.timeZone && (
{t("detail.timezone")}: {a.timeZone}
)}
))}
)} {onlineServices.length > 0 && (
{onlineServices.map((svc, i) => (
{svc.uri.startsWith("http") ? ( {svc.user || svc.uri} ) : ( {svc.user || svc.uri} )} {svc.service && ( {svc.service} )} {svc.contexts && }
))}
)} {anniversaries.length > 0 && (
{anniversaries.map((ann, i) => (
{formatDate(ann.date)} {t(`detail.anniversary_${ann.kind}`)}
))}
)} {personalInfo.length > 0 && (
{personalInfo.map((pi, i) => (
{pi.value} {t(`detail.personal_${pi.kind}`)} {pi.level && ( ({pi.level}) )}
))}
)} {contact.gender && (contact.gender.sex || contact.gender.identity) && (
{contact.gender.sex && {t(`detail.gender_${contact.gender.sex.toUpperCase()}`, { defaultValue: contact.gender.sex })}} {contact.gender.identity && ( {contact.gender.sex ? " — " : ""}{contact.gender.identity} )}
)} {preferredLanguages.length > 0 && (
{preferredLanguages.map((lang, i) => (
{lang.language} {lang.contexts && }
))}
)} {keywords.length > 0 && (
{keywords.map((kw, i) => ( {kw} ))}
)} {relatedTo.length > 0 && (
{relatedTo.map(([uri, rel], i) => { const relType = rel.relation ? Object.keys(rel.relation).find(k => rel.relation![k]) : undefined; return (
{uri} {relType && ( {relType} )}
); })}
)} {cryptoKeys.length > 0 && (
{cryptoKeys.map((key, i) => (
{key.uri.startsWith("http") ? ( {key.uri} ) : ( {key.uri.substring(0, 80)}{key.uri.length > 80 ? "…" : ""} )}
))}
)} {(contact.calendarUri || contact.schedulingUri || contact.freeBusyUri) && (
{contact.calendarUri && (
{t("detail.calendar_uri")}: {contact.calendarUri}
)} {contact.schedulingUri && (
{t("detail.scheduling_uri")}: {contact.schedulingUri}
)} {contact.freeBusyUri && (
{t("detail.freebusy_uri")}: {contact.freeBusyUri}
)}
)} {/* Notes span full width */} {notes.length > 0 && (
{notes.map((n, i) => (

{n.note}

))}
)} {/* Timestamps span full width */} {(contact.created || contact.updated) && (
{contact.created &&
{t("detail.created")}: {formatDate(contact.created)}
} {contact.updated &&
{t("detail.updated")}: {formatDate(contact.updated)}
}
)}
); } type SectionCategory = "contact" | "work" | "location" | "personal" | "digital" | "calendar" | "notes"; const categoryStyles: Record = { contact: "border-l-blue-400 dark:border-l-blue-500", work: "border-l-amber-400 dark:border-l-amber-500", location: "border-l-emerald-400 dark:border-l-emerald-500", personal: "border-l-violet-400 dark:border-l-violet-500", digital: "border-l-cyan-400 dark:border-l-cyan-500", calendar: "border-l-rose-400 dark:border-l-rose-500", notes: "border-l-stone-400 dark:border-l-stone-500", }; function Section({ icon: Icon, title, children, category = "contact" }: { icon: React.ComponentType<{ className?: string }>; title: string; children: React.ReactNode; category?: SectionCategory }) { return (

{title}

{children}
); } function ContextBadge({ contexts }: { contexts: Record }) { const labels = Object.keys(contexts).filter(k => contexts[k]); if (labels.length === 0) return null; return ( {labels.join(", ")} ); } function CopyButton({ value, label, successMsg, failMsg, className }: { value: string; label: string; successMsg: string; failMsg: string; className?: string }) { return ( ); }