mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 09:13:11 +00:00
feat(work-orders): add domain core types, api, mappers and use-cases
This commit is contained in:
parent
a3e8a533b1
commit
e13bc4c1a2
21 changed files with 1333 additions and 303 deletions
|
|
@ -1,6 +1,18 @@
|
|||
import { API_PATHS } from "@/api/api-paths";
|
||||
import { apiDelete, apiGet, apiPost } from "@/api/api";
|
||||
import { handleApiResponse, handlePaginatedResponse } from "@/api/handle-api-response";
|
||||
import {
|
||||
isWoTableMockEnabled,
|
||||
shouldUseMockWorkOrderDetail,
|
||||
} from "@/domain/work-orders/mocks/is-wo-table-mock-enabled";
|
||||
import {
|
||||
buildMockWorkOrderDetailFromTableRow,
|
||||
getMockWorkOrderLocations,
|
||||
getMockWorkOrderTableRow,
|
||||
getMockWorkOrderVendors,
|
||||
getMockWorkOrdersList,
|
||||
MOCK_WO_TABLE_USERS,
|
||||
} from "@/domain/work-orders/mocks/wo-table-mock-data";
|
||||
import {
|
||||
mapDispatchComment,
|
||||
mapDispatchDetail,
|
||||
|
|
@ -15,7 +27,7 @@ import {
|
|||
mapWorkOrderDetail,
|
||||
mapWorkOrderFormToCreatePayload,
|
||||
mapWorkOrderFormToUpdatePayload,
|
||||
mapWorkOrderListItems,
|
||||
mapWorkOrderTableRows,
|
||||
} from "@/domain/work-orders/mappers/work-order-mapper";
|
||||
import type { WorkOrderFormSchemaValues } from "@/domain/work-orders/schemas/work-order-schema";
|
||||
import type {
|
||||
|
|
@ -30,9 +42,12 @@ import type {
|
|||
WorkOrderDetail,
|
||||
WorkOrderFormValues,
|
||||
WorkOrderUser,
|
||||
WorkOrdersListParams,
|
||||
WorkOrdersListResult,
|
||||
} from "@/domain/work-orders/types/work-order";
|
||||
import {
|
||||
DISPATCH_LOCATION_WO_PAGE_SIZE,
|
||||
type WorkOrdersListParams,
|
||||
type WorkOrdersListResult,
|
||||
} from "@/domain/work-orders/types/work-order-list";
|
||||
|
||||
function buildSearchParams(params: WorkOrdersListParams): Record<string, string | number> {
|
||||
const searchParams: Record<string, string | number> = {
|
||||
|
|
@ -56,6 +71,18 @@ function buildSearchParams(params: WorkOrdersListParams): Record<string, string
|
|||
if (params.locationId) {
|
||||
searchParams.locationId = params.locationId;
|
||||
}
|
||||
if (params.dateFrom) {
|
||||
searchParams.dateFrom = params.dateFrom;
|
||||
}
|
||||
if (params.dateTo) {
|
||||
searchParams.dateTo = params.dateTo;
|
||||
}
|
||||
if (params.type) {
|
||||
searchParams.type = params.type;
|
||||
}
|
||||
if (params.assigneeIds) {
|
||||
searchParams.assigneeIds = params.assigneeIds;
|
||||
}
|
||||
|
||||
return searchParams;
|
||||
}
|
||||
|
|
@ -75,8 +102,17 @@ function mapUser(raw: unknown): WorkOrderUser {
|
|||
};
|
||||
}
|
||||
|
||||
async function mockNetworkDelay(): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
|
||||
export const workOrdersApi = {
|
||||
getList: async (params: WorkOrdersListParams = {}): Promise<WorkOrdersListResult> => {
|
||||
if (isWoTableMockEnabled()) {
|
||||
await mockNetworkDelay();
|
||||
return getMockWorkOrdersList(params);
|
||||
}
|
||||
|
||||
const searchParams = buildSearchParams(params);
|
||||
const data = await apiGet<unknown>(API_PATHS.workOrder.getList, { searchParams });
|
||||
const paginated = handlePaginatedResponse<unknown>(data, {
|
||||
|
|
@ -85,20 +121,45 @@ export const workOrdersApi = {
|
|||
});
|
||||
|
||||
return {
|
||||
items: mapWorkOrderListItems(paginated.items),
|
||||
items: mapWorkOrderTableRows(paginated.items),
|
||||
totalCount: paginated.totalCount,
|
||||
page: paginated.page,
|
||||
pageSize: paginated.pageSize,
|
||||
};
|
||||
},
|
||||
|
||||
getListByLocation: async (
|
||||
locationId: string | number,
|
||||
search = "",
|
||||
): Promise<WorkOrdersListResult> => {
|
||||
return workOrdersApi.getList({
|
||||
locationId,
|
||||
search,
|
||||
page: 1,
|
||||
pageSize: DISPATCH_LOCATION_WO_PAGE_SIZE,
|
||||
sortBy: "number",
|
||||
sortDir: "asc",
|
||||
});
|
||||
},
|
||||
|
||||
fetchUsers: async (): Promise<WorkOrderUser[]> => {
|
||||
if (isWoTableMockEnabled()) {
|
||||
return MOCK_WO_TABLE_USERS;
|
||||
}
|
||||
const data = await apiGet<unknown>(API_PATHS.user.list);
|
||||
const users = handleApiResponse<unknown[]>(data);
|
||||
return Array.isArray(users) ? users.map(mapUser) : [];
|
||||
},
|
||||
|
||||
getById: async (id: string | number): Promise<WorkOrderDetail> => {
|
||||
if (shouldUseMockWorkOrderDetail(id)) {
|
||||
await mockNetworkDelay();
|
||||
const row = getMockWorkOrderTableRow(id);
|
||||
if (!row) {
|
||||
throw new Error("Mock work order not found");
|
||||
}
|
||||
return buildMockWorkOrderDetailFromTableRow(row);
|
||||
}
|
||||
const data = await apiGet<unknown>(API_PATHS.workOrder.getById, {
|
||||
searchParams: { id },
|
||||
});
|
||||
|
|
@ -117,6 +178,22 @@ export const workOrdersApi = {
|
|||
id: string | number,
|
||||
values: WorkOrderFormSchemaValues | WorkOrderFormValues,
|
||||
): Promise<WorkOrderDetail> => {
|
||||
if (shouldUseMockWorkOrderDetail(id)) {
|
||||
await mockNetworkDelay();
|
||||
const row = getMockWorkOrderTableRow(id);
|
||||
if (!row) {
|
||||
throw new Error("Mock work order not found");
|
||||
}
|
||||
return buildMockWorkOrderDetailFromTableRow({
|
||||
...row,
|
||||
woNumber: values.workOrderNumber || row.woNumber,
|
||||
dueDate: values.dueDate || row.dueDate,
|
||||
scheduledOn: values.scheduledDate || row.scheduledOn,
|
||||
dispatcherName: values.assignedTo || row.dispatcherName,
|
||||
pm: values.problem || row.pm,
|
||||
type: (values.source || row.type) as typeof row.type,
|
||||
});
|
||||
}
|
||||
const payload = mapWorkOrderFormToUpdatePayload(id, values);
|
||||
const data = await apiPost<unknown>(API_PATHS.workOrder.editWorkorder, payload);
|
||||
return mapWorkOrderDetail(handleApiResponse(data));
|
||||
|
|
@ -142,50 +219,44 @@ export const workOrdersApi = {
|
|||
},
|
||||
|
||||
changeStatus: async (id: string | number, status: string): Promise<void> => {
|
||||
if (shouldUseMockWorkOrderDetail(id)) {
|
||||
await mockNetworkDelay();
|
||||
return;
|
||||
}
|
||||
await apiPost<unknown>(API_PATHS.workOrder.changeStatus, undefined, {
|
||||
searchParams: { id, status },
|
||||
});
|
||||
},
|
||||
|
||||
changeAssignment: async (id: string | number, assignTo: string | number): Promise<void> => {
|
||||
if (shouldUseMockWorkOrderDetail(id)) {
|
||||
await mockNetworkDelay();
|
||||
return;
|
||||
}
|
||||
await apiPost<unknown>(API_PATHS.workOrder.changeAssignment, undefined, {
|
||||
searchParams: { id, assignTo: assignTo || "" },
|
||||
});
|
||||
},
|
||||
|
||||
fetchLocations: async (): Promise<LocationOption[]> => {
|
||||
if (isWoTableMockEnabled()) {
|
||||
return getMockWorkOrderLocations();
|
||||
}
|
||||
const data = await apiGet<unknown>(API_PATHS.contact.getLocations);
|
||||
const items = handleApiResponse<unknown[]>(data);
|
||||
return Array.isArray(items) ? mapLocationOptions(items) : [];
|
||||
},
|
||||
|
||||
fetchEmployeesDropdown: async (): Promise<EmployeeOption[]> => {
|
||||
try {
|
||||
const data = await apiGet<unknown>(API_PATHS.employee.dropdown);
|
||||
const items = handleApiResponse<unknown[]>(data);
|
||||
return Array.isArray(items) ? mapEmployeeOptions(items) : [];
|
||||
} catch {
|
||||
const data = await apiGet<unknown>(API_PATHS.employee.getList, {
|
||||
searchParams: { page: 1, pageSize: 1000 },
|
||||
});
|
||||
const paginated = handlePaginatedResponse<unknown>(data);
|
||||
const items = paginated.items.map((item) => {
|
||||
const record = (typeof item === "object" && item !== null ? item : {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const firstName = String(record.firstName ?? record.FirstName ?? "");
|
||||
const lastName = String(record.lastName ?? record.LastName ?? "");
|
||||
return {
|
||||
id: record.id ?? record.Id,
|
||||
name: `${firstName} ${lastName}`.trim(),
|
||||
};
|
||||
});
|
||||
return mapEmployeeOptions(items);
|
||||
}
|
||||
const data = await apiGet<unknown>(API_PATHS.employee.dropdown);
|
||||
const items = handleApiResponse<unknown[]>(data);
|
||||
return Array.isArray(items) ? mapEmployeeOptions(items) : [];
|
||||
},
|
||||
|
||||
fetchVendorsDropdown: async (trade?: string, siteZip?: string): Promise<VendorDropdownItem[]> => {
|
||||
if (isWoTableMockEnabled()) {
|
||||
return getMockWorkOrderVendors();
|
||||
}
|
||||
const searchParams: Record<string, string> = {};
|
||||
if (trade) {
|
||||
searchParams.trade = trade;
|
||||
|
|
|
|||
122
src/domain/work-orders/mappers/wizard-draft-mapper.ts
Normal file
122
src/domain/work-orders/mappers/wizard-draft-mapper.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import type { WorkOrderFormValues } from "@/domain/work-orders/types/work-order-detail";
|
||||
import type {
|
||||
WOType,
|
||||
WizardWOStatus,
|
||||
WorkOrderWizardDraft,
|
||||
} from "@/domain/work-orders/types/work-order-wizard";
|
||||
import type { WorkOrderStatus } from "@/domain/work-orders/types/work-order-common";
|
||||
import { formatApptRange, todayIso } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
|
||||
const WIZARD_TO_API_STATUS: Record<WizardWOStatus, WorkOrderStatus> = {
|
||||
Incomplete: "Open",
|
||||
Pending: "Open",
|
||||
Scheduled: "Open",
|
||||
"En Route": "In Progress",
|
||||
"On Site": "In Progress",
|
||||
"In Progress": "In Progress",
|
||||
Completed: "Done",
|
||||
Rescheduled: "On Hold",
|
||||
"Pending Quote": "On Hold",
|
||||
};
|
||||
|
||||
const API_TO_WIZARD_STATUS: Partial<Record<WorkOrderStatus, WizardWOStatus>> = {
|
||||
Open: "Incomplete",
|
||||
"In Progress": "In Progress",
|
||||
"On Hold": "Pending",
|
||||
Done: "Completed",
|
||||
};
|
||||
|
||||
const TYPE_TO_PRIORITY: Record<WOType, string> = {
|
||||
Emergency: "Sev 1",
|
||||
Reactive: "Sev 2",
|
||||
Overdue: "Sev 2",
|
||||
"Add-On": "Sev 3",
|
||||
PM: "Sev 3",
|
||||
};
|
||||
|
||||
export function mapWizardStatusToApi(status: WizardWOStatus | ""): WorkOrderStatus {
|
||||
if (!status) {
|
||||
return "Open";
|
||||
}
|
||||
return WIZARD_TO_API_STATUS[status] ?? "Open";
|
||||
}
|
||||
|
||||
export function mapApiStatusToWizard(status: string): WizardWOStatus | "" {
|
||||
if (!status) {
|
||||
return "";
|
||||
}
|
||||
if (API_TO_WIZARD_STATUS[status as WorkOrderStatus]) {
|
||||
return API_TO_WIZARD_STATUS[status as WorkOrderStatus]!;
|
||||
}
|
||||
const wizardStatuses = Object.keys(WIZARD_TO_API_STATUS) as WizardWOStatus[];
|
||||
if (wizardStatuses.includes(status as WizardWOStatus)) {
|
||||
return status as WizardWOStatus;
|
||||
}
|
||||
if (status === "Canceled" || status === "Cancelled") {
|
||||
return "Canceled" as WizardWOStatus;
|
||||
}
|
||||
return "Incomplete";
|
||||
}
|
||||
|
||||
export function mapTypeToPriority(type: WOType | ""): string {
|
||||
if (!type) {
|
||||
return "Sev 3";
|
||||
}
|
||||
return TYPE_TO_PRIORITY[type];
|
||||
}
|
||||
|
||||
function buildTitle(draft: WorkOrderWizardDraft): string {
|
||||
if (draft.woNumber) {
|
||||
return `${draft.type || "Work Order"} — #${draft.woNumber}`;
|
||||
}
|
||||
if (draft.locationName) {
|
||||
return `${draft.type || "Work Order"} — ${draft.locationName}`;
|
||||
}
|
||||
return draft.type || "Work Order";
|
||||
}
|
||||
|
||||
function buildDescription(draft: WorkOrderWizardDraft): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (draft.pocNotes) {
|
||||
parts.push(`POC notes: ${draft.pocNotes}`);
|
||||
}
|
||||
if (draft.pocName || draft.pocPhone) {
|
||||
parts.push(`POC: ${[draft.pocName, draft.pocPhone].filter(Boolean).join(" · ")}`);
|
||||
}
|
||||
if (draft.apptStart) {
|
||||
parts.push(`Appt: ${formatApptRange(draft.apptStart, draft.apptEnd)}`);
|
||||
}
|
||||
if (draft.vendorNotes) {
|
||||
parts.push(`Vendor notes: ${draft.vendorNotes}`);
|
||||
}
|
||||
if (draft.vendorName) {
|
||||
parts.push(`Vendor: ${draft.vendorName}`);
|
||||
}
|
||||
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
export function mapWizardDraftToFormValues(draft: WorkOrderWizardDraft): WorkOrderFormValues {
|
||||
return {
|
||||
internalWONumber: "",
|
||||
workOrderNumber: draft.woNumber,
|
||||
title: buildTitle(draft),
|
||||
locationId: draft.locationId,
|
||||
priority: mapTypeToPriority(draft.type),
|
||||
status: mapWizardStatusToApi(draft.status),
|
||||
createdDate: todayIso(),
|
||||
dueDate: draft.dueDate,
|
||||
scheduledDate: draft.scheduleWeekOnly ? "" : draft.scheduledOn,
|
||||
completedDate: "",
|
||||
assignedTo: draft.assigneeName,
|
||||
description: buildDescription(draft),
|
||||
po: "",
|
||||
tt: "",
|
||||
problem: draft.pm,
|
||||
trade: "",
|
||||
subTrade: "",
|
||||
vendorNTE: "",
|
||||
source: draft.type,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { mapApiStatusToWizard } from "@/domain/work-orders/mappers/wizard-draft-mapper";
|
||||
import type {
|
||||
DispatchChecklistItem,
|
||||
DispatchComment,
|
||||
|
|
@ -9,7 +10,6 @@ import type {
|
|||
LocationOption,
|
||||
TaskListTemplate,
|
||||
UpdateDispatchPayload,
|
||||
UpliftRequest,
|
||||
VendorDropdownItem,
|
||||
WorkOrderAuditEntry,
|
||||
WorkOrderComment,
|
||||
|
|
@ -18,12 +18,29 @@ import type {
|
|||
WorkOrderFormValues,
|
||||
WorkOrderListItem,
|
||||
} from "@/domain/work-orders/types/work-order";
|
||||
import type {
|
||||
CompletionDocStatus,
|
||||
WorkOrderTableRow,
|
||||
} from "@/domain/work-orders/types/work-order-table-row";
|
||||
import type { WOType } from "@/domain/work-orders/types/work-order-wizard";
|
||||
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
|
||||
import { formatApptRange } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
import type { UpliftRequest } from "@/domain/uplifts/types/uplift";
|
||||
import type { WorkOrderStatus } from "@/domain/work-orders/types/work-order-common";
|
||||
import { WORK_ORDER_STATUS_OPTIONS } from "@/domain/work-orders/types/work-order-common";
|
||||
import { toDateInputValue } from "@/lib/time-utils";
|
||||
|
||||
function asRecord(raw: unknown): Record<string, unknown> {
|
||||
return typeof raw === "object" && raw !== null ? (raw as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
function toWorkOrderStatus(value: string): WorkOrderStatus {
|
||||
if (WORK_ORDER_STATUS_OPTIONS.includes(value as WorkOrderStatus)) {
|
||||
return value as WorkOrderStatus;
|
||||
}
|
||||
return "Open";
|
||||
}
|
||||
|
||||
function readString(record: Record<string, unknown>, ...keys: string[]): string {
|
||||
for (const key of keys) {
|
||||
const value = record[key];
|
||||
|
|
@ -92,6 +109,114 @@ export function mapWorkOrderListItems(rawItems: unknown[]): WorkOrderListItem[]
|
|||
return rawItems.map(mapWorkOrderListItem);
|
||||
}
|
||||
|
||||
const WO_TYPES_SET = new Set(["PM", "Reactive", "Emergency", "Add-On", "Overdue"]);
|
||||
|
||||
function readWoType(record: Record<string, unknown>): WOType | "" {
|
||||
const raw = readString(record, "type", "Type", "source", "Source", "woType", "WoType");
|
||||
if (WO_TYPES_SET.has(raw)) {
|
||||
return raw as WOType;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function readDocStatus(record: Record<string, unknown>): CompletionDocStatus {
|
||||
const raw = readString(record, "docStatus", "DocStatus", "completionDoc", "CompletionDoc");
|
||||
if (raw === "Yes" || raw === "No" || raw === "NN") {
|
||||
return raw;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function mapWorkOrderTableRow(raw: unknown): WorkOrderTableRow {
|
||||
const item = asRecord(raw);
|
||||
const scheduledOn = toDateInputValue(
|
||||
readString(item, "scheduledOn", "scheduledDate", "ScheduledDate", "ScheduledOn"),
|
||||
);
|
||||
const { dayGroup, dayLabel } = deriveDayFields(scheduledOn);
|
||||
const woNumber = readString(
|
||||
item,
|
||||
"woNumber",
|
||||
"internalWONumber",
|
||||
"InternalWONumber",
|
||||
"number",
|
||||
"workOrderNumber",
|
||||
"WorkerOrderNumber",
|
||||
);
|
||||
const site = readString(item, "site", "Site", "locationCode", "LocationCode");
|
||||
const location = readString(item, "location", "Location", "locationName", "LocationName");
|
||||
const dispatcherId = readString(
|
||||
item,
|
||||
"dispatcherId",
|
||||
"DispatcherId",
|
||||
"assigneeId",
|
||||
"AssigneeId",
|
||||
"employeeId",
|
||||
"EmployeeId",
|
||||
);
|
||||
const dispatcherName = readString(
|
||||
item,
|
||||
"dispatcherName",
|
||||
"DispatcherName",
|
||||
"assignedTo",
|
||||
"AssignedTo",
|
||||
"employeeName",
|
||||
"EmployeeName",
|
||||
"assignee",
|
||||
"Assignee",
|
||||
);
|
||||
const apptStart = readString(item, "apptStart", "ApptStart", "appointmentStart");
|
||||
const apptEnd = readString(item, "apptEnd", "ApptEnd", "appointmentEnd");
|
||||
const apptTime =
|
||||
readString(item, "apptTime", "ApptTime") ||
|
||||
(apptStart ? formatApptRange(apptStart, apptEnd) : "");
|
||||
const apiStatus = readString(item, "status", "Status");
|
||||
const locationIdRaw = item.locationId ?? item.LocationId;
|
||||
|
||||
const row: WorkOrderTableRow = {
|
||||
id: readId(item),
|
||||
woNumber,
|
||||
site: site || location.slice(0, 8),
|
||||
type: readWoType(item),
|
||||
dispatcherId,
|
||||
dispatcherName,
|
||||
scheduledOn,
|
||||
targetWeek: readString(item, "targetWeek", "TargetWeek"),
|
||||
dayGroup,
|
||||
dayLabel,
|
||||
location,
|
||||
locationId:
|
||||
typeof locationIdRaw === "string" || typeof locationIdRaw === "number" ? locationIdRaw : "",
|
||||
pm: readString(item, "pm", "PM", "problem", "Problem", "service", "Service"),
|
||||
company: readString(item, "company", "Company", "vendorName", "VendorName"),
|
||||
tech: readString(item, "tech", "Tech", "technician", "Technician"),
|
||||
techPhone: readString(item, "techPhone", "TechPhone"),
|
||||
apptStart,
|
||||
apptEnd,
|
||||
apptTime,
|
||||
status: mapApiStatusToWizard(apiStatus),
|
||||
docStatus: readDocStatus(item),
|
||||
pocName: readString(item, "pocName", "PocName", "POCName"),
|
||||
pocPhone: readString(item, "pocPhone", "PocPhone", "POCPhone"),
|
||||
pocNotes: readString(item, "pocNotes", "PocNotes", "POCNotes"),
|
||||
dueDate: toDateInputValue(readString(item, "dueDate", "DueDate")),
|
||||
rescheduleCount: readNumber(item, "rescheduleCount", "RescheduleCount") ?? 0,
|
||||
carriedOver: readNumber(item, "carriedOver", "CarriedOver") ?? 0,
|
||||
originalWeek: readString(item, "originalWeek", "OriginalWeek"),
|
||||
originalDate: toDateInputValue(readString(item, "originalDate", "OriginalDate")),
|
||||
isPastDue: readBool(item, "isPastDue", "IsPastDue"),
|
||||
scheduleWeekOnly: readBool(item, "scheduleWeekOnly", "ScheduleWeekOnly"),
|
||||
mediaCount: readNumber(item, "mediaCount", "MediaCount") ?? 0,
|
||||
title: readString(item, "title", "Title", "workerOrderTitle", "WorkerOrderTitle"),
|
||||
priority: readString(item, "priority", "Priority"),
|
||||
};
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
export function mapWorkOrderTableRows(rawItems: unknown[]): WorkOrderTableRow[] {
|
||||
return rawItems.map(mapWorkOrderTableRow);
|
||||
}
|
||||
|
||||
export function mapWorkOrderComment(raw: unknown): WorkOrderComment {
|
||||
const item = asRecord(raw);
|
||||
|
||||
|
|
@ -187,7 +312,7 @@ export function mapWorkOrderDetailToFormValues(detail: WorkOrderDetail): WorkOrd
|
|||
title: detail.title,
|
||||
locationId: detail.locationId,
|
||||
priority: detail.priority,
|
||||
status: detail.status,
|
||||
status: toWorkOrderStatus(detail.status),
|
||||
createdDate: detail.createdDate,
|
||||
dueDate: detail.dueDate,
|
||||
scheduledDate: detail.scheduledDate,
|
||||
|
|
|
|||
42
src/domain/work-orders/types/completion-doc.ts
Normal file
42
src/domain/work-orders/types/completion-doc.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import type { ElementType } from "react";
|
||||
import type { CompletionDocStatus } from "@/domain/work-orders/types/work-order-table-row";
|
||||
|
||||
export interface CompDocData {
|
||||
notes: string;
|
||||
generatedAt?: string;
|
||||
}
|
||||
|
||||
export interface DocFields {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zip: string;
|
||||
dateCompleted: string;
|
||||
amazonRep: string;
|
||||
seaRep: string;
|
||||
}
|
||||
|
||||
export type DocState = "draft" | "generated" | "not-required";
|
||||
|
||||
export interface CompletionDocStateConfig {
|
||||
pillLabel: string;
|
||||
pillBg: string;
|
||||
pillText: string;
|
||||
cardBg: string;
|
||||
cardBorder: string;
|
||||
cardIconBg: string;
|
||||
iconColor: string;
|
||||
cardTitle: string;
|
||||
icon: ElementType;
|
||||
}
|
||||
|
||||
export interface CompDocSiteInfo {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zip: string;
|
||||
label: string;
|
||||
fullAddress: string;
|
||||
}
|
||||
|
||||
export type CompDocSaveStatus = Exclude<CompletionDocStatus, "">;
|
||||
80
src/domain/work-orders/types/dispatch.ts
Normal file
80
src/domain/work-orders/types/dispatch.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
export interface WorkOrderDispatchSummary {
|
||||
id: string | number;
|
||||
dispatchNumber: string;
|
||||
poNumber: string;
|
||||
vendorName: string;
|
||||
nteAmount: number | null;
|
||||
status: string;
|
||||
dispatchedAt: string;
|
||||
}
|
||||
|
||||
export interface DispatchChecklistItem {
|
||||
id: string | number;
|
||||
itemText: string;
|
||||
isCompleted: boolean;
|
||||
completedBy: string | null;
|
||||
completedAt: string | null;
|
||||
}
|
||||
|
||||
export interface DispatchSignoff {
|
||||
id: string | number;
|
||||
signoffType: string;
|
||||
name: string;
|
||||
signature: string;
|
||||
signatureMethod: string;
|
||||
signedAt: string;
|
||||
}
|
||||
|
||||
export interface DispatchComment {
|
||||
id: string | number;
|
||||
createdDate: string;
|
||||
commenttext: string;
|
||||
userName: string;
|
||||
commenter: string | null;
|
||||
}
|
||||
|
||||
export interface DispatchWorkOrderRef {
|
||||
id: string | number;
|
||||
woNumber: string;
|
||||
}
|
||||
|
||||
export interface DispatchDetail {
|
||||
id: string | number;
|
||||
dispatchNumber: string;
|
||||
poNumber: string;
|
||||
vendorName: string;
|
||||
vendorEmail: string;
|
||||
woNumber: string;
|
||||
status: string;
|
||||
nteAmount: number | null;
|
||||
scheduledDate: string;
|
||||
completedDate: string;
|
||||
description: string;
|
||||
dispatchedAt: string;
|
||||
acknowledgedAt: string;
|
||||
verifiedAt: string;
|
||||
verifiedBy: string;
|
||||
checklistItems: DispatchChecklistItem[];
|
||||
signoffs: DispatchSignoff[];
|
||||
comments: DispatchComment[];
|
||||
workOrders: DispatchWorkOrderRef[];
|
||||
}
|
||||
|
||||
export interface DispatchToVendorPayload {
|
||||
workOrderIds: (string | number)[];
|
||||
vendorIds: (string | number)[];
|
||||
nteAmount: number | null;
|
||||
scheduledDate: string | null;
|
||||
description: string;
|
||||
taskListTemplateId: number | null;
|
||||
customChecklistItems: string[] | null;
|
||||
}
|
||||
|
||||
export interface UpdateDispatchPayload {
|
||||
id: string | number;
|
||||
status: string;
|
||||
nteAmount: number | null;
|
||||
scheduledDate: string | null;
|
||||
completedDate: string | null;
|
||||
description: string;
|
||||
}
|
||||
74
src/domain/work-orders/types/work-order-advanced-filters.ts
Normal file
74
src/domain/work-orders/types/work-order-advanced-filters.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { ASSIGNEE_FILTER_UNASSIGNED } from "./work-order-list";
|
||||
import {
|
||||
ALL_WIZARD_STATUSES,
|
||||
WO_TYPE_META,
|
||||
WO_TYPES,
|
||||
type WizardWOStatus,
|
||||
type WOType,
|
||||
} from "./work-order-wizard";
|
||||
|
||||
export { ASSIGNEE_FILTER_UNASSIGNED };
|
||||
|
||||
export type DateRangeKey =
|
||||
| "this-week"
|
||||
| "last-week"
|
||||
| "this-month"
|
||||
| "last-3-months"
|
||||
| "next-week"
|
||||
| "next-month"
|
||||
| "custom";
|
||||
|
||||
export type CompletionDocFilter = "No" | "Yes" | "NN";
|
||||
|
||||
export interface WorkOrderAdvancedFilters {
|
||||
dateRange: DateRangeKey;
|
||||
customFrom: string;
|
||||
customTo: string;
|
||||
sites: Set<string>;
|
||||
types: Set<WOType>;
|
||||
dispatchers: Set<string>;
|
||||
statuses: Set<WizardWOStatus>;
|
||||
pmTypes: Set<string>;
|
||||
vendorTechs: Set<string>;
|
||||
docs: Set<CompletionDocFilter>;
|
||||
}
|
||||
|
||||
export const EMPTY_ADVANCED_FILTERS: WorkOrderAdvancedFilters = {
|
||||
dateRange: "this-week",
|
||||
customFrom: "",
|
||||
customTo: "",
|
||||
sites: new Set(),
|
||||
types: new Set(),
|
||||
dispatchers: new Set(),
|
||||
statuses: new Set(),
|
||||
pmTypes: new Set(),
|
||||
vendorTechs: new Set(),
|
||||
docs: new Set(),
|
||||
};
|
||||
|
||||
export const DATE_RANGE_OPTIONS: { key: DateRangeKey; label: string }[] = [
|
||||
{ key: "this-week", label: "This week" },
|
||||
{ key: "last-week", label: "Last week" },
|
||||
{ key: "this-month", label: "This month" },
|
||||
{ key: "last-3-months", label: "Last 3 months" },
|
||||
{ key: "next-week", label: "Next week" },
|
||||
{ key: "next-month", label: "Next month" },
|
||||
{ key: "custom", label: "Custom" },
|
||||
];
|
||||
|
||||
export const DOC_FILTER_LABELS: Record<CompletionDocFilter, string> = {
|
||||
No: "Pending",
|
||||
Yes: "Uploaded",
|
||||
NN: "N/N",
|
||||
};
|
||||
|
||||
export const COMPLETION_DOC_OPTIONS: CompletionDocFilter[] = ["No", "Yes", "NN"];
|
||||
|
||||
export const WO_TYPE_DESCRIPTIONS: Record<WOType, string> = Object.fromEntries(
|
||||
WO_TYPES.map((type) => [type, WO_TYPE_META[type].desc]),
|
||||
) as Record<WOType, string>;
|
||||
|
||||
export { ALL_WIZARD_STATUSES, WO_TYPES };
|
||||
|
||||
export type AdvancedFilterSetKey =
|
||||
"sites" | "types" | "dispatchers" | "statuses" | "pmTypes" | "vendorTechs" | "docs";
|
||||
48
src/domain/work-orders/types/work-order-common.ts
Normal file
48
src/domain/work-orders/types/work-order-common.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
export const WORK_ORDER_STATUS_OPTIONS = [
|
||||
"Open",
|
||||
"In Progress",
|
||||
"On Hold",
|
||||
"Done",
|
||||
"Cancelled",
|
||||
] as const;
|
||||
|
||||
export const WORK_ORDER_PRIORITY_OPTIONS = ["Sev 1", "Sev 2", "Sev 3", "Sev 4", "Sev 5"] as const;
|
||||
|
||||
export const DISPATCH_STATUS_OPTIONS = [
|
||||
"Sent",
|
||||
"Acknowledged",
|
||||
"In Progress",
|
||||
"Completed",
|
||||
"Verified",
|
||||
"Cancelled",
|
||||
] as const;
|
||||
|
||||
export type WorkOrderStatus = (typeof WORK_ORDER_STATUS_OPTIONS)[number];
|
||||
export type WorkOrderPriority = (typeof WORK_ORDER_PRIORITY_OPTIONS)[number];
|
||||
export type DispatchStatus = (typeof DISPATCH_STATUS_OPTIONS)[number];
|
||||
|
||||
export type WorkOrderCommentType = "customer" | "internal";
|
||||
|
||||
export interface LocationOption {
|
||||
id: string | number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface EmployeeOption {
|
||||
id: string | number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface VendorDropdownItem {
|
||||
id: string | number;
|
||||
companyName: string;
|
||||
tradeSpecialties: string;
|
||||
address: string;
|
||||
distanceMiles: number | null;
|
||||
}
|
||||
|
||||
export interface TaskListTemplate {
|
||||
id: string | number;
|
||||
name: string;
|
||||
items: { id?: string | number; itemText?: string }[];
|
||||
}
|
||||
97
src/domain/work-orders/types/work-order-detail.ts
Normal file
97
src/domain/work-orders/types/work-order-detail.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import type { WorkOrderCommentType, WorkOrderPriority, WorkOrderStatus } from "./work-order-common";
|
||||
import type { WorkOrderDispatchSummary } from "./dispatch";
|
||||
|
||||
export interface WorkOrderComment {
|
||||
id: string | number;
|
||||
createdDate: string;
|
||||
commenttext: string;
|
||||
commentType: WorkOrderCommentType | string;
|
||||
userName: string;
|
||||
commenter: string | null;
|
||||
recordType: string | null;
|
||||
}
|
||||
|
||||
export interface WorkOrderAuditEntry {
|
||||
id: string | number;
|
||||
fieldName: string;
|
||||
oldValue: string;
|
||||
newValue: string;
|
||||
action: string;
|
||||
createdAt: string;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export interface WorkOrderDetail {
|
||||
id: string | number;
|
||||
internalWONumber: string;
|
||||
workOrderNumber: string;
|
||||
title: string;
|
||||
location: string;
|
||||
locationId: string;
|
||||
locationZip: string;
|
||||
assignedTo: string;
|
||||
dueDate: string;
|
||||
createdDate: string;
|
||||
createdDateFull: string;
|
||||
description: string;
|
||||
priority: WorkOrderPriority | string;
|
||||
status: WorkOrderStatus | string;
|
||||
po: string;
|
||||
tt: string;
|
||||
problem: string;
|
||||
trade: string;
|
||||
subTrade: string;
|
||||
vendorNTE: string;
|
||||
scheduledDate: string;
|
||||
completedDate: string;
|
||||
source: string;
|
||||
comments: WorkOrderComment[];
|
||||
auditLog: WorkOrderAuditEntry[];
|
||||
dispatches: WorkOrderDispatchSummary[];
|
||||
}
|
||||
|
||||
export interface WorkOrderFormValues {
|
||||
internalWONumber: string;
|
||||
workOrderNumber: string;
|
||||
title: string;
|
||||
locationId: string;
|
||||
priority: string;
|
||||
status: WorkOrderStatus;
|
||||
createdDate: string;
|
||||
dueDate: string;
|
||||
scheduledDate: string;
|
||||
completedDate: string;
|
||||
assignedTo: string;
|
||||
description: string;
|
||||
po: string;
|
||||
tt: string;
|
||||
problem: string;
|
||||
trade: string;
|
||||
subTrade: string;
|
||||
vendorNTE: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export function createDefaultWorkOrderFormValues(): WorkOrderFormValues {
|
||||
return {
|
||||
internalWONumber: "",
|
||||
workOrderNumber: "",
|
||||
title: "",
|
||||
locationId: "",
|
||||
priority: "",
|
||||
status: "Open",
|
||||
createdDate: new Date().toISOString().split("T")[0] ?? "",
|
||||
dueDate: "",
|
||||
scheduledDate: "",
|
||||
completedDate: "",
|
||||
assignedTo: "",
|
||||
description: "",
|
||||
po: "",
|
||||
tt: "",
|
||||
problem: "",
|
||||
trade: "",
|
||||
subTrade: "",
|
||||
vendorNTE: "",
|
||||
source: "",
|
||||
};
|
||||
}
|
||||
9
src/domain/work-orders/types/work-order-list-filters.ts
Normal file
9
src/domain/work-orders/types/work-order-list-filters.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type { WOType } from "./work-order-wizard";
|
||||
|
||||
export type WorkOrderTypeFilter = WOType | "All";
|
||||
|
||||
export interface WorkOrderBarFilters {
|
||||
weekMonday: string;
|
||||
dispatcherIds: Set<string>;
|
||||
type: WorkOrderTypeFilter;
|
||||
}
|
||||
61
src/domain/work-orders/types/work-order-list.ts
Normal file
61
src/domain/work-orders/types/work-order-list.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import type { WorkOrderPriority, WorkOrderStatus } from "./work-order-common";
|
||||
import type { WorkOrderTableRow } from "./work-order-table-row";
|
||||
|
||||
export type SortDirection = "asc" | "desc" | "";
|
||||
|
||||
export type WorkOrdersSortableColumn = "number" | "title";
|
||||
|
||||
export const ASSIGNEE_FILTER_UNASSIGNED = "__unassigned" as const;
|
||||
|
||||
export interface WorkOrderListItem {
|
||||
id: string | number;
|
||||
number: string;
|
||||
title: string;
|
||||
location: string;
|
||||
priority: WorkOrderPriority | string;
|
||||
status: WorkOrderStatus | string;
|
||||
createdDate: string;
|
||||
dueDate: string;
|
||||
assignedTo: string;
|
||||
lastUpdated: string;
|
||||
}
|
||||
|
||||
export interface WorkOrdersListParams {
|
||||
search?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sortBy?: WorkOrdersSortableColumn | string;
|
||||
sortDir?: SortDirection;
|
||||
status?: string;
|
||||
assignee?: string;
|
||||
locationId?: string | number;
|
||||
/** Phase 2: filter bar week range (Monday ISO). */
|
||||
dateFrom?: string;
|
||||
/** Phase 2: filter bar week range (Friday ISO). */
|
||||
dateTo?: string;
|
||||
/** Phase 2: filter bar WO type. */
|
||||
type?: string;
|
||||
/** Phase 2: filter bar dispatcher ids (format TBD by API contract). */
|
||||
assigneeIds?: string;
|
||||
}
|
||||
|
||||
export interface WorkOrderUser {
|
||||
id: string | number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface WorkOrdersListResult {
|
||||
items: WorkOrderTableRow[];
|
||||
totalCount: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface SelectedWorkOrderRef {
|
||||
id: string | number;
|
||||
number: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/** Max page size when loading WOs at a location for dispatch multi-select. */
|
||||
export const DISPATCH_LOCATION_WO_PAGE_SIZE = 50;
|
||||
24
src/domain/work-orders/types/work-order-media.ts
Normal file
24
src/domain/work-orders/types/work-order-media.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export type FileCategory = "image" | "video" | "pdf" | "doc" | "other";
|
||||
|
||||
export type MediaCategory =
|
||||
"Photo" | "Video" | "Receipt" | "Quote" | "Invoice" | "Report" | "Other";
|
||||
|
||||
export interface MediaFile {
|
||||
id: string;
|
||||
name: string;
|
||||
fileType: FileCategory;
|
||||
url: string;
|
||||
category: MediaCategory;
|
||||
}
|
||||
|
||||
export const MEDIA_CATEGORIES: MediaCategory[] = [
|
||||
"Photo",
|
||||
"Video",
|
||||
"Receipt",
|
||||
"Quote",
|
||||
"Invoice",
|
||||
"Report",
|
||||
"Other",
|
||||
];
|
||||
|
||||
export const EMPTY_MEDIA: MediaFile[] = [];
|
||||
99
src/domain/work-orders/types/work-order-table-row.ts
Normal file
99
src/domain/work-orders/types/work-order-table-row.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import type { WizardWOStatus, WOType } from "./work-order-wizard";
|
||||
|
||||
export type CompletionDocStatus = "Yes" | "No" | "NN" | "";
|
||||
|
||||
export const WORK_ORDERS_TABLE_PAGE_SIZE = 500;
|
||||
|
||||
export interface WorkOrderTableRow {
|
||||
id: string | number;
|
||||
woNumber: string;
|
||||
site: string;
|
||||
type: WOType | "";
|
||||
dispatcherId: string;
|
||||
dispatcherName: string;
|
||||
scheduledOn: string;
|
||||
targetWeek: string;
|
||||
dayGroup: string;
|
||||
dayLabel: string;
|
||||
location: string;
|
||||
locationId: string | number;
|
||||
pm: string;
|
||||
company: string;
|
||||
tech: string;
|
||||
techPhone: string;
|
||||
apptStart: string;
|
||||
apptEnd: string;
|
||||
apptTime: string;
|
||||
status: WizardWOStatus | "";
|
||||
docStatus: CompletionDocStatus;
|
||||
pocName: string;
|
||||
pocPhone: string;
|
||||
pocNotes: string;
|
||||
dueDate: string;
|
||||
rescheduleCount: number;
|
||||
carriedOver: number;
|
||||
originalWeek: string;
|
||||
originalDate: string;
|
||||
isPastDue: boolean;
|
||||
scheduleWeekOnly: boolean;
|
||||
mediaCount: number;
|
||||
title: string;
|
||||
priority: string;
|
||||
}
|
||||
|
||||
export interface WorkOrderTableDraft {
|
||||
site: string;
|
||||
woNumber: string;
|
||||
type: WOType | "";
|
||||
dispatcherId: string;
|
||||
scheduledOn: string;
|
||||
schedNotes: string;
|
||||
dueDate: string;
|
||||
location: string;
|
||||
locationId: string | number;
|
||||
pm: string;
|
||||
company: string;
|
||||
tech: string;
|
||||
techPhone: string;
|
||||
apptStart: string;
|
||||
apptEnd: string;
|
||||
status: WizardWOStatus | "";
|
||||
pocName: string;
|
||||
pocPhone: string;
|
||||
pocNotes: string;
|
||||
scheduleWeekOnly: boolean;
|
||||
targetWeek: string;
|
||||
}
|
||||
|
||||
export const EMPTY_TABLE_DRAFT: WorkOrderTableDraft = {
|
||||
site: "",
|
||||
woNumber: "",
|
||||
type: "",
|
||||
dispatcherId: "",
|
||||
scheduledOn: "",
|
||||
schedNotes: "",
|
||||
dueDate: "",
|
||||
location: "",
|
||||
locationId: "",
|
||||
pm: "",
|
||||
company: "",
|
||||
tech: "",
|
||||
techPhone: "",
|
||||
apptStart: "",
|
||||
apptEnd: "",
|
||||
status: "Incomplete",
|
||||
pocName: "",
|
||||
pocPhone: "",
|
||||
pocNotes: "",
|
||||
scheduleWeekOnly: false,
|
||||
targetWeek: "",
|
||||
};
|
||||
|
||||
export interface WorkOrderDayGroup {
|
||||
key: string;
|
||||
label: string;
|
||||
date: string;
|
||||
items: WorkOrderTableRow[];
|
||||
}
|
||||
|
||||
export const UNSCHEDULED_DAY_KEY = "__unscheduled__" as const;
|
||||
149
src/domain/work-orders/types/work-order-wizard.ts
Normal file
149
src/domain/work-orders/types/work-order-wizard.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
export const WO_TYPES = ["PM", "Reactive", "Emergency", "Add-On", "Overdue"] as const;
|
||||
|
||||
export type WOType = (typeof WO_TYPES)[number];
|
||||
|
||||
export const ALL_WIZARD_STATUSES = [
|
||||
"Incomplete",
|
||||
"Pending",
|
||||
"Scheduled",
|
||||
"En Route",
|
||||
"On Site",
|
||||
"In Progress",
|
||||
"Completed",
|
||||
"Rescheduled",
|
||||
"Pending Quote",
|
||||
] as const;
|
||||
|
||||
export type WizardWOStatus = (typeof ALL_WIZARD_STATUSES)[number];
|
||||
|
||||
export interface WorkOrderWizardDraft {
|
||||
locationId: string;
|
||||
locationName: string;
|
||||
woNumber: string;
|
||||
type: WOType | "";
|
||||
assigneeId: string;
|
||||
assigneeName: string;
|
||||
scheduledOn: string;
|
||||
dueDate: string;
|
||||
pm: string;
|
||||
vendorId: string;
|
||||
vendorName: string;
|
||||
techPhone: string;
|
||||
vendorNotes: string;
|
||||
apptStart: string;
|
||||
apptEnd: string;
|
||||
status: WizardWOStatus | "";
|
||||
pocName: string;
|
||||
pocPhone: string;
|
||||
pocNotes: string;
|
||||
scheduleWeekOnly: boolean;
|
||||
}
|
||||
|
||||
export const EMPTY_WIZARD_DRAFT: WorkOrderWizardDraft = {
|
||||
locationId: "",
|
||||
locationName: "",
|
||||
woNumber: "",
|
||||
type: "",
|
||||
assigneeId: "",
|
||||
assigneeName: "",
|
||||
scheduledOn: "",
|
||||
dueDate: "",
|
||||
pm: "",
|
||||
vendorId: "",
|
||||
vendorName: "",
|
||||
techPhone: "",
|
||||
vendorNotes: "",
|
||||
apptStart: "",
|
||||
apptEnd: "",
|
||||
status: "Incomplete",
|
||||
pocName: "",
|
||||
pocPhone: "",
|
||||
pocNotes: "",
|
||||
scheduleWeekOnly: false,
|
||||
};
|
||||
|
||||
export const WIZARD_STEPS: { title: string; nav: string; subtitle: string }[] = [
|
||||
{
|
||||
title: "Type & schedule",
|
||||
nav: "What and when",
|
||||
subtitle: "Set the work-order type and when it should happen.",
|
||||
},
|
||||
{
|
||||
title: "Location & service",
|
||||
nav: "Where and what work",
|
||||
subtitle: "Pick the site and the service to be performed.",
|
||||
},
|
||||
{
|
||||
title: "Vendor & time",
|
||||
nav: "Who and the window",
|
||||
subtitle: "Assign a vendor crew and the appointment window.",
|
||||
},
|
||||
];
|
||||
|
||||
export const WO_TYPE_META: Record<WOType, { desc: string; bg: string; fg: string }> = {
|
||||
PM: {
|
||||
desc: "Scheduled preventive maintenance",
|
||||
bg: "var(--type-pm-bg)",
|
||||
fg: "var(--type-pm-text)",
|
||||
},
|
||||
Reactive: {
|
||||
desc: "Tenant-reported repair",
|
||||
bg: "var(--type-reactive-bg)",
|
||||
fg: "var(--type-reactive-text)",
|
||||
},
|
||||
Emergency: {
|
||||
desc: "Urgent same-day dispatch",
|
||||
bg: "var(--type-emergency-bg)",
|
||||
fg: "var(--type-emergency-text)",
|
||||
},
|
||||
"Add-On": {
|
||||
desc: "Extra scope on an existing visit",
|
||||
bg: "var(--type-addon-bg)",
|
||||
fg: "var(--type-addon-text)",
|
||||
},
|
||||
Overdue: {
|
||||
desc: "No client response — due date passed",
|
||||
bg: "var(--type-overdue-bg)",
|
||||
fg: "var(--type-overdue-text)",
|
||||
},
|
||||
};
|
||||
|
||||
export const WIZARD_STATUS_DOTS: Record<WizardWOStatus, string> = {
|
||||
Incomplete: "#9CA3AF",
|
||||
Pending: "#D97706",
|
||||
Scheduled: "#3B82F6",
|
||||
"En Route": "#0EA5E9",
|
||||
"On Site": "#10B981",
|
||||
"In Progress": "#2563EB",
|
||||
Completed: "#16A34A",
|
||||
Rescheduled: "#F59E0B",
|
||||
"Pending Quote": "#9333EA",
|
||||
};
|
||||
|
||||
export const TIME_SLOTS: string[] = (() => {
|
||||
const out: string[] = [];
|
||||
for (let m = 5 * 60; m <= 22 * 60; m += 30) {
|
||||
const h = Math.floor(m / 60);
|
||||
const mm = m % 60;
|
||||
out.push(`${String(h).padStart(2, "0")}:${String(mm).padStart(2, "0")}`);
|
||||
}
|
||||
return out;
|
||||
})();
|
||||
|
||||
export function canContinueWizardStep1(draft: WorkOrderWizardDraft): boolean {
|
||||
return Boolean(draft.type);
|
||||
}
|
||||
|
||||
export function canCreateWorkOrderFromWizard(draft: WorkOrderWizardDraft): boolean {
|
||||
return Boolean(draft.locationId);
|
||||
}
|
||||
|
||||
export function isWizardStepValid(step: number, draft: WorkOrderWizardDraft): boolean {
|
||||
if (step === 1) {
|
||||
return canContinueWizardStep1(draft);
|
||||
}
|
||||
if (step === 2) {
|
||||
return Boolean(draft.locationId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,270 +1,61 @@
|
|||
export interface WorkOrderListItem {
|
||||
id: string | number;
|
||||
number: string;
|
||||
title: string;
|
||||
location: string;
|
||||
priority: string;
|
||||
status: string;
|
||||
createdDate: string;
|
||||
dueDate: string;
|
||||
assignedTo: string;
|
||||
lastUpdated: string;
|
||||
}
|
||||
export type {
|
||||
DispatchChecklistItem,
|
||||
DispatchComment,
|
||||
DispatchDetail,
|
||||
DispatchSignoff,
|
||||
DispatchToVendorPayload,
|
||||
DispatchWorkOrderRef,
|
||||
UpdateDispatchPayload,
|
||||
WorkOrderDispatchSummary,
|
||||
} from "./dispatch";
|
||||
|
||||
export interface WorkOrdersListParams {
|
||||
search?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sortBy?: string;
|
||||
sortDir?: string;
|
||||
status?: string;
|
||||
assignee?: string;
|
||||
locationId?: string | number;
|
||||
}
|
||||
export type {
|
||||
DispatchStatus,
|
||||
EmployeeOption,
|
||||
LocationOption,
|
||||
TaskListTemplate,
|
||||
VendorDropdownItem,
|
||||
WorkOrderCommentType,
|
||||
WorkOrderPriority,
|
||||
WorkOrderStatus,
|
||||
} from "./work-order-common";
|
||||
|
||||
export interface WorkOrderUser {
|
||||
id: string | number;
|
||||
name: string;
|
||||
}
|
||||
export {
|
||||
DISPATCH_STATUS_OPTIONS,
|
||||
WORK_ORDER_PRIORITY_OPTIONS,
|
||||
WORK_ORDER_STATUS_OPTIONS,
|
||||
} from "./work-order-common";
|
||||
|
||||
export interface WorkOrdersListResult {
|
||||
items: WorkOrderListItem[];
|
||||
totalCount: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
export type {
|
||||
WorkOrderAuditEntry,
|
||||
WorkOrderComment,
|
||||
WorkOrderDetail,
|
||||
WorkOrderFormValues,
|
||||
} from "./work-order-detail";
|
||||
|
||||
export interface WorkOrderComment {
|
||||
id: string | number;
|
||||
createdDate: string;
|
||||
commenttext: string;
|
||||
commentType: string;
|
||||
userName: string;
|
||||
commenter: string | null;
|
||||
recordType: string | null;
|
||||
}
|
||||
export { createDefaultWorkOrderFormValues } from "./work-order-detail";
|
||||
|
||||
export interface WorkOrderAuditEntry {
|
||||
id: string | number;
|
||||
fieldName: string;
|
||||
oldValue: string;
|
||||
newValue: string;
|
||||
action: string;
|
||||
createdAt: string;
|
||||
userName: string;
|
||||
}
|
||||
export type {
|
||||
SelectedWorkOrderRef,
|
||||
SortDirection,
|
||||
WorkOrderListItem,
|
||||
WorkOrdersListParams,
|
||||
WorkOrdersListResult,
|
||||
WorkOrderUser,
|
||||
WorkOrdersSortableColumn,
|
||||
} from "./work-order-list";
|
||||
|
||||
export interface WorkOrderDispatchSummary {
|
||||
id: string | number;
|
||||
dispatchNumber: string;
|
||||
poNumber: string;
|
||||
vendorName: string;
|
||||
nteAmount: number | null;
|
||||
status: string;
|
||||
dispatchedAt: string;
|
||||
}
|
||||
export { ASSIGNEE_FILTER_UNASSIGNED, DISPATCH_LOCATION_WO_PAGE_SIZE } from "./work-order-list";
|
||||
|
||||
export interface WorkOrderDetail {
|
||||
id: string | number;
|
||||
internalWONumber: string;
|
||||
workOrderNumber: string;
|
||||
title: string;
|
||||
location: string;
|
||||
locationId: string;
|
||||
locationZip: string;
|
||||
assignedTo: string;
|
||||
dueDate: string;
|
||||
createdDate: string;
|
||||
createdDateFull: string;
|
||||
description: string;
|
||||
priority: string;
|
||||
status: string;
|
||||
po: string;
|
||||
tt: string;
|
||||
problem: string;
|
||||
trade: string;
|
||||
subTrade: string;
|
||||
vendorNTE: string;
|
||||
scheduledDate: string;
|
||||
completedDate: string;
|
||||
source: string;
|
||||
comments: WorkOrderComment[];
|
||||
auditLog: WorkOrderAuditEntry[];
|
||||
dispatches: WorkOrderDispatchSummary[];
|
||||
}
|
||||
export type {
|
||||
CompletionDocStatus,
|
||||
WorkOrderDayGroup,
|
||||
WorkOrderTableDraft,
|
||||
WorkOrderTableRow,
|
||||
} from "./work-order-table-row";
|
||||
|
||||
export interface WorkOrderFormValues {
|
||||
internalWONumber: string;
|
||||
workOrderNumber: string;
|
||||
title: string;
|
||||
locationId: string;
|
||||
priority: string;
|
||||
status: string;
|
||||
createdDate: string;
|
||||
dueDate: string;
|
||||
scheduledDate: string;
|
||||
completedDate: string;
|
||||
assignedTo: string;
|
||||
description: string;
|
||||
po: string;
|
||||
tt: string;
|
||||
problem: string;
|
||||
trade: string;
|
||||
subTrade: string;
|
||||
vendorNTE: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface LocationOption {
|
||||
id: string | number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface EmployeeOption {
|
||||
id: string | number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface VendorDropdownItem {
|
||||
id: string | number;
|
||||
companyName: string;
|
||||
tradeSpecialties: string;
|
||||
address: string;
|
||||
distanceMiles: number | null;
|
||||
}
|
||||
|
||||
export interface TaskListTemplate {
|
||||
id: string | number;
|
||||
name: string;
|
||||
items: { id?: string | number; itemText?: string }[];
|
||||
}
|
||||
|
||||
export interface DispatchChecklistItem {
|
||||
id: string | number;
|
||||
itemText: string;
|
||||
isCompleted: boolean;
|
||||
completedBy: string | null;
|
||||
completedAt: string | null;
|
||||
}
|
||||
|
||||
export interface DispatchSignoff {
|
||||
id: string | number;
|
||||
signoffType: string;
|
||||
name: string;
|
||||
signature: string;
|
||||
signatureMethod: string;
|
||||
signedAt: string;
|
||||
}
|
||||
|
||||
export interface DispatchComment {
|
||||
id: string | number;
|
||||
createdDate: string;
|
||||
commenttext: string;
|
||||
userName: string;
|
||||
commenter: string | null;
|
||||
}
|
||||
|
||||
export interface DispatchWorkOrderRef {
|
||||
id: string | number;
|
||||
woNumber: string;
|
||||
}
|
||||
|
||||
export interface DispatchDetail {
|
||||
id: string | number;
|
||||
dispatchNumber: string;
|
||||
poNumber: string;
|
||||
vendorName: string;
|
||||
vendorEmail: string;
|
||||
woNumber: string;
|
||||
status: string;
|
||||
nteAmount: number | null;
|
||||
scheduledDate: string;
|
||||
completedDate: string;
|
||||
description: string;
|
||||
dispatchedAt: string;
|
||||
acknowledgedAt: string;
|
||||
verifiedAt: string;
|
||||
verifiedBy: string;
|
||||
checklistItems: DispatchChecklistItem[];
|
||||
signoffs: DispatchSignoff[];
|
||||
comments: DispatchComment[];
|
||||
workOrders: DispatchWorkOrderRef[];
|
||||
}
|
||||
|
||||
export interface DispatchToVendorPayload {
|
||||
workOrderIds: (string | number)[];
|
||||
vendorIds: (string | number)[];
|
||||
nteAmount: number | null;
|
||||
scheduledDate: string | null;
|
||||
description: string;
|
||||
taskListTemplateId: number | null;
|
||||
customChecklistItems: string[] | null;
|
||||
}
|
||||
|
||||
export interface UpdateDispatchPayload {
|
||||
id: string | number;
|
||||
status: string;
|
||||
nteAmount: number | null;
|
||||
scheduledDate: string | null;
|
||||
completedDate: string | null;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface UpliftRequest {
|
||||
id: string | number;
|
||||
status: string;
|
||||
currentNTE: number | null;
|
||||
requestedNTE: number;
|
||||
delta: number;
|
||||
vendorReason: string;
|
||||
requestedAt: string;
|
||||
requestedByVendorName: string;
|
||||
decidedAt: string;
|
||||
decidedByName: string;
|
||||
decisionNote: string;
|
||||
requiredTier: number;
|
||||
canDecide: boolean;
|
||||
}
|
||||
|
||||
export const WORK_ORDER_STATUS_OPTIONS = [
|
||||
"Open",
|
||||
"In Progress",
|
||||
"On Hold",
|
||||
"Done",
|
||||
"Cancelled",
|
||||
] as const;
|
||||
|
||||
export const WORK_ORDER_PRIORITY_OPTIONS = ["Sev 1", "Sev 2", "Sev 3", "Sev 4", "Sev 5"] as const;
|
||||
|
||||
export const DISPATCH_STATUS_OPTIONS = [
|
||||
"Sent",
|
||||
"Acknowledged",
|
||||
"In Progress",
|
||||
"Completed",
|
||||
"Verified",
|
||||
"Cancelled",
|
||||
] as const;
|
||||
|
||||
export function createDefaultWorkOrderFormValues(): WorkOrderFormValues {
|
||||
return {
|
||||
internalWONumber: "",
|
||||
workOrderNumber: "",
|
||||
title: "",
|
||||
locationId: "",
|
||||
priority: "",
|
||||
status: "Open",
|
||||
createdDate: new Date().toISOString().split("T")[0] ?? "",
|
||||
dueDate: "",
|
||||
scheduledDate: "",
|
||||
completedDate: "",
|
||||
assignedTo: "",
|
||||
description: "",
|
||||
po: "",
|
||||
tt: "",
|
||||
problem: "",
|
||||
trade: "",
|
||||
subTrade: "",
|
||||
vendorNTE: "",
|
||||
source: "",
|
||||
};
|
||||
}
|
||||
export {
|
||||
EMPTY_TABLE_DRAFT,
|
||||
UNSCHEDULED_DAY_KEY,
|
||||
WORK_ORDERS_TABLE_PAGE_SIZE,
|
||||
} from "./work-order-table-row";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { API_PATHS } from "@/api/api-paths";
|
||||
import { apiGet } from "@/api/api";
|
||||
import { handleApiResponse, handlePaginatedResponse } from "@/api/handle-api-response";
|
||||
import { mapEmployeeOptions } from "@/domain/work-orders/mappers/work-order-mapper";
|
||||
import type { EmployeeOption } from "@/domain/work-orders/types/work-order";
|
||||
|
||||
async function fetchEmployeesFromList(): Promise<EmployeeOption[]> {
|
||||
const data = await apiGet<unknown>(API_PATHS.employee.getList, {
|
||||
searchParams: { page: 1, pageSize: 1000 },
|
||||
});
|
||||
const paginated = handlePaginatedResponse<unknown>(data);
|
||||
const items = paginated.items.map((item) => {
|
||||
const record = (typeof item === "object" && item !== null ? item : {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const firstName = String(record.firstName ?? record.FirstName ?? "");
|
||||
const lastName = String(record.lastName ?? record.LastName ?? "");
|
||||
return {
|
||||
id: record.id ?? record.Id,
|
||||
name: `${firstName} ${lastName}`.trim(),
|
||||
};
|
||||
});
|
||||
return mapEmployeeOptions(items);
|
||||
}
|
||||
|
||||
export async function fetchWorkOrderEmployees(): Promise<EmployeeOption[]> {
|
||||
try {
|
||||
const data = await apiGet<unknown>(API_PATHS.employee.dropdown);
|
||||
const items = handleApiResponse<unknown[]>(data);
|
||||
return Array.isArray(items) ? mapEmployeeOptions(items) : [];
|
||||
} catch {
|
||||
return fetchEmployeesFromList();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { useMutation, useQueryClient, type UseMutationResult } from "@tanstack/react-query";
|
||||
import { toast } from "react-toastify";
|
||||
import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
|
||||
import { shouldUseMockWoWizardCreate } from "@/domain/work-orders/mocks/is-wo-wizard-mock-enabled";
|
||||
import { buildMockWorkOrderDetail } from "@/domain/work-orders/mocks/wo-wizard-mock-data";
|
||||
import type { WorkOrderFormValues } from "@/domain/work-orders/types/work-order-detail";
|
||||
import type { WorkOrderDetail } from "@/domain/work-orders/types/work-order";
|
||||
import { queryKeys } from "@/infra/query-key/query-key";
|
||||
|
||||
async function createWorkOrderFromWizard(values: WorkOrderFormValues): Promise<WorkOrderDetail> {
|
||||
if (shouldUseMockWoWizardCreate(values.locationId)) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||
return buildMockWorkOrderDetail(values);
|
||||
}
|
||||
|
||||
return workOrdersApi.create(values);
|
||||
}
|
||||
|
||||
export function useCreateWorkOrderFromWizard(
|
||||
onSuccess?: () => void,
|
||||
): UseMutationResult<WorkOrderDetail, Error, WorkOrderFormValues> {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: createWorkOrderFromWizard,
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
|
||||
toast.success("Work Order created successfully!");
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to create work order");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { useQuery, type UseQueryResult } from "@tanstack/react-query";
|
||||
import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
|
||||
import { fetchWorkOrderEmployees } from "@/domain/work-orders/use-cases/fetch-work-order-employees";
|
||||
import type {
|
||||
DispatchDetail,
|
||||
EmployeeOption,
|
||||
|
|
@ -32,7 +33,7 @@ export function useWorkOrderLocations(): UseQueryResult<LocationOption[], Error>
|
|||
export function useWorkOrderEmployeesDropdown(): UseQueryResult<EmployeeOption[], Error> {
|
||||
return useQuery({
|
||||
queryKey: [...queryKeys.employees.all, "dropdown"] as const,
|
||||
queryFn: () => workOrdersApi.fetchEmployeesDropdown(),
|
||||
queryFn: () => fetchWorkOrderEmployees(),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,32 +20,32 @@ function invalidateWorkOrders(queryClient: ReturnType<typeof useQueryClient>) {
|
|||
|
||||
interface AddWorkOrderCommentInput {
|
||||
text: string;
|
||||
commentType: string;
|
||||
commentType: "customer" | "internal";
|
||||
}
|
||||
|
||||
interface ChangeWorkOrderAssignmentInput {
|
||||
userId: string | number;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
interface AddDispatchCommentInput {
|
||||
export interface AddDispatchCommentInput {
|
||||
text: string;
|
||||
sendEmail: boolean;
|
||||
}
|
||||
|
||||
interface UpdateChecklistItemInput {
|
||||
export interface UpdateChecklistItemInput {
|
||||
itemId: string | number;
|
||||
isCompleted: boolean;
|
||||
completedBy: string;
|
||||
}
|
||||
|
||||
interface AddDispatchSignoffInput {
|
||||
export interface AddDispatchSignoffInput {
|
||||
signoffType: string;
|
||||
name: string;
|
||||
signature: string;
|
||||
signatureMethod: string;
|
||||
}
|
||||
|
||||
interface ChangeWorkOrderAssignmentInput {
|
||||
userId: string | number;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export function useCreateWorkOrder(): UseMutationResult<
|
||||
WorkOrderDetail,
|
||||
Error,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "react-toastify";
|
||||
import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
|
||||
import {
|
||||
mapApiStatusToWizard,
|
||||
mapWizardStatusToApi,
|
||||
} from "@/domain/work-orders/mappers/wizard-draft-mapper";
|
||||
import {
|
||||
mapWorkOrderDetailToFormValues,
|
||||
mapWorkOrderTableRow,
|
||||
} from "@/domain/work-orders/mappers/work-order-mapper";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import type { WizardWOStatus } from "@/domain/work-orders/types/work-order-wizard";
|
||||
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
|
||||
import { enrichTableRow } from "@/domain/work-orders/utils/table-filter-utils";
|
||||
import { queryKeys } from "@/infra/query-key/query-key";
|
||||
|
||||
export type WorkOrderTablePatch = Partial<WorkOrderTableRow>;
|
||||
|
||||
async function loadFormAndUpdate(
|
||||
id: string | number,
|
||||
patch: (
|
||||
values: ReturnType<typeof mapWorkOrderDetailToFormValues>,
|
||||
) => ReturnType<typeof mapWorkOrderDetailToFormValues>,
|
||||
) {
|
||||
const detail = await workOrdersApi.getById(id);
|
||||
const values = mapWorkOrderDetailToFormValues(detail);
|
||||
const next = patch(values);
|
||||
await workOrdersApi.update(id, next);
|
||||
return detail;
|
||||
}
|
||||
|
||||
export function useWorkOrderTableMutations(
|
||||
onPatch: (id: string | number, patch: WorkOrderTablePatch) => void,
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const invalidate = () => {
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all });
|
||||
};
|
||||
|
||||
const patchMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
id,
|
||||
patch,
|
||||
useFullUpdate,
|
||||
}: {
|
||||
id: string | number;
|
||||
patch: WorkOrderTablePatch;
|
||||
useFullUpdate?: boolean;
|
||||
}) => {
|
||||
if (patch.status !== undefined && !useFullUpdate) {
|
||||
const apiStatus = mapWizardStatusToApi(patch.status as WizardWOStatus);
|
||||
await workOrdersApi.changeStatus(id, apiStatus);
|
||||
return patch;
|
||||
}
|
||||
if (patch.dispatcherId !== undefined && !useFullUpdate) {
|
||||
await workOrdersApi.changeAssignment(id, patch.dispatcherId || "");
|
||||
return {
|
||||
...patch,
|
||||
dispatcherName: patch.dispatcherName ?? "",
|
||||
};
|
||||
}
|
||||
await loadFormAndUpdate(id, (values) => ({
|
||||
...values,
|
||||
workOrderNumber: patch.woNumber ?? values.workOrderNumber,
|
||||
internalWONumber: patch.woNumber ?? values.internalWONumber,
|
||||
locationId: patch.locationId != null ? String(patch.locationId) : values.locationId,
|
||||
dueDate: patch.dueDate ?? values.dueDate,
|
||||
scheduledDate: patch.scheduledOn ?? values.scheduledDate,
|
||||
assignedTo: patch.dispatcherName ?? values.assignedTo,
|
||||
problem: patch.pm ?? values.problem,
|
||||
source: patch.type ?? values.source,
|
||||
priority: values.priority,
|
||||
status:
|
||||
patch.status !== undefined
|
||||
? mapWizardStatusToApi(patch.status as WizardWOStatus)
|
||||
: values.status,
|
||||
description: [
|
||||
values.description,
|
||||
patch.pocNotes ? `POC notes: ${patch.pocNotes}` : "",
|
||||
patch.pocName || patch.pocPhone
|
||||
? `POC: ${[patch.pocName, patch.pocPhone].filter(Boolean).join(" · ")}`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
}));
|
||||
return patch;
|
||||
},
|
||||
onMutate: async ({ id, patch }) => {
|
||||
onPatch(id, patch);
|
||||
},
|
||||
onSuccess: () => {
|
||||
invalidate();
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message || "Failed to update work order");
|
||||
invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const patchField = (id: string | number, patch: WorkOrderTablePatch, useFullUpdate = false) => {
|
||||
patchMutation.mutate({ id, patch, useFullUpdate });
|
||||
};
|
||||
|
||||
return {
|
||||
patchField,
|
||||
isPending: patchMutation.isPending,
|
||||
};
|
||||
}
|
||||
|
||||
export function tableRowFromDetail(detail: Awaited<ReturnType<typeof workOrdersApi.getById>>) {
|
||||
return enrichTableRow(
|
||||
mapWorkOrderTableRow({
|
||||
...detail,
|
||||
status: mapApiStatusToWizard(detail.status),
|
||||
scheduledOn: detail.scheduledDate,
|
||||
woNumber: detail.internalWONumber || detail.workOrderNumber,
|
||||
dispatcherName: detail.assignedTo,
|
||||
pm: detail.problem,
|
||||
type: detail.source,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function maybeAutoSchedulePatch(row: WorkOrderTableRow): WorkOrderTablePatch {
|
||||
const hasDate = !!row.scheduledOn;
|
||||
const hasDispatcher = !!row.dispatcherId;
|
||||
if (hasDate && hasDispatcher && row.status === "Incomplete") {
|
||||
return { status: "Scheduled" };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function applySchedulePatch(
|
||||
scheduledOn: string,
|
||||
scheduleWeekOnly: boolean,
|
||||
): WorkOrderTablePatch {
|
||||
if (scheduleWeekOnly) {
|
||||
return {
|
||||
scheduledOn: "",
|
||||
targetWeek: scheduledOn,
|
||||
scheduleWeekOnly: true,
|
||||
...deriveDayFields(""),
|
||||
};
|
||||
}
|
||||
return {
|
||||
scheduledOn,
|
||||
scheduleWeekOnly: false,
|
||||
targetWeek: "",
|
||||
...deriveDayFields(scheduledOn),
|
||||
};
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export function useWorkOrdersList(
|
|||
params: WorkOrdersListParams,
|
||||
): UseQueryResult<WorkOrdersListResult, Error> {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.workOrders.list(params as Record<string, unknown>),
|
||||
queryKey: queryKeys.workOrders.list(params),
|
||||
queryFn: () => workOrdersApi.getList(params),
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
|
@ -24,3 +24,16 @@ export function useWorkOrderUsers(): UseQueryResult<WorkOrderUser[], Error> {
|
|||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useWorkOrdersByLocation(
|
||||
locationId: string | number | undefined,
|
||||
search = "",
|
||||
enabled = true,
|
||||
): UseQueryResult<WorkOrdersListResult, Error> {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.workOrders.byLocation(locationId ?? "", search),
|
||||
queryFn: () => workOrdersApi.getListByLocation(locationId!, search),
|
||||
enabled: enabled && locationId !== undefined && locationId !== "",
|
||||
staleTime: 30 * 1000,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
type WorkOrdersListParams = Record<string, unknown>;
|
||||
import type { WorkOrdersListParams } from "@/domain/work-orders/types/work-order-list";
|
||||
|
||||
export const queryKeys = {
|
||||
auth: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue