diff --git a/src/components/common.ts b/src/components/common.ts index 1aaf7380..0e249393 100644 --- a/src/components/common.ts +++ b/src/components/common.ts @@ -560,6 +560,26 @@ export interface ScheduleData { service_at_location_id: string | null; } +export interface PhoneData { + id: string | null; + number: string; + extension: string | number | null; + type: string | null; + language: string | null; + description: string | null; + createdAt: Date; + updatedAt: Date; + location_id: string | null; + organization_id: string | null; + service_id: string | null; + service_at_location_id: string | null; +} + +export type YourPeerLegacyPhoneData = Pick< + PhoneData, + "id" | "number" | "extension" | "type" | "language" | "description" +>; + export interface ServiceData { id: string; name: string | null; @@ -643,7 +663,7 @@ export interface ServiceData { updatedAt: Date; service_id: string; }; - Phones: []; + Phones: PhoneData[]; EventRelatedInfos: { id: string; event: string | null; @@ -673,22 +693,9 @@ export interface AbstractDetailedLocationData { url: string | null; createdAt: Date; updatedAt: Date; - Phones: string[]; + Phones: PhoneData[]; }; - Phones: { - id: string | null; - number: string; - extension: string | null; - type: string | null; - language: string | null; - description: string | null; - createdAt: Date; - updatedAt: Date; - location_id: string | null; - organization_id: string | null; - service_id: string | null; - service_at_location_id: string | null; - }[]; + Phones: PhoneData[]; Services: ServiceData[]; EventRelatedInfos: { id: string; @@ -858,6 +865,7 @@ export interface YourPeerLegacyLocationData { last_updated_date: Date; name: string | null; phone: string | null; + phones: YourPeerLegacyPhoneData[]; url: string | null; streetview_url: string | null; accommodation_services: YourPeerLegacyServiceDataWrapper; diff --git a/src/components/location-detail/location-detail-info.tsx b/src/components/location-detail/location-detail-info.tsx index baea255f..1cd420d8 100644 --- a/src/components/location-detail/location-detail-info.tsx +++ b/src/components/location-detail/location-detail-info.tsx @@ -1,6 +1,9 @@ "use client"; -import { YourPeerLegacyLocationData } from "@/components/common"; +import { + YourPeerLegacyLocationData, + YourPeerLegacyPhoneData, +} from "@/components/common"; function normalizeWebsiteUrl(url: string): string | undefined { if (url) { @@ -18,11 +21,204 @@ function renderNormalizedWebsiteUrl(url: string): string | undefined { } } +type FormattedPhone = { + display: string; + href?: string; + external?: boolean; + key: string; +}; + +type PhoneUse = "phone" | "sms" | "whatsapp" | "fax"; + +const extensionPattern = /(?:ext\.?|extension|x)\s*[:#-]?\s*(\d+)\s*$/i; + +function normalizePhoneUse(type?: string | null): PhoneUse { + const normalizedType = (type ?? "").toLowerCase().replace(/[\s_-]+/g, ""); + + if (normalizedType.includes("whatsapp")) { + return "whatsapp"; + } + + if (normalizedType.includes("sms") || normalizedType.includes("text")) { + return "sms"; + } + + if (normalizedType.includes("fax")) { + return "fax"; + } + + return "phone"; +} + +function phoneCanHaveExtension(phoneUse: PhoneUse): boolean { + return phoneUse === "phone"; +} + +function normalizePhoneDigits(rawNumber: string): string { + const digits = rawNumber.replace(/\D/g, ""); + return digits.length === 11 && digits.startsWith("1") + ? digits.slice(1) + : digits; +} + +function formatPhoneDigits(digits: string): string { + if (digits.length === 10) { + return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; + } + + if (digits.length === 7) { + return `${digits.slice(0, 3)}-${digits.slice(3)}`; + } + + return digits; +} + +function getPhoneLabel( + phone: YourPeerLegacyPhoneData, + phoneUse: PhoneUse, +): string | null { + if (phoneUse === "sms") { + return "Text"; + } + + if (phoneUse === "whatsapp") { + return "WhatsApp"; + } + + if (phoneUse === "fax") { + return "Fax"; + } + + const customType = phone.type === "Phone" ? null : phone.type; + return phone.description || customType || null; +} + +function getPhoneDetails( + phone: YourPeerLegacyPhoneData, + label: string | null, +): string | null { + if (!phone.description || phone.description === label) { + return null; + } + + return phone.description; +} + +function formatPhoneContact( + phone: YourPeerLegacyPhoneData, + index: number, +): FormattedPhone | null { + const trimmed = phone.number.trim(); + if (!trimmed) { + return null; + } + + const phoneUse = normalizePhoneUse(phone.type); + const allowExtension = phoneCanHaveExtension(phoneUse); + const extensionMatch = trimmed.match(extensionPattern); + let extension = `${phone.extension ?? ""}`.trim() || null; + let numberPart = trimmed; + + if (extensionMatch) { + if (allowExtension && !extension) { + extension = extensionMatch[1]; + } + numberPart = trimmed.slice(0, extensionMatch.index).trim(); + } + + let digits = normalizePhoneDigits(numberPart); + if (!digits) { + return { + display: trimmed, + key: phone.id ?? `${trimmed}-${index}`, + }; + } + + if (digits.length > 10) { + if (allowExtension && !extension) { + extension = digits.slice(10); + } + digits = digits.slice(0, 10); + } + + if (!allowExtension) { + extension = null; + } + + const label = getPhoneLabel(phone, phoneUse); + const details = getPhoneDetails(phone, label); + const formattedNumber = formatPhoneDigits(digits); + const extensionText = extension ? ` x${extension}` : ""; + const detailsText = details ? ` (${details})` : ""; + const display = `${label ? `${label}: ` : ""}${formattedNumber}${extensionText}${detailsText}`; + + if (digits.length < 3 || digits.length > 10) { + return { + display, + key: phone.id ?? `${digits}-${index}`, + }; + } + + if (phoneUse === "fax") { + return { + display, + key: phone.id ?? `${digits}-${index}`, + }; + } + + if (phoneUse === "whatsapp") { + return { + display, + href: digits.length === 10 ? `https://wa.me/1${digits}` : undefined, + external: true, + key: phone.id ?? `${digits}-${index}`, + }; + } + + const dialableNumber = digits.length === 10 ? `+1${digits}` : digits; + const href = + phoneUse === "sms" + ? `sms:${dialableNumber}` + : `tel:${dialableNumber}${extension ? `;ext=${extension}` : ""}`; + + return { + display, + href, + key: phone.id ?? `${digits}-${index}`, + }; +} + +function getPhoneContacts( + location: YourPeerLegacyLocationData, +): FormattedPhone[] { + const phones = location.phones.length + ? location.phones + : location.phone + ? [ + { + id: null, + number: location.phone, + extension: null, + type: null, + language: null, + description: null, + }, + ] + : []; + + return phones.flatMap((phone, index) => { + const formattedPhone = formatPhoneContact(phone, index); + return formattedPhone ? [formattedPhone] : []; + }); +} + export default function LocationDetailInfo({ location, }: { location: YourPeerLegacyLocationData; }) { + const phoneContacts = getPhoneContacts(location); + return ( <> {location.closed ? ( @@ -80,27 +276,40 @@ export default function LocationDetailInfo({ ) : undefined}
- + <> {!location.closed ? ( <> - {location.phone ? ( -- - {" "} - {location.phone}{" "} - + {phoneContact.href ? ( + + {phoneContact.display} + + ) : ( + {phoneContact.display} + )}