mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 08:03:13 +00:00
Use the extracted inline Schedule On cell from dev and keep additional-contacts fixtures complete.
328 lines
10 KiB
TypeScript
328 lines
10 KiB
TypeScript
import { readAdditionalContacts } from "@/domain/work-orders/mappers/additional-contacts-mapper";
|
|
import { mapApiStatusToWizard } from "@/domain/work-orders/mappers/wizard-draft-mapper";
|
|
import {
|
|
DOC_STATUS_API,
|
|
LIFECYCLE_STATUS_API_TO_FE,
|
|
WORK_ORDER_TYPE_API,
|
|
WORK_ORDER_TYPE_API_TO_FE,
|
|
} from "@/domain/work-orders/types/work-order-board";
|
|
import type {
|
|
CompletionDocStatus,
|
|
WorkOrderTableRow,
|
|
} from "@/domain/work-orders/types/work-order-table-row";
|
|
import { parseWorkOrderSeverity } from "@/domain/work-orders/types/work-order-severity";
|
|
import type { WOType } from "@/domain/work-orders/types/work-order-wizard";
|
|
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
|
|
import { formatApptRange } from "@/domain/work-orders/utils/wizard-date-utils";
|
|
import { toDateInputValue } from "@/lib/time-utils";
|
|
|
|
function asRecord(raw: unknown): Record<string, unknown> {
|
|
return typeof raw === "object" && raw !== null ? (raw as Record<string, unknown>) : {};
|
|
}
|
|
|
|
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 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 "";
|
|
}
|
|
|
|
function readNumber(record: Record<string, unknown>, ...keys: string[]): number | null {
|
|
for (const key of keys) {
|
|
const value = record[key];
|
|
if (typeof value === "number") {
|
|
return value;
|
|
}
|
|
if (typeof value === "string" && value !== "") {
|
|
const parsed = Number(value);
|
|
if (!Number.isNaN(parsed)) {
|
|
return parsed;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function readBool(record: Record<string, unknown>, ...keys: string[]): boolean {
|
|
for (const key of keys) {
|
|
const value = record[key];
|
|
if (typeof value === "boolean") {
|
|
return value;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function readStringArray(record: Record<string, unknown>, ...keys: string[]): string[] {
|
|
for (const key of keys) {
|
|
const value = record[key];
|
|
if (Array.isArray(value)) {
|
|
return value
|
|
.filter((item): item is string => typeof item === "string")
|
|
.map((item) => item.trim())
|
|
.filter(Boolean);
|
|
}
|
|
if (typeof value === "string" && value.trim()) {
|
|
try {
|
|
const parsed: unknown = JSON.parse(value);
|
|
if (Array.isArray(parsed)) {
|
|
return parsed
|
|
.filter((item): item is string => typeof item === "string")
|
|
.map((item) => item.trim())
|
|
.filter(Boolean);
|
|
}
|
|
} catch {
|
|
// ignore invalid JSON
|
|
}
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function readRowVersion(record: Record<string, unknown>, ...keys: string[]): string {
|
|
for (const key of keys) {
|
|
const value = record[key];
|
|
if (typeof value === "string" && value.length > 0) {
|
|
return value;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
const WO_TYPES_SET = new Set(["PM", "Reactive", "Emergency", "Overdue"]);
|
|
|
|
function isLegacyAddOnType(record: Record<string, unknown>): boolean {
|
|
const numeric =
|
|
readNumber(record, "workOrderType", "WorkOrderType") ?? readNumber(record, "type", "Type");
|
|
if (numeric === WORK_ORDER_TYPE_API.AddOn) {
|
|
return true;
|
|
}
|
|
const raw = readString(
|
|
record,
|
|
"workOrderType",
|
|
"WorkOrderType",
|
|
"type",
|
|
"Type",
|
|
"woType",
|
|
"WoType",
|
|
"source",
|
|
"Source",
|
|
);
|
|
return raw === "AddOn" || raw === "Add-On";
|
|
}
|
|
|
|
function readIsAddOn(record: Record<string, unknown>): boolean {
|
|
for (const key of ["isAddOn", "IsAddOn"] as const) {
|
|
if (!(key in record)) continue;
|
|
const value = record[key];
|
|
// Explicit boolean is authoritative; null/undefined fall through to legacy.
|
|
if (typeof value === "boolean") {
|
|
return value;
|
|
}
|
|
if (value == null) {
|
|
break;
|
|
}
|
|
return Boolean(value);
|
|
}
|
|
return isLegacyAddOnType(record);
|
|
}
|
|
|
|
function readWoType(record: Record<string, unknown>): WOType | "" {
|
|
const numeric =
|
|
readNumber(record, "workOrderType", "WorkOrderType") ?? readNumber(record, "type", "Type");
|
|
if (numeric === WORK_ORDER_TYPE_API.AddOn) {
|
|
return "";
|
|
}
|
|
if (numeric != null && WORK_ORDER_TYPE_API_TO_FE[numeric]) {
|
|
return WORK_ORDER_TYPE_API_TO_FE[numeric] as WOType;
|
|
}
|
|
|
|
const raw = readString(
|
|
record,
|
|
"workOrderType",
|
|
"WorkOrderType",
|
|
"type",
|
|
"Type",
|
|
"woType",
|
|
"WoType",
|
|
"source",
|
|
"Source",
|
|
);
|
|
if (raw === "AddOn" || raw === "Add-On") {
|
|
return "";
|
|
}
|
|
if (WO_TYPES_SET.has(raw)) {
|
|
return raw as WOType;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function readDocStatus(record: Record<string, unknown>): CompletionDocStatus {
|
|
const numeric = readNumber(record, "docStatus", "DocStatus");
|
|
if (numeric === DOC_STATUS_API.Yes) return "Yes";
|
|
if (numeric === DOC_STATUS_API.No) return "No";
|
|
if (numeric === DOC_STATUS_API.NotNeeded) return "NN";
|
|
|
|
const raw = readString(record, "docStatus", "DocStatus", "completionDoc", "CompletionDoc");
|
|
if (raw === "Yes" || raw === "No" || raw === "NN") {
|
|
return raw;
|
|
}
|
|
if (raw === "NotNeeded") {
|
|
return "NN";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function readBoardStatus(record: Record<string, unknown>): ReturnType<typeof mapApiStatusToWizard> {
|
|
const label = readString(record, "lifecycleStatusLabel", "LifecycleStatusLabel");
|
|
if (label) {
|
|
return mapApiStatusToWizard(label);
|
|
}
|
|
|
|
const lifecycle = readNumber(record, "lifecycleStatus", "LifecycleStatus");
|
|
if (lifecycle != null && LIFECYCLE_STATUS_API_TO_FE[lifecycle]) {
|
|
return mapApiStatusToWizard(LIFECYCLE_STATUS_API_TO_FE[lifecycle]);
|
|
}
|
|
|
|
const legacy = readString(record, "legacyStatus", "LegacyStatus", "status", "Status");
|
|
return mapApiStatusToWizard(legacy);
|
|
}
|
|
|
|
function readFlagColor(record: Record<string, unknown>): string | null {
|
|
for (const key of ["flagColor", "FlagColor"]) {
|
|
const value = record[key];
|
|
if (value == null) {
|
|
continue;
|
|
}
|
|
if (typeof value === "string") {
|
|
const trimmed = value.trim();
|
|
return trimmed || null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function mapWorkOrderTableRow(raw: unknown): WorkOrderTableRow {
|
|
const item = asRecord(raw);
|
|
const scheduledOn = toDateInputValue(
|
|
readString(item, "scheduledOn", "scheduledDate", "ScheduledDate", "ScheduledOn"),
|
|
);
|
|
// FE day groups use MM/DD keys (week skeleton); ignore BE weekday labels like "monday".
|
|
const { dayGroup, dayLabel } = deriveDayFields(scheduledOn);
|
|
const woNumber = readString(
|
|
item,
|
|
"woNumber",
|
|
"internalWONumber",
|
|
"InternalWONumber",
|
|
"number",
|
|
"workOrderNumber",
|
|
"WorkerOrderNumber",
|
|
);
|
|
const site = readString(
|
|
item,
|
|
"siteCode",
|
|
"SiteCode",
|
|
"site",
|
|
"Site",
|
|
"locationCode",
|
|
"LocationCode",
|
|
);
|
|
const location = readString(item, "location", "Location", "locationName", "LocationName");
|
|
const dispatcherId = readString(
|
|
item,
|
|
"dispatcherId",
|
|
"DispatcherId",
|
|
"assigneeId",
|
|
"AssigneeId",
|
|
"employeeId",
|
|
"EmployeeId",
|
|
);
|
|
const dispatcherName = readString(
|
|
item,
|
|
"dispatcherName",
|
|
"DispatcherName",
|
|
"assignedTo",
|
|
"AssignedTo",
|
|
"employeeName",
|
|
"EmployeeName",
|
|
"assignee",
|
|
"Assignee",
|
|
);
|
|
const apptStart = readString(item, "apptStart", "ApptStart", "appointmentStart");
|
|
const apptEnd = readString(item, "apptEnd", "ApptEnd", "appointmentEnd");
|
|
const apptTime =
|
|
readString(item, "apptTime", "ApptTime") ||
|
|
(apptStart ? formatApptRange(apptStart, apptEnd) : "");
|
|
const locationIdRaw = item.locationId ?? item.LocationId;
|
|
const vendorIdNum = readNumber(item, "vendorId", "VendorId");
|
|
const primaryDispatchId = readNumber(item, "primaryDispatchId", "PrimaryDispatchId");
|
|
|
|
const row: WorkOrderTableRow = {
|
|
id: readId(item),
|
|
woNumber,
|
|
site: site || location.slice(0, 8),
|
|
type: readWoType(item),
|
|
severity: parseWorkOrderSeverity(item.severity ?? item.Severity),
|
|
dispatcherId,
|
|
dispatcherName,
|
|
scheduledOn,
|
|
completedDate: toDateInputValue(readString(item, "completedDate", "CompletedDate")),
|
|
targetWeek: toDateInputValue(readString(item, "targetWeek", "TargetWeek")),
|
|
dayGroup,
|
|
dayLabel,
|
|
location,
|
|
locationId:
|
|
typeof locationIdRaw === "string" || typeof locationIdRaw === "number" ? locationIdRaw : "",
|
|
pm: readString(item, "pm", "PM", "problem", "Problem", "service", "Service", "trade", "Trade"),
|
|
serviceNotes: readString(item, "serviceNotes", "ServiceNotes", "pmNote", "PmNote"),
|
|
extraServices: readStringArray(item, "extraServices", "ExtraServices"),
|
|
vendorId: vendorIdNum != null ? String(vendorIdNum) : "",
|
|
company: readString(item, "company", "Company", "vendorName", "VendorName"),
|
|
tech: readString(item, "techName", "TechName", "tech", "Tech", "technician", "Technician"),
|
|
techPhone: readString(item, "techPhone", "TechPhone"),
|
|
apptStart,
|
|
apptEnd,
|
|
apptTime,
|
|
status: readBoardStatus(item),
|
|
docStatus: readDocStatus(item),
|
|
pocName: readString(item, "pocName", "PocName", "POCName"),
|
|
pocPhone: readString(item, "pocPhone", "PocPhone", "POCPhone"),
|
|
pocNotes: readString(item, "pocNotes", "PocNotes", "POCNotes"),
|
|
additionalContacts: readAdditionalContacts(item),
|
|
dueDate: toDateInputValue(readString(item, "dueDate", "DueDate")),
|
|
rescheduleCount: readNumber(item, "rescheduleCount", "RescheduleCount") ?? 0,
|
|
carriedOver: readNumber(item, "carriedOver", "CarriedOver") ?? 0,
|
|
isAddOn: readIsAddOn(item),
|
|
originalWeek: readString(item, "originalWeek", "OriginalWeek"),
|
|
originalDate: toDateInputValue(readString(item, "originalDate", "OriginalDate")),
|
|
isPastDue: readBool(item, "isPastDue", "IsPastDue"),
|
|
scheduleWeekOnly: readBool(item, "scheduleWeekOnly", "ScheduleWeekOnly"),
|
|
mediaCount: readNumber(item, "mediaCount", "MediaCount") ?? 0,
|
|
title: readString(item, "title", "Title", "workerOrderTitle", "WorkerOrderTitle"),
|
|
priority: readString(item, "priority", "Priority"),
|
|
primaryDispatchId,
|
|
rowVersion: readRowVersion(item, "rowVersion", "RowVersion"),
|
|
dispatchRowVersion: readRowVersion(item, "dispatchRowVersion", "DispatchRowVersion"),
|
|
flagColor: readFlagColor(item),
|
|
};
|
|
|
|
return row;
|
|
}
|
|
|
|
export function mapWorkOrderTableRows(rawItems: unknown[]): WorkOrderTableRow[] {
|
|
return rawItems.map(mapWorkOrderTableRow);
|
|
}
|