import type { WorkOrderAuditEntry } from "@/domain/work-orders/types/work-order-detail"; type AuditActionSource = Pick< WorkOrderAuditEntry, "action" | "fieldName" | "oldValue" | "newValue" >; function looksLikeHumanSentence(action: string): boolean { return /\s/.test(action); } function humanizeFieldLabel(fieldName: string): string { const spaced = fieldName .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/[_-]+/g, " ") .trim() .toLowerCase(); return spaced || "field"; } function normalizeKey(value: string): string { return value .trim() .toLowerCase() .replace(/[\s_-]+/g, ""); } /** JSON array strings โ†’ `"a", "b", "c"` instead of `["a","b","c"]`. */ function formatJsonArrayDisplayValue(raw: string): string | null { const trimmed = raw.trim(); if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null; try { const parsed: unknown = JSON.parse(trimmed); if (!Array.isArray(parsed)) return null; const items = parsed .map((item) => String(item ?? "").trim()) .filter(Boolean) .map((item) => `"${item}"`); return items.join(", "); } catch { return null; } } type FormattedAuditValue = { text: string; /** Per-item quotes already applied; omit outer quotes in sentence templates. */ isQuotedList: boolean; }; function wrapAuditValue(value: FormattedAuditValue): string { if (!value.text) return value.text; return value.isQuotedList ? value.text : `"${value.text}"`; } /** Parse ISO / date-only / pipe-separated API values โ†’ unique mm/dd/yyyy. */ function formatAuditDisplayValue(raw: string): FormattedAuditValue { const trimmed = raw.trim(); if (!trimmed) return { text: trimmed, isQuotedList: false }; const jsonArray = formatJsonArrayDisplayValue(trimmed); if (jsonArray !== null) return { text: jsonArray, isQuotedList: true }; const parts = trimmed .split("|") .map((part) => part.trim()) .filter(Boolean); const dates: string[] = []; for (const part of parts) { const isoMatch = part.match(/^(\d{4})-(\d{2})-(\d{2})(?:T|$)/); if (isoMatch) { const [, year, month, day] = isoMatch; dates.push(`${month}/${day}/${year}`); continue; } const date = new Date(part); if (!Number.isNaN(date.getTime()) && /\d{4}/.test(part)) { const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); const year = String(date.getFullYear()); dates.push(`${month}/${day}/${year}`); continue; } return { text: trimmed, isQuotedList: false }; } const unique = [...new Set(dates)]; if (unique.length === 0) return { text: trimmed, isQuotedList: false }; if (unique.length === 1) return { text: unique[0]!, isQuotedList: false }; return { text: unique.join(" โ€“ "), isQuotedList: false }; } /** * Builds a design-style audit sentence (the part after `Name ยท `), * e.g. `Changed status to "Incomplete"`, from board API fields. */ export function formatBoardAuditAction(entry: AuditActionSource): string { const action = entry.action?.trim() ?? ""; const fieldName = entry.fieldName?.trim() ?? ""; const oldValue = formatAuditDisplayValue(entry.oldValue?.trim() ?? ""); const newValue = formatAuditDisplayValue(entry.newValue?.trim() ?? ""); if (action && looksLikeHumanSentence(action)) { return action; } const fieldKey = normalizeKey(fieldName); const actionKey = normalizeKey(action); if ( fieldKey.includes("status") || fieldKey === "lifecyclestatus" || actionKey.includes("status") ) { if (newValue.text) return `Changed status to ${wrapAuditValue(newValue)}`; } if (fieldKey.includes("appt") || fieldKey.includes("appointment") || actionKey.includes("appt")) { if (oldValue.text && newValue.text) { return `Changed appt time from ${wrapAuditValue(oldValue)} to ${wrapAuditValue(newValue)}`; } if (newValue.text) return `Changed appt time to ${wrapAuditValue(newValue)}`; } if ( fieldKey.includes("scheduled") || fieldKey === "schedule" || fieldKey.includes("scheduledate") || fieldKey.includes("duedate") || actionKey === "rescheduled" || actionKey.includes("reschedule") ) { if (newValue.text) return `Changed schedule to ${wrapAuditValue(newValue)}`; return "Rescheduled work order"; } if ( fieldKey.includes("carriedover") || actionKey === "weekrolled" || actionKey.includes("carryover") ) { const increment = Number(entry.newValue?.trim() ?? ""); if (Number.isFinite(increment) && increment > 0) { return `Carried over to the next week (increment #${increment})`; } return "Carried over to the next week"; } if ( fieldKey.includes("pastdue") || actionKey.includes("pastdue") || actionKey === "markedpastdue" ) { return "WO marked as Past Due. Scheduled date has passed without completion."; } if (fieldKey === "created" || actionKey === "workordercreated" || actionKey === "created") { return "Work order created in the system."; } if (!action && !fieldName && !oldValue.text && !newValue.text) { return "Work order created in the system."; } const label = humanizeFieldLabel(fieldName || action || "field"); if (oldValue.text && newValue.text) { return `Changed ${label} from ${wrapAuditValue(oldValue)} to ${wrapAuditValue(newValue)}`; } if (newValue.text) { return `Changed ${label} to ${wrapAuditValue(newValue)}`; } if (action) return action; return "Updated work order"; }