import { DOC_STATUS_API } from "@/domain/work-orders/types/work-order-board"; import type { BoardMediaCategory, WorkOrderBoardCompletion, WorkOrderBoardDetail, WorkOrderBoardMediaItem, } from "@/domain/work-orders/types/work-order-board-detail"; import { BOARD_MEDIA_CATEGORY_API } from "@/domain/work-orders/types/work-order-board-detail"; import type { CompletionDocStatus } from "@/domain/work-orders/types/work-order-table-row"; import type { WorkOrderAuditEntry, WorkOrderComment, } from "@/domain/work-orders/types/work-order-detail"; import { mapWorkOrderTableRow } from "@/domain/work-orders/mappers/work-order-mapper"; import type { FileCategory, MediaCategory, MediaFile, } from "@/domain/work-orders/types/work-order-media"; function asRecord(raw: unknown): Record { return typeof raw === "object" && raw !== null ? (raw as Record) : {}; } function readString(record: Record, ...keys: string[]): string { for (const key of keys) { const value = record[key]; if (typeof value === "string") { return value; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } } return ""; } function readNumber(record: Record, ...keys: string[]): number | null { for (const key of keys) { const value = record[key]; if (typeof value === "number" && Number.isFinite(value)) { return value; } if (typeof value === "string" && value.trim() !== "") { const parsed = Number(value); if (Number.isFinite(parsed)) { return parsed; } } } return null; } function readBoolean(record: Record, ...keys: string[]): boolean { for (const key of keys) { const value = record[key]; if (typeof value === "boolean") { return value; } } return false; } function readStringArray(record: Record, ...keys: string[]): string[] { for (const key of keys) { const value = record[key]; if (!Array.isArray(value)) { continue; } const mapped = value .map((entry) => { if (typeof entry === "string") { return entry; } if (typeof entry === "number" || typeof entry === "boolean") { return String(entry); } return ""; }) .filter(Boolean); if (mapped.length > 0) { return mapped; } } return []; } function mapDocStatus(raw: unknown): CompletionDocStatus { const numeric = typeof raw === "number" ? raw : Number(raw); if (numeric === DOC_STATUS_API.Yes) return "Yes"; if (numeric === DOC_STATUS_API.No) return "No"; if (numeric === DOC_STATUS_API.NotNeeded) return "NN"; if (raw === "Yes" || raw === "No" || raw === "NN") return raw; if (raw === "NotNeeded") return "NN"; return ""; } function mapMediaCategory(raw: unknown): BoardMediaCategory { const numeric = typeof raw === "number" ? raw : Number(raw); if (numeric === BOARD_MEDIA_CATEGORY_API.Before) return "Before"; if (numeric === BOARD_MEDIA_CATEGORY_API.After) return "After"; if (numeric === BOARD_MEDIA_CATEGORY_API.Extra) return "Extra"; if (numeric === BOARD_MEDIA_CATEGORY_API.Completion) return "Completion"; return "Extra"; } export function mapBoardDetailComment(raw: unknown): WorkOrderComment { const item = asRecord(raw); const id = readNumber(item, "id"); const authorId = readString(item, "authorId", "AuthorId") || null; return { id: id ?? (readString(item, "id") || `comment-${readString(item, "time")}`), createdDate: readString(item, "time", "createdDate", "CreatedDate"), commenttext: readString(item, "text", "commenttext", "Commenttext"), commentType: readString(item, "commentType", "CommentType") || "internal", userName: readString(item, "userName", "UserName", "authorName", "AuthorName"), authorId, commenter: readString(item, "commenter", "Commenter") || null, recordType: readString(item, "recordType", "RecordType") || null, mentions: readStringArray(item, "mentions", "Mentions"), }; } export function mapBoardDetailAuditEntry(raw: unknown, index: number): WorkOrderAuditEntry { const item = asRecord(raw); const time = readString(item, "time", "createdAt", "CreatedAt"); const typeRaw = readString(item, "type", "Type").toLowerCase(); const type: "manual" | "system" = typeRaw === "system" ? "system" : "manual"; const dispatcherId = readString(item, "dispatcherId", "DispatcherId"); const userName = readString(item, "userName", "UserName"); return { id: `${time || "audit"}-${index}`, fieldName: readString(item, "fieldName", "FieldName"), oldValue: readString(item, "oldValue", "OldValue"), newValue: readString(item, "newValue", "NewValue"), action: readString(item, "action", "Action"), createdAt: time, userName: type === "system" ? "System" : userName, type, dispatcherId: dispatcherId || undefined, }; } export type UploadableMediaCategory = "Before" | "After" | "Extra"; export function buildMediaFormData(file: File, category?: UploadableMediaCategory): FormData { const form = new FormData(); if (category) { form.append("category", category); } form.append("file", file); return form; } export function buildCompletionDocFormData(params: { file: File; workOrderVersion: string; signOffName?: string; signOffSignature?: string; }): FormData { const form = new FormData(); form.append("file", params.file); form.append("workOrderVersion", params.workOrderVersion); if (params.signOffName) { form.append("signOffName", params.signOffName); } if (params.signOffSignature) { form.append("signOffSignature", params.signOffSignature); } return form; } export function mapCompletion(raw: unknown): WorkOrderBoardCompletion { const item = asRecord(raw); const templateRaw = item.template ?? item.Template; const templateRecord = templateRaw != null ? asRecord(templateRaw) : null; const signoffsRaw = item.dispatchSignoffs ?? item.DispatchSignoffs; const signoffs = Array.isArray(signoffsRaw) ? signoffsRaw.map((entry) => { const s = asRecord(entry); return { dispatchId: readNumber(s, "dispatchId", "DispatchId"), signoffType: readString(s, "signoffType", "SignoffType"), name: readString(s, "name", "Name"), signedAt: readString(s, "signedAt", "SignedAt"), }; }) : []; return { docStatus: mapDocStatus(item.docStatus ?? item.DocStatus), template: templateRecord ? { id: readNumber(templateRecord, "id", "Id") ?? 0, name: readString(templateRecord, "name", "Name"), serviceKey: readString(templateRecord, "serviceKey", "ServiceKey"), workOrderType: readNumber(templateRecord, "workOrderType", "WorkOrderType"), templateUrl: readString(templateRecord, "templateUrl", "TemplateUrl"), isActive: readBoolean(templateRecord, "isActive", "IsActive"), } : null, signOffName: readString(item, "signOffName", "SignOffName"), signOffAttachment: readString(item, "signOffAttachment", "SignOffAttachment"), signOffSignature: readString(item, "signOffSignature", "SignOffSignature"), dispatchSignoffs: signoffs, }; } export function mapBoardDetailMediaItem(raw: unknown): WorkOrderBoardMediaItem { const item = asRecord(raw); const id = readNumber(item, "id"); const isLegacyFlag = readBoolean(item, "isLegacy", "IsLegacy"); return { id: String(id ?? (readString(item, "id") || cryptoRandomId())), category: mapMediaCategory(item.category ?? item.Category), url: readString(item, "url", "Url"), fileName: readString(item, "fileName", "FileName") || "file", uploadedAt: readString(item, "uploadedAt", "UploadedAt"), isLegacy: isLegacyFlag || (id != null && id <= 0), }; } function cryptoRandomId(): string { return `media-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; } function classifyFileName(fileName: string, url: string): FileCategory { const name = `${fileName} ${url}`.toLowerCase(); if (/\.(jpe?g|png|gif|webp|bmp)(\?|$)/i.test(name) || name.includes("image/")) { return "image"; } if (/\.(mp4|mov|webm|m4v)(\?|$)/i.test(name) || name.includes("video/")) { return "video"; } if (/\.pdf(\?|$)/i.test(name)) { return "pdf"; } if (/\.(docx?|xlsx?|pptx?)(\?|$)/i.test(name)) { return "doc"; } return "other"; } function boardCategoryToMediaCategory(category: BoardMediaCategory): MediaCategory { if (category === "Extra") return "Other"; if (category === "Completion") return "Report"; return "Photo"; } /** Adapt board detail media items for existing MediaUploader / Extras UI. */ export function boardMediaItemsToMediaFiles(items: WorkOrderBoardMediaItem[]): MediaFile[] { return items.map((item) => ({ id: item.id, name: item.fileName, fileType: classifyFileName(item.fileName, item.url), url: item.url, category: boardCategoryToMediaCategory(item.category), purpose: item.category, isLegacy: item.isLegacy || Number(item.id) <= 0, uploadStatus: "done" as const, })); } export function mapWorkOrderBoardDetail(raw: unknown): WorkOrderBoardDetail { const root = asRecord(raw); const infoRaw = root.info ?? root.Info ?? root; const commentsRaw = root.comments ?? root.Comments; const auditRaw = root.audit ?? root.Audit; const mediaRaw = root.media ?? root.Media; const completionRaw = root.completion ?? root.Completion; const comments = Array.isArray(commentsRaw) ? commentsRaw.map(mapBoardDetailComment) : []; const audit = Array.isArray(auditRaw) ? auditRaw.map((entry, index) => mapBoardDetailAuditEntry(entry, index)) : []; const media = Array.isArray(mediaRaw) ? mediaRaw.map(mapBoardDetailMediaItem) : []; return { info: mapWorkOrderTableRow(infoRaw), completion: mapCompletion(completionRaw ?? {}), comments, audit, media, }; }