shoc-frontend-new/src/domain/locations/mappers/location-mapper.ts
Arthur Bassi 94aa7a1d66 Merge remote-tracking branch 'origin/main' into feat/SH-388-uplift-slide-over
# Conflicts:
#	e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-detail.png
#	src/app/(protected)/workorders/_components/list/table/cells/uplift-cell.tsx
#	src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx
#	src/app/(protected)/workorders/_components/uplifts/work-order-uplift-list-item.tsx
#	src/app/(protected)/workorders/_components/uplifts/work-order-uplifts-dialog-content.tsx
#	src/app/(protected)/workorders/_components/uplifts/work-order-uplifts-dialog.tsx
#	src/components/common/address-autocomplete-field.tsx
#	src/domain/work-orders/utils/uplift-display-utils.ts
#	src/test/app/(protected)/workorders/work-order-uplifts-affordances.test.tsx
2026-09-28 14:08:51 -03:00

270 lines
8.9 KiB
TypeScript

import type {
Location,
LocationContact,
LocationListItem,
LocationSite,
SiteOpenWorkOrders,
} from "@/domain/locations/types/location";
function readString(record: Record<string, unknown>, ...keys: string[]): string {
for (const key of keys) {
const value = record[key];
if (typeof value === "string") {
return value;
}
if (typeof value === "number") {
return String(value);
}
}
return "";
}
/** Like readString, but an empty value becomes undefined. */
function readOptionalString(
record: Record<string, unknown>,
...keys: string[]
): string | undefined {
return readString(record, ...keys) || undefined;
}
function readId(record: Record<string, unknown>): string | number {
const value = record.id ?? record.Id;
if (typeof value === "string" || typeof value === "number") {
return value;
}
return "";
}
function readOptionalId(record: Record<string, unknown>): string | number {
const value = record.accountId ?? record.AccountId;
if (typeof value === "string" || typeof value === "number") {
return value;
}
return "";
}
export function formatLocationSiteLabel(
site: Pick<LocationSite, "code" | "city" | "state">,
): string {
const place = [site.city, site.state].filter(Boolean).join(", ");
if (site.code && place) {
return `${site.code} · ${place}`;
}
return site.code || place || "";
}
/** Table cell label: `BK5 . Dallas, TX` (period separator). */
export function formatSiteTableLabel(params: {
code?: string;
city?: string;
state?: string;
fallbackLocation?: string;
}): string {
const code = params.code?.trim() ?? "";
const place =
[params.city, params.state].filter(Boolean).join(", ") ||
(params.fallbackLocation?.trim() ?? "");
if (code && place) {
return `${code} . ${place}`;
}
return code || place || "";
}
export function formatSitePlace(city?: string, state?: string, fallbackLocation = ""): string {
return [city, state].filter(Boolean).join(", ") || fallbackLocation.trim() || "";
}
/** Wizard preview: `3811 Distribution Dr, Dallas, TX 70068` */
export function formatLocationAddressPreview(
location: Pick<Location, "address" | "city" | "state" | "zipCode">,
): string {
const street = location.address?.trim() ?? "";
const city = location.city?.trim() ?? "";
const stateZip = [location.state?.trim(), location.zipCode?.trim()].filter(Boolean).join(" ");
const cityStateZip = [city, stateZip].filter(Boolean).join(", ");
if (street && cityStateZip) {
return `${street}, ${cityStateZip}`;
}
return street || cityStateZip || "";
}
export function mapLocationSite(raw: unknown): LocationSite {
const item = (typeof raw === "object" && raw !== null ? raw : {}) as Record<string, unknown>;
return {
id: readId(item),
code: readString(item, "code", "Code", "siteCode", "SiteCode"),
city: readString(item, "city", "City"),
state: readString(item, "state", "State"),
};
}
export function mapLocationSites(rawItems: unknown[]): LocationSite[] {
return rawItems.map(mapLocationSite);
}
/** Reads the additive contacts array; returns [] when absent or malformed. */
export function mapLocationContacts(raw: unknown): LocationContact[] {
if (!Array.isArray(raw)) {
return [];
}
return raw.map((entry) => {
const item = (typeof entry === "object" && entry !== null ? entry : {}) as Record<
string,
unknown
>;
const id = item.id ?? item.Id;
return {
...(typeof id === "number" && Number.isInteger(id) && id > 0 ? { id } : {}),
name: readString(item, "name", "Name", "contact", "Contact"),
phone: readString(item, "phone", "Phone"),
};
});
}
export function mapLocationListItem(raw: unknown): LocationListItem {
const item = (typeof raw === "object" && raw !== null ? raw : {}) as Record<string, unknown>;
const contacts = mapLocationContacts(item.contacts ?? item.Contacts);
const mainContact = contacts[0];
return {
id: readId(item),
name: readString(item, "name", "Name"),
clientName: readString(item, "clientName", "ClientName", "accountName", "AccountName"),
sitePhone: readString(item, "phone", "Phone"),
address: readString(item, "address", "Address"),
city: readString(item, "city", "City"),
state: readString(item, "state", "State"),
zipCode: readString(item, "zipCode", "ZipCode", "Zip"),
phone: mainContact ? mainContact.phone : readString(item, "phone", "Phone"),
contact: mainContact
? mainContact.name
: readString(item, "contact", "Contact", "contactPerson", "ContactPerson"),
contactEmail: readString(item, "contactEmail", "ContactEmail"),
status: readString(item, "status", "Status"),
accountId: readOptionalId(item),
contacts: contacts.length > 0 ? contacts : undefined,
};
}
export function mapLocationListItems(rawItems: unknown[]): LocationListItem[] {
return rawItems.map(mapLocationListItem);
}
export function mapLocation(raw: unknown): Location {
const item = (typeof raw === "object" && raw !== null ? raw : {}) as Record<string, unknown>;
const address =
readString(item, "address", "Address") ||
[readString(item, "address1", "Address1"), readString(item, "address2", "Address2")]
.filter(Boolean)
.join(", ") ||
undefined;
const contacts = mapLocationContacts(item.contacts ?? item.Contacts);
const mainContact = contacts[0];
const legacyPhone = readOptionalString(item, "phone", "Phone");
const legacyContact = readOptionalString(
item,
"contact",
"Contact",
"contactPerson",
"ContactPerson",
);
return {
id: readId(item) || undefined,
name: readOptionalString(item, "name", "Name"),
title: readOptionalString(item, "title", "Title"),
clientName: readOptionalString(item, "clientName", "ClientName", "accountName", "AccountName"),
sitePhone: legacyPhone,
notes: readOptionalString(item, "notes", "Notes"),
address,
city: readOptionalString(item, "city", "City"),
state: readOptionalString(item, "state", "State"),
zipCode: readOptionalString(item, "zipCode", "ZipCode", "Zip"),
phone: mainContact ? mainContact.phone : legacyPhone,
contact: mainContact ? mainContact.name : legacyContact,
contactEmail: readOptionalString(item, "contactEmail", "ContactEmail"),
status: readOptionalString(item, "status", "Status"),
accountId: readOptionalId(item) || undefined,
contacts: contacts.length > 0 ? contacts : undefined,
};
}
/** Request payload contacts: trimmed, in displayed order. */
export function mapContactsToBackend(raw: unknown): LocationContact[] {
if (!Array.isArray(raw)) {
return [];
}
return raw
.map((entry) => {
const item = (typeof entry === "object" && entry !== null ? entry : {}) as Record<
string,
unknown
>;
return {
...(typeof item.id === "number" && Number.isInteger(item.id) && item.id > 0
? { id: item.id }
: {}),
name: readString(item, "name", "Name", "contact", "Contact").trim(),
phone: readString(item, "phone", "Phone").trim(),
};
})
.filter((contact) => contact.name !== "" || contact.phone !== "");
}
/**
* Create/update payload. `phone` is the Site Phone (independent of the contacts); `status`,
* `title` and `notes` are sent only when present so an update keeps the stored values.
*/
export function mapLocationToBackend(location: unknown): Record<string, unknown> {
const item = (typeof location === "object" && location !== null ? location : {}) as Record<
string,
unknown
>;
const contacts = mapContactsToBackend(item.contacts ?? item.Contacts);
const mainContact = contacts[0];
const payload: Record<string, unknown> = {
name: typeof item.name === "string" ? item.name.trim() : "",
address: item.address ?? "",
city: item.city ?? "",
state: item.state ?? "",
zipCode: item.zipCode ?? "",
phone: typeof item.sitePhone === "string" ? item.sitePhone.trim() : "",
contact: mainContact ? mainContact.name : readString(item, "contact").trim(),
contactEmail: typeof item.contactEmail === "string" ? item.contactEmail.trim() : "",
accountId: item.accountId ?? "",
contacts,
};
for (const key of ["status", "title", "notes"] as const) {
if (typeof item[key] === "string") {
payload[key] = item[key];
}
}
const id = item.id ?? item.Id;
if (typeof id === "string" || typeof id === "number") {
payload.id = id;
}
return payload;
}
export function mapSiteOpenWorkOrders(raw: unknown): SiteOpenWorkOrders {
const item = (typeof raw === "object" && raw !== null ? raw : {}) as Record<string, unknown>;
const rawIds = item.workOrderIds ?? item.WorkOrderIds;
const workOrderIds = Array.isArray(rawIds)
? rawIds.filter((id): id is number => typeof id === "number" && Number.isInteger(id) && id > 0)
: [];
const count = item.count ?? item.Count;
return {
count: typeof count === "number" && count >= 0 ? count : workOrderIds.length,
workOrderIds,
};
}