shoc-frontend-new/src/domain/work-orders/utils/format-board-audit-action.ts
2026-09-16 16:56:26 -03:00

136 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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";
}