From 83ae05be272aa734456af2469ea1fc134c2fdd91 Mon Sep 17 00:00:00 2001 From: "arthur.bassi" Date: Wed, 12 Aug 2026 16:07:26 -0300 Subject: [PATCH] chore(work-orders): resolve Add-On indicator conflicts with current dev --- scripts/governance-baseline.json | 13 +- .../_components/comp-doc/comp-doc-co-line.tsx | 19 ++ .../comp-doc/comp-doc-detail-row.tsx | 39 +++ .../comp-doc/comp-doc-details-section.tsx | 75 +++++ .../comp-doc-dialog-footer-draft-actions.tsx | 70 +++++ .../comp-doc/comp-doc-dialog-footer-draft.tsx | 50 ++++ ...mp-doc-dialog-footer-generated-actions.tsx | 62 ++++ .../comp-doc-dialog-footer-generated.tsx | 35 +++ ...doc-dialog-footer-not-required-actions.tsx | 53 ++++ .../comp-doc-dialog-footer-not-required.tsx | 47 ++++ .../comp-doc/comp-doc-dialog-footer.tsx | 94 +++++++ .../comp-doc/comp-doc-dialog-header.tsx | 96 +++++++ .../comp-doc-dialog-resource-links.tsx | 48 ++++ .../_components/comp-doc/comp-doc-dialog.tsx | 132 +++++++++ .../comp-doc/comp-doc-draft-toast.tsx | 29 ++ .../comp-doc/comp-doc-edit-row.tsx | 51 ++++ .../comp-doc/comp-doc-editable-row.tsx | 54 ++++ .../comp-doc/comp-doc-footer-btn-sx.ts | 8 + .../comp-doc/comp-doc-footer-button-sx.ts | 8 + .../comp-doc/comp-doc-not-required-stamp.tsx | 25 ++ .../comp-doc/comp-doc-notes-field.tsx | 50 ++++ .../comp-doc/comp-doc-preview-badge.tsx | 32 +++ .../comp-doc-preview-company-header.tsx | 36 +++ .../comp-doc/comp-doc-preview-document.tsx | 58 ++++ .../comp-doc/comp-doc-preview-header.tsx | 36 +++ .../comp-doc-preview-not-required-stamp.tsx | 25 ++ .../comp-doc-preview-procedure-notes.tsx | 68 +++++ .../comp-doc/comp-doc-preview-procedure.tsx | 87 ++++++ .../comp-doc-preview-service-info.tsx | 89 ++++++ .../comp-doc-preview-status-badge.tsx | 32 +++ .../comp-doc/comp-doc-preview-summary.tsx | 96 +++++++ .../comp-doc-preview-vendor-safety.tsx | 35 +++ .../_components/comp-doc/comp-doc-preview.tsx | 65 +++++ .../comp-doc/comp-doc-procedure-list.tsx | 32 +++ .../_components/comp-doc/comp-doc-sidebar.tsx | 174 ++++++++++++ .../comp-doc/comp-doc-status-card.tsx | 61 ++++ .../comp-doc/use-comp-doc-dialog-seed.ts | 59 ++++ .../comp-doc/use-comp-doc-dialog.tsx | 179 ++++++++++++ .../use-comp-doc-not-required-actions.ts | 58 ++++ .../comp-doc/use-comp-doc-pdf-actions.ts | 98 +++++++ .../comp-doc/use-comp-doc-persist-flow.ts | 84 ++++++ .../_components/detail/audit-timeline.tsx | 67 +++++ .../detail/slide-over-audit-list.tsx | 164 +++++++++++ .../_components/detail/slide-over-footer.tsx | 143 ++++++++++ .../_components/detail/slide-over-header.tsx | 106 +++++++ .../detail/slide-over-info-row.tsx | 22 ++ .../detail/tabs/slide-over-audit-tab.tsx | 11 + .../detail/tabs/slide-over-comments-tab.tsx | 265 ++++++++++++++++++ .../tabs/slide-over-completion-actions.tsx | 92 ++++++ .../tabs/slide-over-completion-media.tsx | 52 ++++ .../slide-over-completion-readonly-status.tsx | 45 +++ .../tabs/slide-over-completion-signoffs.tsx | 35 +++ .../detail/tabs/slide-over-completion-tab.tsx | 143 ++++++++++ .../detail/tabs/slide-over-doc-badge.tsx | 44 +++ .../detail/tabs/slide-over-extras-tab.tsx | 163 +++++++++++ .../slide-over-info-tab-assigned-field.tsx | 28 ++ .../tabs/slide-over-info-tab-display-view.tsx | 92 ++++++ .../tabs/slide-over-info-tab-edit-view.tsx | 160 +++++++++++ .../tabs/slide-over-info-tab-status-field.tsx | 26 ++ .../tabs/slide-over-info-tab-vendor-field.tsx | 45 +++ .../detail/tabs/slide-over-info-tab.tsx | 64 +++++ .../detail/use-slide-over-edit-state.ts | 162 +++++++++++ .../detail/use-slide-over-media-state.ts | 101 +++++++ .../detail/use-work-order-slide-over.ts | 160 +++++++++++ .../detail/work-order-activity-section.tsx | 101 +++++++ .../detail/work-order-assignee-menu.tsx | 56 ++++ .../work-order-description-accordion.tsx | 20 ++ .../detail/work-order-detail-sections.tsx | 49 ++++ .../detail/work-order-details-card.tsx | 45 +++ .../work-order-dispatches-accordion.tsx | 85 ++++++ .../detail/work-order-simple-accordions.tsx | 61 ++++ .../detail/work-order-slide-over-tab-nav.tsx | 52 ++++ .../work-order-slide-over-tab-panel.tsx | 33 +++ .../detail/work-order-slide-over-tabs.tsx | 156 +++++++++++ .../detail/work-order-slide-over.tsx | 108 +++++++ .../detail/work-order-status-menu.tsx | 50 ++++ .../table/build-inline-draft-create-input.ts | 7 +- .../list/table/cells/comp-doc-cell.tsx | 18 ++ .../list/table/cells/date-cell.tsx | 18 ++ .../list/table/cells/site-cell.tsx | 14 +- .../table/cells/site-dialog-poc-fields.tsx | 76 +++-- .../list/table/cells/site-dialog.tsx | 143 ++++------ .../list/table/cells/use-site-dialog-state.ts | 142 ++++++++++ .../list/table/inline-create-field.tsx | 32 +++ .../list/table/to-inline-draft-row.ts | 1 + .../_components/list/table/wo-table-cols.ts | 3 +- .../table/wo-table-inline-identity-cells.tsx | 93 ++++++ .../table/wo-table-inline-row-actions.tsx | 11 +- .../list/table/wo-table-inline-row-cells.tsx | 76 +++-- .../list/table/wo-table-inline-row.tsx | 87 ++++-- .../list/table/wo-table-row-service-cells.tsx | 16 +- .../list/work-order-cancel-dialog.tsx | 50 ++++ .../list/work-orders-list-overlays.tsx | 90 ++++++ .../_components/media/media-upload-modal.tsx | 93 +++++- .../media/media-uploader-dropzone.tsx | 60 ++++ .../media/media-uploader-grid-item-chrome.tsx | 113 ++++++++ .../media/media-uploader-grid-item-meta.tsx | 67 +++++ .../media-uploader-grid-item-preview.tsx | 52 ++++ .../media/media-uploader-grid-item.tsx | 46 +++ .../_components/media/media-uploader-grid.tsx | 42 +++ .../_components/media/media-uploader.tsx | 208 ++++++-------- .../_hooks/create-comp-doc-dialog-actions.ts | 170 +++++++++++ .../workorders/_hooks/use-comp-doc-dialog.ts | 157 +++++++++++ .../_hooks/use-media-upload-session.ts | 199 +++++++++++++ .../_hooks/use-work-orders-list-page.ts | 131 +++++++++ .../_hooks/use-work-orders-table-ui-state.ts | 8 +- src/app/(protected)/workorders/index.tsx | 147 +++++----- .../api/work-order-board-documents-api.ts | 50 +++- .../mappers/assign-wizard-schedule-fields.ts | 4 +- .../work-orders/mappers/doc-status-mapper.ts | 28 ++ .../mappers/expand-scalar-board-ops.ts | 9 +- .../mappers/wizard-draft-mapper.ts | 1 + .../mappers/work-order-board-detail-mapper.ts | 8 +- .../work-orders/mappers/work-order-mapper.ts | 10 +- .../mappers/work-order-table-row-mapper.ts | 1 + .../work-orders/mocks/wo-wizard-mock-data.ts | 1 + .../types/work-order-board-patch.ts | 8 + .../work-orders/types/work-order-board.ts | 3 + .../work-orders/types/work-order-detail.ts | 6 + .../work-orders/types/work-order-media.ts | 6 + .../work-orders/types/work-order-table-row.ts | 2 + .../use-cases/use-work-order-mutations.ts | 45 ++- .../use-work-order-table-mutations.ts | 126 ++++++--- .../utils/completion-doc-service-gate.ts | 4 + .../utils/completion-doc-service-title.ts | 19 +- .../utils/get-inline-create-missing.ts | 62 ++++ .../utils/run-sequential-uploads.ts | 24 ++ .../work-orders/utils/slide-over-draft.ts | 4 +- .../utils/work-order-media-limits.ts | 17 ++ .../utils/work-order-patch-queue.ts | 20 ++ .../build-inline-draft-create-input.test.ts | 55 ++++ .../comp-doc-cell-service-gate.test.tsx | 29 ++ ...mp-doc-dialog-completion-contract.test.tsx | 230 +++++++++++++++ .../comp-doc-dialog-footer-exclusive.test.tsx | 53 ++++ .../media-upload-modal-api.test.tsx | 115 ++++++++ .../use-comp-doc-dialog-address-seed.test.tsx | 149 ++++++++++ .../use-media-upload-session.test.tsx | 206 ++++++++++++++ .../workorders/use-site-dialog-state.test.tsx | 112 ++++++++ .../use-slide-over-media-state.test.tsx | 143 ++++++++++ .../wo-table-unscheduled-highlight.test.tsx | 1 + ...work-order-slide-over-failed-save.test.tsx | 172 ++++++++++++ ...ork-orders-advanced-search-wiring.test.tsx | 7 + ...ork-orders-board-core-affordances.test.tsx | 23 +- .../app/v/dispatch-snapshot-details.test.tsx | 52 ++++ .../work-orders/api/work-orders-api.test.ts | 69 ++++- .../completed-date-board-column.test.ts | 26 ++ .../work-order-board-detail-mapper.test.ts | 29 ++ .../mappers/work-order-board-mapper.test.ts | 15 + .../mappers/work-order-form-payload.test.ts | 14 + .../mappers/work-order-mapper.test.ts | 37 +++ ...der-table-mutations-completed-date.test.ts | 50 ++++ .../use-work-order-table-mutations.test.tsx | 51 ++++ .../completion-doc-service-title.test.ts | 27 ++ .../utils/get-inline-create-missing.test.ts | 82 ++++++ .../utils/run-sequential-uploads.test.ts | 65 +++++ .../utils/slide-over-draft.test.ts | 15 + .../slide-over-failed-save-baseline.test.ts | 100 +++++++ .../utils/slide-over-save-contract.test.ts | 76 +++++ .../utils/table-filter-utils.test.ts | 1 + .../utils/wizard-date-utils.test.ts | 28 +- .../utils/work-order-media-limits.test.ts | 14 + .../utils/work-order-patch-queue.test.ts | 62 ++++ 162 files changed, 9912 insertions(+), 492 deletions(-) create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-co-line.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-detail-row.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft-actions.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated-actions.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required-actions.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-header.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-resource-links.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-draft-toast.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-edit-row.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-editable-row.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-btn-sx.ts create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-button-sx.ts create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-not-required-stamp.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-notes-field.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-badge.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-company-header.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-document.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-header.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-not-required-stamp.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-procedure-notes.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-procedure.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-service-info.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-status-badge.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-summary.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview-vendor-safety.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-preview.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-procedure-list.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-sidebar.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/comp-doc-status-card.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog-seed.ts create mode 100644 src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx create mode 100644 src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-not-required-actions.ts create mode 100644 src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-pdf-actions.ts create mode 100644 src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-persist-flow.ts create mode 100644 src/app/(protected)/workorders/_components/detail/audit-timeline.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/slide-over-audit-list.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/slide-over-footer.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/slide-over-header.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/slide-over-info-row.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-audit-tab.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-comments-tab.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-actions.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-media.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-readonly-status.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-signoffs.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-tab.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-doc-badge.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-extras-tab.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-assigned-field.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-display-view.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-status-field.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-vendor-field.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/use-slide-over-edit-state.ts create mode 100644 src/app/(protected)/workorders/_components/detail/use-slide-over-media-state.ts create mode 100644 src/app/(protected)/workorders/_components/detail/use-work-order-slide-over.ts create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-activity-section.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-assignee-menu.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-description-accordion.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-detail-sections.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-details-card.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-dispatches-accordion.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-simple-accordions.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-slide-over-tab-nav.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-slide-over-tab-panel.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-slide-over-tabs.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-slide-over.tsx create mode 100644 src/app/(protected)/workorders/_components/detail/work-order-status-menu.tsx create mode 100644 src/app/(protected)/workorders/_components/list/table/cells/use-site-dialog-state.ts create mode 100644 src/app/(protected)/workorders/_components/list/table/inline-create-field.tsx create mode 100644 src/app/(protected)/workorders/_components/list/table/wo-table-inline-identity-cells.tsx create mode 100644 src/app/(protected)/workorders/_components/list/work-order-cancel-dialog.tsx create mode 100644 src/app/(protected)/workorders/_components/list/work-orders-list-overlays.tsx create mode 100644 src/app/(protected)/workorders/_components/media/media-uploader-dropzone.tsx create mode 100644 src/app/(protected)/workorders/_components/media/media-uploader-grid-item-chrome.tsx create mode 100644 src/app/(protected)/workorders/_components/media/media-uploader-grid-item-meta.tsx create mode 100644 src/app/(protected)/workorders/_components/media/media-uploader-grid-item-preview.tsx create mode 100644 src/app/(protected)/workorders/_components/media/media-uploader-grid-item.tsx create mode 100644 src/app/(protected)/workorders/_components/media/media-uploader-grid.tsx create mode 100644 src/app/(protected)/workorders/_hooks/create-comp-doc-dialog-actions.ts create mode 100644 src/app/(protected)/workorders/_hooks/use-comp-doc-dialog.ts create mode 100644 src/app/(protected)/workorders/_hooks/use-media-upload-session.ts create mode 100644 src/app/(protected)/workorders/_hooks/use-work-orders-list-page.ts create mode 100644 src/domain/work-orders/mappers/doc-status-mapper.ts create mode 100644 src/domain/work-orders/types/work-order-board-patch.ts create mode 100644 src/domain/work-orders/utils/completion-doc-service-gate.ts create mode 100644 src/domain/work-orders/utils/get-inline-create-missing.ts create mode 100644 src/domain/work-orders/utils/run-sequential-uploads.ts create mode 100644 src/domain/work-orders/utils/work-order-patch-queue.ts create mode 100644 src/test/app/(protected)/workorders/build-inline-draft-create-input.test.ts create mode 100644 src/test/app/(protected)/workorders/comp-doc-cell-service-gate.test.tsx create mode 100644 src/test/app/(protected)/workorders/comp-doc-dialog-completion-contract.test.tsx create mode 100644 src/test/app/(protected)/workorders/comp-doc-dialog-footer-exclusive.test.tsx create mode 100644 src/test/app/(protected)/workorders/media-upload-modal-api.test.tsx create mode 100644 src/test/app/(protected)/workorders/use-comp-doc-dialog-address-seed.test.tsx create mode 100644 src/test/app/(protected)/workorders/use-media-upload-session.test.tsx create mode 100644 src/test/app/(protected)/workorders/use-site-dialog-state.test.tsx create mode 100644 src/test/app/(protected)/workorders/use-slide-over-media-state.test.tsx create mode 100644 src/test/app/(protected)/workorders/work-order-slide-over-failed-save.test.tsx create mode 100644 src/test/app/v/dispatch-snapshot-details.test.tsx create mode 100644 src/test/domain/work-orders/mappers/completed-date-board-column.test.ts create mode 100644 src/test/domain/work-orders/use-cases/use-work-order-table-mutations-completed-date.test.ts create mode 100644 src/test/domain/work-orders/utils/completion-doc-service-title.test.ts create mode 100644 src/test/domain/work-orders/utils/get-inline-create-missing.test.ts create mode 100644 src/test/domain/work-orders/utils/run-sequential-uploads.test.ts create mode 100644 src/test/domain/work-orders/utils/slide-over-failed-save-baseline.test.ts create mode 100644 src/test/domain/work-orders/utils/slide-over-save-contract.test.ts create mode 100644 src/test/domain/work-orders/utils/work-order-patch-queue.test.ts diff --git a/scripts/governance-baseline.json b/scripts/governance-baseline.json index 401e1e70..3862e898 100644 --- a/scripts/governance-baseline.json +++ b/scripts/governance-baseline.json @@ -2,16 +2,5 @@ "version": 1, "purpose": "Frozen grandfathered debt inventory for the frontend governance checks. New entries and cap increases fail the governance comparison. Existing caps may only decrease and entries must be removed when compliant.", "maxFileLines": 500, - "godfileDebt": [ - { - "path": "src/app/(protected)/workorders/[id].tsx", - "maxLines": 797, - "reason": "Work-order detail route page; legacy godfile targeted for decomposition into focused state components." - }, - { - "path": "src/app/(protected)/vendors/index.tsx", - "maxLines": 612, - "reason": "Vendors list route; legacy page with mixed query/filter/table state pending extraction." - } - ] + "godfileDebt": [] } diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-co-line.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-co-line.tsx new file mode 100644 index 00000000..5d3804bb --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-co-line.tsx @@ -0,0 +1,19 @@ +import type { ElementType } from "react"; + +type CompDocCoLineProps = { + icon: ElementType; + text: string; + bold?: boolean; +}; + +export function CompDocCoLine({ icon: Icon, text, bold }: CompDocCoLineProps) { + return ( +
+ {text} + +
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-detail-row.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-detail-row.tsx new file mode 100644 index 00000000..c5f08558 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-detail-row.tsx @@ -0,0 +1,39 @@ +import type { ElementType } from "react"; + +type CompDocDetailRowProps = { + icon: ElementType; + label: string; + value: string; + last?: boolean; +}; + +export function CompDocDetailRow({ icon: Icon, label, value, last }: CompDocDetailRowProps) { + return ( +
+ +
+
{label}
+
+ {value} +
+
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx new file mode 100644 index 00000000..3a38f2e3 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-details-section.tsx @@ -0,0 +1,75 @@ +import { Building2, MapPin, Users, Wrench } from "lucide-react"; +import type { CompDocSiteInfo } from "@/domain/work-orders/types/completion-doc"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { CompDocDetailRow } from "./comp-doc-detail-row"; +import { CompDocEditableRow } from "./comp-doc-editable-row"; + +type CompDocDetailsSectionProps = { + row: WorkOrderTableRow; + site: CompDocSiteInfo; + editAddress: string; + onAddressChange: (value: string) => void; + editDateCompleted: string; + onDateCompletedChange: (value: string) => void; + amazonRep: string; + seaRep: string; + isGenerated: boolean; +}; + +export function CompDocDetailsSection({ + row, + site, + editAddress, + onAddressChange, + editDateCompleted, + onDateCompletedChange, + amazonRep, + seaRep, + isGenerated, +}: CompDocDetailsSectionProps) { + return ( +
+
+ Work Order Details +
+
+ + + + + + +
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft-actions.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft-actions.tsx new file mode 100644 index 00000000..86749598 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft-actions.tsx @@ -0,0 +1,70 @@ +import type { RefObject } from "react"; +import { Ban, Save, Sparkles, Upload } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { COMP_DOC_FOOTER_BTN_SX } from "./comp-doc-footer-button-sx"; + +type CompDocDialogFooterDraftActionsProps = { + rowVersion?: string | number | null; + isUploading: boolean; + pdfInputRef: RefObject; + onMarkNotRequired: () => void; + onSaveDraft: () => void; + onGenerate: () => void; +}; + +export function CompDocDialogFooterDraftActions({ + rowVersion, + isUploading, + pdfInputRef, + onMarkNotRequired, + onSaveDraft, + onGenerate, +}: CompDocDialogFooterDraftActionsProps) { + return ( + <> + +
+ + + +
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft.tsx new file mode 100644 index 00000000..fad1529f --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-draft.tsx @@ -0,0 +1,50 @@ +import { Ban, Save, Sparkles } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { COMP_DOC_FOOTER_BTN_SX } from "./comp-doc-footer-btn-sx"; + +type CompDocDialogFooterDraftProps = { + onMarkNotRequired: () => void; + onSaveDraft: () => void; + onGenerate: () => void; +}; + +export function CompDocDialogFooterDraft({ + onMarkNotRequired, + onSaveDraft, + onGenerate, +}: CompDocDialogFooterDraftProps) { + return ( + <> + +
+ + +
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated-actions.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated-actions.tsx new file mode 100644 index 00000000..5c006a97 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated-actions.tsx @@ -0,0 +1,62 @@ +import type { RefObject } from "react"; +import { CheckCircle2, Download, Upload } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { COMP_DOC_FOOTER_BTN_SX } from "./comp-doc-footer-button-sx"; + +type CompDocDialogFooterGeneratedActionsProps = { + generatedAt: string; + signOffAttachment?: string | null; + isDocumentsReadOnly: boolean; + rowVersion?: string | number | null; + isUploading: boolean; + pdfInputRef: RefObject; + onDownloadPdf: () => void; +}; + +export function CompDocDialogFooterGeneratedActions({ + generatedAt, + signOffAttachment, + isDocumentsReadOnly, + rowVersion, + isUploading, + pdfInputRef, + onDownloadPdf, +}: CompDocDialogFooterGeneratedActionsProps) { + return ( + <> + + {" "} + {signOffAttachment ? "Document on file" : `Generated ${generatedAt}`} + +
+ {!isDocumentsReadOnly && ( + + )} + +
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated.tsx new file mode 100644 index 00000000..91b9792c --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-generated.tsx @@ -0,0 +1,35 @@ +import { CheckCircle2, Download } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { COMP_DOC_FOOTER_BTN_SX } from "./comp-doc-footer-btn-sx"; + +type CompDocDialogFooterGeneratedProps = { + generatedAt: string; + onDownloadPdf: () => void; +}; + +export function CompDocDialogFooterGenerated({ + generatedAt, + onDownloadPdf, +}: CompDocDialogFooterGeneratedProps) { + return ( + <> + + Generated {generatedAt} + +
+ +
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required-actions.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required-actions.tsx new file mode 100644 index 00000000..79d16bca --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required-actions.tsx @@ -0,0 +1,53 @@ +import { Ban, Sparkles, Undo2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { COMP_DOC_FOOTER_BTN_SX } from "./comp-doc-footer-button-sx"; + +type CompDocDialogFooterNotRequiredActionsProps = { + isDocumentsReadOnly: boolean; + onUndo: () => void; +}; + +export function CompDocDialogFooterNotRequiredActions({ + isDocumentsReadOnly, + onUndo, +}: CompDocDialogFooterNotRequiredActionsProps) { + return ( + <> + + Marked{" "} + not required for this work order + +
+ {!isDocumentsReadOnly && ( + + )} + +
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required.tsx new file mode 100644 index 00000000..da1976bb --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer-not-required.tsx @@ -0,0 +1,47 @@ +import { Ban, Sparkles, Undo2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { COMP_DOC_FOOTER_BTN_SX } from "./comp-doc-footer-btn-sx"; + +type CompDocDialogFooterNotRequiredProps = { + onUndo: () => void; +}; + +export function CompDocDialogFooterNotRequired({ onUndo }: CompDocDialogFooterNotRequiredProps) { + return ( + <> + + Marked{" "} + not required for this work order + +
+ + +
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer.tsx new file mode 100644 index 00000000..b21cbd05 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-footer.tsx @@ -0,0 +1,94 @@ +import type { RefObject } from "react"; +import type { DocState } from "@/domain/work-orders/types/completion-doc"; +import { CompDocDialogFooterDraftActions } from "./comp-doc-dialog-footer-draft-actions"; +import { CompDocDialogFooterGeneratedActions } from "./comp-doc-dialog-footer-generated-actions"; +import { CompDocDialogFooterNotRequiredActions } from "./comp-doc-dialog-footer-not-required-actions"; + +type CompDocDialogFooterProps = { + docState: DocState; + isDocumentsReadOnly: boolean; + rowVersion?: string | number | null; + isUploading: boolean; + signOffAttachment?: string | null; + generatedAt: string; + pdfInputRef: RefObject; + onMarkNotRequired: () => void; + onSaveDraft: () => void; + onGenerate: () => void; + onUndo: () => void; + onDownloadPdf: () => void; + onUploadPdf: (files: FileList | null) => void; +}; + +export function CompDocDialogFooter({ + docState, + isDocumentsReadOnly, + rowVersion, + isUploading, + signOffAttachment, + generatedAt, + pdfInputRef, + onMarkNotRequired, + onSaveDraft, + onGenerate, + onUndo, + onDownloadPdf, + onUploadPdf, +}: CompDocDialogFooterProps) { + // Exclusive footer branches (prototype: one docState at a time). A signed PDF + // on file implies Generated even if local docState has not caught up yet — never + // render Draft actions alongside Generated. + const showNotRequired = docState === "not-required"; + const showGenerated = + !showNotRequired && (docState === "generated" || Boolean(signOffAttachment)); + const showDraft = !showNotRequired && !showGenerated && docState === "draft"; + + return ( +
+ { + onUploadPdf(event.target.files); + event.target.value = ""; + }} + /> + {Boolean(showDraft && !isDocumentsReadOnly) && ( + + )} + {showGenerated && ( + + )} + {showNotRequired && ( + + )} +
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-header.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-header.tsx new file mode 100644 index 00000000..08a9cb20 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-header.tsx @@ -0,0 +1,96 @@ +import { FileText, X } from "lucide-react"; +import type { CompletionDocStateConfig } from "@/domain/work-orders/types/completion-doc"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { serviceTitle } from "@/domain/work-orders/utils/completion-doc-service-title"; + +type CompDocDialogHeaderProps = { + info: WorkOrderTableRow; + cfg: CompletionDocStateConfig; + isUploading: boolean; + onClose: () => void; +}; + +export function CompDocDialogHeader({ info, cfg, isUploading, onClose }: CompDocDialogHeaderProps) { + return ( +
+
+ +
+
+ Completion Document +
+
+ WO{" "} + + #{info.woNumber} + + {" · "} + {serviceTitle(info)} +
+
+
+
+ + + {cfg.pillLabel} + + +
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-resource-links.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-resource-links.tsx new file mode 100644 index 00000000..bb772f0b --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog-resource-links.tsx @@ -0,0 +1,48 @@ +import { Download, FileText } from "lucide-react"; + +type CompDocDialogResourceLinksProps = { + templateUrl?: string | null; + signOffAttachment?: string | null; +}; + +export function CompDocDialogResourceLinks({ + templateUrl, + signOffAttachment, +}: CompDocDialogResourceLinksProps) { + if (!templateUrl && !signOffAttachment) return null; + + return ( +
+ {Boolean(templateUrl) && ( + + + Download blank template + + )} + {Boolean(signOffAttachment) && ( + + + Open uploaded PDF + + )} +
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog.tsx new file mode 100644 index 00000000..f87fbb78 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog.tsx @@ -0,0 +1,132 @@ +import type { MouseEvent } from "react"; +import { Dialog, DialogContent, DialogTitle } from "@mui/material"; +import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdrop"; +import { Text } from "@/components/ui/text"; +import type { CompDocData, CompDocSaveStatus } from "@/domain/work-orders/types/completion-doc"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { CompDocDialogFooter } from "./comp-doc-dialog-footer"; +import { CompDocDialogHeader } from "./comp-doc-dialog-header"; +import { CompDocDialogResourceLinks } from "./comp-doc-dialog-resource-links"; +import { CompDocPreview } from "./comp-doc-preview"; +import { CompDocSidebar } from "./comp-doc-sidebar"; +import { useCompDocDialog } from "./use-comp-doc-dialog"; + +type CompDocDialogProps = { + row: WorkOrderTableRow; + open: boolean; + onOpenChange: (open: boolean) => void; + data?: CompDocData; + onSave: (data: CompDocData, status: CompDocSaveStatus) => void | Promise; + onDateCompletedChange?: (date: string) => void | Promise; +}; + +export function CompDocDialog({ + row, + open, + onOpenChange, + data, + onSave, + onDateCompletedChange, +}: CompDocDialogProps) { + const dialog = useCompDocDialog({ + row, + open, + data, + onSave, + onDateCompletedChange, + }); + const handleClose = dialog.isUploading ? undefined : () => onOpenChange(false); + + return ( + ) => e.stopPropagation(), + }, + }} + > + + + Completion Document — WO #{dialog.info.woNumber} + + + Generate, save or mark not-required the completion document for this work order. + + +
+ handleClose?.()} + /> + + + +
+ + +
+ + +
+
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-draft-toast.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-draft-toast.tsx new file mode 100644 index 00000000..910b7227 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-draft-toast.tsx @@ -0,0 +1,29 @@ +import { CheckCircle2 } from "lucide-react"; +import { toast } from "react-toastify"; + +export function showCompDocDraftSavedToast() { + toast( +
+ Draft saved +
, + { + position: "bottom-center", + autoClose: 3000, + hideProgressBar: true, + closeButton: false, + icon: false, + className: "!min-h-0 !bg-transparent !p-0 !shadow-none", + }, + ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-edit-row.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-edit-row.tsx new file mode 100644 index 00000000..3823f39b --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-edit-row.tsx @@ -0,0 +1,51 @@ +import type { CSSProperties } from "react"; + +const COMP_DOC_INPUT_CLS = "w-full rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1"; + +const COMP_DOC_INPUT_STYLE: CSSProperties = { + fontSize: 13, + border: "1px solid var(--color-border)", + color: "var(--color-text-primary)", + background: "var(--color-bg-surface)", +}; + +type CompDocEditRowProps = { + label: string; + value: string; + onChange: (value: string) => void; + type?: string; + readOnly?: boolean; +}; + +export function CompDocEditRow({ + label, + value, + onChange, + type = "text", + readOnly = false, +}: CompDocEditRowProps) { + return ( +
+
+ {label} +
+ onChange(e.target.value)} + readOnly={readOnly} + className={COMP_DOC_INPUT_CLS} + style={{ + ...COMP_DOC_INPUT_STYLE, + ...(readOnly + ? { + opacity: 0.6, + cursor: "default", + pointerEvents: "none" as const, + } + : {}), + }} + /> +
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-editable-row.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-editable-row.tsx new file mode 100644 index 00000000..8e9261e2 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-editable-row.tsx @@ -0,0 +1,54 @@ +import type { CSSProperties } from "react"; + +const COMP_DOC_INPUT_CLS = "w-full rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1"; + +const COMP_DOC_INPUT_STYLE: CSSProperties = { + fontSize: 13, + border: "1px solid var(--color-border)", + color: "var(--color-text-primary)", + background: "var(--color-bg-surface)", +}; + +const COMP_DOC_INPUT_DISABLED_STYLE: CSSProperties = { + opacity: 0.6, + cursor: "default", + pointerEvents: "none", +}; + +type CompDocEditableRowProps = { + label: string; + value: string; + onChange: (value: string) => void; + type?: string; + placeholder?: string; + disabled?: boolean; +}; + +export function CompDocEditableRow({ + label, + value, + onChange, + type = "text", + placeholder = "", + disabled = false, +}: CompDocEditableRowProps) { + return ( +
+
+ {label} +
+ onChange(e.target.value)} + readOnly={disabled} + placeholder={placeholder} + className={COMP_DOC_INPUT_CLS} + style={{ + ...COMP_DOC_INPUT_STYLE, + ...(disabled ? COMP_DOC_INPUT_DISABLED_STYLE : {}), + }} + /> +
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-btn-sx.ts b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-btn-sx.ts new file mode 100644 index 00000000..f5c0f2ba --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-btn-sx.ts @@ -0,0 +1,8 @@ +export const COMP_DOC_FOOTER_BTN_SX = { + minHeight: 28, + height: 28, + fontSize: 12, + px: 1.25, + py: 0.5, + gap: 0.75, +} as const; diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-button-sx.ts b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-button-sx.ts new file mode 100644 index 00000000..f5c0f2ba --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-footer-button-sx.ts @@ -0,0 +1,8 @@ +export const COMP_DOC_FOOTER_BTN_SX = { + minHeight: 28, + height: 28, + fontSize: 12, + px: 1.25, + py: 0.5, + gap: 0.75, +} as const; diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-not-required-stamp.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-not-required-stamp.tsx new file mode 100644 index 00000000..ab3cbf04 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-not-required-stamp.tsx @@ -0,0 +1,25 @@ +import { Ban } from "lucide-react"; + +export function CompDocNotRequiredStamp() { + return ( +
+
+ NOT REQUIRED +
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/comp-doc-notes-field.tsx b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-notes-field.tsx new file mode 100644 index 00000000..f6deb5e5 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/comp-doc-notes-field.tsx @@ -0,0 +1,50 @@ +type CompDocNotesFieldProps = { + notes: string; + onNotesChange: (value: string) => void; + isGenerated: boolean; +}; + +export function CompDocNotesField({ notes, onNotesChange, isGenerated }: CompDocNotesFieldProps) { + return ( +
+ +