shoc-frontend-new/src/domain/work-orders/utils/board-audit-action-rules.ts
Arthur Bassi 9c7b42e96b
feat(work-orders): SH-132 FE — Due Date distinct from Schedule On + Emergency calendar modal (#82)
* feat(work-orders): align Due Date and Past Due with SH-132 FE contract [SH-132]

* fix(work-orders): highlight Schedule On for unscheduled rows

Restore forceScheduleHighlight for Unscheduled section rows while keeping Due Date highlight for past-due and attempt feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(work-orders): cover unscheduled Schedule On highlight [SH-132]

Add row-level public-interface regression proving Unscheduled rows force-highlight Schedule On, not Due Date (SH-165).

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Alexandre Brandizzi <alex_brandizzi@hotmail.com>
2026-08-11 13:49:41 -03:00

109 lines
3.3 KiB
TypeScript

import type { WorkOrderAuditEntry } from "@/domain/work-orders/types/work-order-detail";
export type AuditActionSource = Pick<
WorkOrderAuditEntry,
"action" | "fieldName" | "oldValue" | "newValue"
>;
export type FormattedAuditValue = {
text: string;
/** Per-item quotes already applied; omit outer quotes in sentence templates. */
isQuotedList: boolean;
};
export function wrapAuditValue(value: FormattedAuditValue): string {
if (!value.text) return value.text;
return value.isQuotedList ? value.text : `"${value.text}"`;
}
export type AuditActionRuleContext = {
entry: AuditActionSource;
fieldKey: string;
actionKey: string;
oldValue: FormattedAuditValue;
newValue: FormattedAuditValue;
};
export type AuditActionRule = (ctx: AuditActionRuleContext) => string | null;
function tryStatusRule({ fieldKey, actionKey, newValue }: AuditActionRuleContext): string | null {
const matches =
fieldKey.includes("status") || fieldKey === "lifecyclestatus" || actionKey.includes("status");
if (!matches || !newValue.text) return null;
return `Changed status to ${wrapAuditValue(newValue)}`;
}
function tryApptRule({
fieldKey,
actionKey,
oldValue,
newValue,
}: AuditActionRuleContext): string | null {
const matches =
fieldKey.includes("appt") || fieldKey.includes("appointment") || actionKey.includes("appt");
if (!matches) return null;
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)}`;
return null;
}
function tryScheduleRule({ fieldKey, actionKey, newValue }: AuditActionRuleContext): string | null {
const matches =
fieldKey.includes("scheduled") ||
fieldKey === "schedule" ||
fieldKey.includes("scheduledate") ||
fieldKey.includes("duedate") ||
actionKey === "rescheduled" ||
actionKey.includes("reschedule");
if (!matches) return null;
if (newValue.text) return `Changed schedule to ${wrapAuditValue(newValue)}`;
return "Rescheduled work order";
}
function tryCarriedOverRule({ fieldKey, actionKey, entry }: AuditActionRuleContext): string | null {
const matches =
fieldKey.includes("carriedover") ||
actionKey === "weekrolled" ||
actionKey.includes("carryover");
if (!matches) return null;
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";
}
function tryPastDueRule({ fieldKey, actionKey }: AuditActionRuleContext): string | null {
const matches =
fieldKey.includes("pastdue") || actionKey.includes("pastdue") || actionKey === "markedpastdue";
if (!matches) return null;
return "WO marked as Past Due. Due date has passed without completion.";
}
function tryCreatedRule({ fieldKey, actionKey }: AuditActionRuleContext): string | null {
const matches =
fieldKey === "created" || actionKey === "workordercreated" || actionKey === "created";
if (!matches) return null;
return "Work order created in the system.";
}
/** Applied in order; the first non-null result wins. */
export const BOARD_AUDIT_ACTION_RULES: AuditActionRule[] = [
tryStatusRule,
tryApptRule,
tryScheduleRule,
tryCarriedOverRule,
tryPastDueRule,
tryCreatedRule,
];