shoc-frontend-new/src/domain/work-orders/utils/format-board-audit-action.ts

137 lines
4.2 KiB
TypeScript
Raw Normal View History

import {
BOARD_AUDIT_ACTION_RULES,
wrapAuditValue,
type AuditActionSource,
type FormattedAuditValue,
} from "@/domain/work-orders/utils/board-audit-action-rules";
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;
}
}
/** 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);
const ruleContext = { entry, fieldKey, actionKey, oldValue, newValue };
for (const rule of BOARD_AUDIT_ACTION_RULES) {
const result = rule(ruleContext);
if (result !== null) return result;
}
return formatGenericAuditFallback({ action, fieldName, oldValue, newValue });
}
function formatGenericAuditFallback(args: {
action: string;
fieldName: string;
oldValue: FormattedAuditValue;
newValue: FormattedAuditValue;
}): string {
const { action, fieldName, oldValue, newValue } = args;
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 (oldValue.text) {
return `Changed ${label} from ${wrapAuditValue(oldValue)} to "—"`;
}
// Never surface a raw audit enum such as "FieldChanged" as the sentence.
if (fieldName && action) return `Updated ${label}`;
return "Updated work order";
}