mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 17:53:12 +00:00
136 lines
4.2 KiB
TypeScript
136 lines
4.2 KiB
TypeScript
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";
|
||
}
|