mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-04 14:52:06 +00:00
fix(work-orders): separate Unscheduled header collapse and Add WO controls
[recover] remove malicious eslint payload (was ccec7322)
This commit is contained in:
parent
78ed8571a0
commit
2ed770bc79
6 changed files with 445 additions and 34 deletions
|
|
@ -118,6 +118,15 @@ Pending (`No`)=2, Uploaded (`Yes`)=1, N/N (`NN`)=3.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Buckets
|
||||||
|
|
||||||
|
| Array | Inclusion rule |
|
||||||
|
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `scheduled` | WOs with `scheduledDate` inside `weekStart`–`weekEnd` |
|
||||||
|
| `unscheduled` | **All** WOs without `scheduledDate` (cross-week). Visible for every week request; `targetWeek` is not a week filter — Schedule-cell marker only |
|
||||||
|
|
||||||
|
The FE concatenates `unscheduled + scheduled` into `items` and renders `unscheduled` in the pinned Unscheduled section above day groups. Dated WOs without a dispatcher stay in `scheduled` / day groups (Assigned To highlight), not in `unscheduled`.
|
||||||
|
|
||||||
### `/board/search`
|
### `/board/search`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
|
||||||
|
|
@ -23,52 +23,79 @@ export function WoTableSectionHeader({
|
||||||
const isUnscheduled = variant === "unscheduled";
|
const isUnscheduled = variant === "unscheduled";
|
||||||
|
|
||||||
if (isUnscheduled) {
|
if (isUnscheduled) {
|
||||||
|
const toggleCollapse = () => {
|
||||||
|
onToggleCollapse?.();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr style={{ background: "rgba(217, 119, 6, 0.08)" }}>
|
<tr style={{ background: "rgba(217, 119, 6, 0.08)" }}>
|
||||||
<td
|
<td
|
||||||
colSpan={WO_TABLE_COLS.length}
|
colSpan={WO_TABLE_COLS.length}
|
||||||
onClick={onToggleCollapse}
|
|
||||||
style={{
|
style={{
|
||||||
height: 40,
|
height: 40,
|
||||||
padding: "0 14px",
|
padding: "0 14px",
|
||||||
cursor: "pointer",
|
|
||||||
boxShadow: "inset 3px 0 0 var(--warning)",
|
boxShadow: "inset 3px 0 0 var(--warning)",
|
||||||
borderTop: "1px solid #FDE68A",
|
borderTop: "1px solid #FDE68A",
|
||||||
borderBottom: "1px solid #FDE68A",
|
borderBottom: "1px solid #FDE68A",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<AlertTriangle size={14} className="shrink-0" style={{ color: "var(--warning)" }} />
|
<button
|
||||||
<span
|
type="button"
|
||||||
className="uppercase"
|
className="flex items-center gap-2.5"
|
||||||
|
aria-expanded={!collapsed}
|
||||||
|
aria-label={label}
|
||||||
|
onClick={toggleCollapse}
|
||||||
style={{
|
style={{
|
||||||
fontFamily: "var(--font-mono)",
|
background: "transparent",
|
||||||
fontSize: 12,
|
border: "none",
|
||||||
fontWeight: 700,
|
padding: 0,
|
||||||
letterSpacing: "0.08em",
|
cursor: "pointer",
|
||||||
color: "var(--warning)",
|
color: "inherit",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{label}
|
<AlertTriangle size={14} className="shrink-0" style={{ color: "var(--warning)" }} />
|
||||||
</span>
|
<span
|
||||||
<span style={{ fontSize: 12, color: "var(--color-text-muted)" }}>
|
className="uppercase"
|
||||||
{count} work order{count !== 1 ? "s" : ""}
|
style={{
|
||||||
</span>
|
fontFamily: "var(--font-mono)",
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: "0.08em",
|
||||||
|
color: "var(--warning)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 12, color: "var(--color-text-muted)" }}>
|
||||||
|
{count} work order{count !== 1 ? "s" : ""}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center gap-1 transition-opacity hover:opacity-70"
|
className="flex items-center gap-1 transition-opacity hover:opacity-70"
|
||||||
style={{ fontSize: 12.5, fontWeight: 600, color: "var(--primary)" }}
|
style={{ fontSize: 12.5, fontWeight: 600, color: "var(--primary)" }}
|
||||||
onClick={(e) => {
|
onClick={onAddWo}
|
||||||
e.stopPropagation();
|
|
||||||
onAddWo();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Plus size={13} />
|
<Plus size={13} />
|
||||||
Add WO
|
Add WO
|
||||||
</button>
|
</button>
|
||||||
<span className="ml-auto flex items-center" style={{ color: "var(--warning)" }}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ml-auto flex items-center"
|
||||||
|
aria-expanded={!collapsed}
|
||||||
|
aria-label={collapsed ? `Expand ${label}` : `Collapse ${label}`}
|
||||||
|
onClick={toggleCollapse}
|
||||||
|
style={{
|
||||||
|
background: "transparent",
|
||||||
|
border: "none",
|
||||||
|
padding: 0,
|
||||||
|
cursor: "pointer",
|
||||||
|
color: "var(--warning)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{collapsed ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
|
{collapsed ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
|
||||||
</span>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -129,10 +156,7 @@ export function WoTableSectionHeader({
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center gap-1 transition-opacity hover:opacity-70"
|
className="flex items-center gap-1 transition-opacity hover:opacity-70"
|
||||||
style={{ fontSize: 12.5, fontWeight: 600, color: "var(--primary)" }}
|
style={{ fontSize: 12.5, fontWeight: 600, color: "var(--primary)" }}
|
||||||
onClick={(e) => {
|
onClick={onAddWo}
|
||||||
e.stopPropagation();
|
|
||||||
onAddWo();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Plus size={13} />
|
<Plus size={13} />
|
||||||
Add WO
|
Add WO
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import type {
|
||||||
WorkOrderTableRow,
|
WorkOrderTableRow,
|
||||||
} from "@/domain/work-orders/types/work-order-table-row";
|
} from "@/domain/work-orders/types/work-order-table-row";
|
||||||
import { ADVANCED_FILTER_PREDICATES } from "@/domain/work-orders/utils/advanced-filter-predicates";
|
import { ADVANCED_FILTER_PREDICATES } from "@/domain/work-orders/utils/advanced-filter-predicates";
|
||||||
import { isDateInWeek } from "@/domain/work-orders/utils/table-day-utils";
|
|
||||||
import { weekRange } from "@/domain/work-orders/utils/list-week-utils";
|
import { weekRange } from "@/domain/work-orders/utils/list-week-utils";
|
||||||
|
|
||||||
const TERMINAL_STATUSES = new Set(["Completed", "Canceled", "Done", "Cancelled"]);
|
const TERMINAL_STATUSES = new Set(["Completed", "Canceled", "Done", "Cancelled"]);
|
||||||
|
|
@ -91,14 +90,10 @@ export function filterByBarAndSearch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.scheduledOn) {
|
// Design weekScoped: undated (!scheduledOn) stay visible every week; targetWeek
|
||||||
if (row.scheduledOn < weekFrom || row.scheduledOn > weekTo) {
|
// is a Schedule-cell marker only, never a week-inclusion predicate.
|
||||||
return false;
|
if (row.scheduledOn && (row.scheduledOn < weekFrom || row.scheduledOn > weekTo)) {
|
||||||
}
|
return false;
|
||||||
} else if (row.targetWeek) {
|
|
||||||
if (!isDateInWeek(row.targetWeek, bar.weekMonday)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { WoTableBody } from "@/app/(protected)/workorders/_components/list/table/wo-table-body";
|
||||||
|
import type { ReturnTypeOfUseWorkOrdersTableData } from "@/app/(protected)/workorders/_components/list/table/wo-table-types";
|
||||||
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||||
|
|
||||||
|
function makeRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
|
||||||
|
return {
|
||||||
|
id: "1",
|
||||||
|
woNumber: "24991",
|
||||||
|
site: "CLV2",
|
||||||
|
type: "PM",
|
||||||
|
dispatcherId: "",
|
||||||
|
dispatcherName: "",
|
||||||
|
scheduledOn: "2026-06-02",
|
||||||
|
targetWeek: "",
|
||||||
|
dayGroup: "06/02",
|
||||||
|
dayLabel: "TUE · 06/02",
|
||||||
|
location: "Cleveland, OH",
|
||||||
|
locationId: "",
|
||||||
|
pm: "Backflow",
|
||||||
|
serviceNotes: "",
|
||||||
|
extraServices: [],
|
||||||
|
company: "",
|
||||||
|
tech: "",
|
||||||
|
techPhone: "",
|
||||||
|
apptStart: "",
|
||||||
|
apptEnd: "",
|
||||||
|
apptTime: "—",
|
||||||
|
status: "Incomplete",
|
||||||
|
docStatus: "No",
|
||||||
|
pocName: "",
|
||||||
|
pocPhone: "",
|
||||||
|
pocNotes: "",
|
||||||
|
dueDate: "",
|
||||||
|
rescheduleCount: 0,
|
||||||
|
carriedOver: 0,
|
||||||
|
originalWeek: "",
|
||||||
|
originalDate: "",
|
||||||
|
isPastDue: false,
|
||||||
|
scheduleWeekOnly: false,
|
||||||
|
mediaCount: 0,
|
||||||
|
title: "",
|
||||||
|
priority: "",
|
||||||
|
vendorId: "",
|
||||||
|
primaryDispatchId: null,
|
||||||
|
rowVersion: "",
|
||||||
|
dispatchRowVersion: "",
|
||||||
|
flagColor: null,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseTableData(
|
||||||
|
overrides: Partial<ReturnTypeOfUseWorkOrdersTableData> = {},
|
||||||
|
): ReturnTypeOfUseWorkOrdersTableData {
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
isLoading: false,
|
||||||
|
isFetching: false,
|
||||||
|
error: null,
|
||||||
|
refetch: vi.fn(),
|
||||||
|
advActive: false,
|
||||||
|
advResults: [],
|
||||||
|
unscheduled: [],
|
||||||
|
displayGroups: [],
|
||||||
|
searchActive: false,
|
||||||
|
noSearchResults: false,
|
||||||
|
isEmptyWeek: false,
|
||||||
|
todayKey: null,
|
||||||
|
draftDayKey: null,
|
||||||
|
draft: null,
|
||||||
|
setDraft: vi.fn(),
|
||||||
|
unschedOpen: true,
|
||||||
|
setUnschedOpen: vi.fn(),
|
||||||
|
selectedId: null,
|
||||||
|
setSelectedId: vi.fn(),
|
||||||
|
selectedRow: null,
|
||||||
|
flashId: null,
|
||||||
|
triggerFlash: vi.fn(),
|
||||||
|
dragViz: null,
|
||||||
|
setDragViz: vi.fn(),
|
||||||
|
dragState: null,
|
||||||
|
patchRow: vi.fn(),
|
||||||
|
clearRowPatch: vi.fn(),
|
||||||
|
reorderRows: vi.fn(),
|
||||||
|
endDrag: vi.fn(),
|
||||||
|
startDayDraft: vi.fn(),
|
||||||
|
cancelDraft: vi.fn(),
|
||||||
|
addRow: vi.fn(),
|
||||||
|
scopeTotalCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
search: "",
|
||||||
|
...overrides,
|
||||||
|
} as ReturnTypeOfUseWorkOrdersTableData;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("WoTableBody Unscheduled vs day groups", () => {
|
||||||
|
it("renders Unscheduled above day groups and keeps dated unassigned under the day header", () => {
|
||||||
|
const datedUnassigned = makeRow({ id: "u-dated", woNumber: "24991" });
|
||||||
|
const undated = makeRow({
|
||||||
|
id: "us1",
|
||||||
|
woNumber: "25021",
|
||||||
|
scheduledOn: "",
|
||||||
|
dayGroup: "",
|
||||||
|
dayLabel: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableData = baseTableData({
|
||||||
|
unscheduled: [undated],
|
||||||
|
displayGroups: [
|
||||||
|
{
|
||||||
|
key: "06/02",
|
||||||
|
label: "TUE · 06/02",
|
||||||
|
date: "2026-06-02",
|
||||||
|
items: [datedUnassigned],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
unschedOpen: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<table>
|
||||||
|
<WoTableBody
|
||||||
|
tableData={tableData}
|
||||||
|
renderRow={(row) => (
|
||||||
|
<tr key={String(row.id)} data-testid={`row-${row.id}`}>
|
||||||
|
<td>{row.woNumber}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
renderInlineRow={() => null}
|
||||||
|
/>
|
||||||
|
</table>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("button", { name: "Unscheduled" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("TUE · 06/02")).toBeInTheDocument();
|
||||||
|
|
||||||
|
const unscheduledHeader = screen.getByRole("button", { name: "Unscheduled" });
|
||||||
|
const dayLabel = screen.getByText("TUE · 06/02");
|
||||||
|
const undatedRow = screen.getByTestId("row-us1");
|
||||||
|
const datedRow = screen.getByTestId("row-u-dated");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
unscheduledHeader.compareDocumentPosition(undatedRow) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
dayLabel.compareDocumentPosition(datedRow) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(screen.queryByTestId("row-u-dated")).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByTestId(/row-/)).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { WorkOrderBarFilters } from "@/domain/work-orders/types/work-order-list-filters";
|
||||||
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||||
|
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
|
||||||
|
import {
|
||||||
|
filterByBarAndSearch,
|
||||||
|
splitScheduledUnscheduled,
|
||||||
|
} from "@/domain/work-orders/utils/table-filter-utils";
|
||||||
|
import { weekRange } from "@/domain/work-orders/utils/list-week-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Design oracle — mirrors shoc-desing/src/pages/WorkOrders.tsx:
|
||||||
|
* - weekScoped (~L9734): !scheduledOn || (scheduledOn >= wStart && scheduledOn <= wEnd)
|
||||||
|
* - split (~L9762): unscheduled = !scheduledOn; dated-but-unassigned stay in day groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
type DesignRow = Pick<WorkOrderTableRow, "id" | "scheduledOn" | "dispatcherId" | "targetWeek">;
|
||||||
|
|
||||||
|
function designWeekScoped(rows: DesignRow[], weekStart: string, weekEnd: string): DesignRow[] {
|
||||||
|
return rows.filter(
|
||||||
|
(row) => !row.scheduledOn || (row.scheduledOn >= weekStart && row.scheduledOn <= weekEnd),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function designSplit(rows: DesignRow[]): {
|
||||||
|
scheduled: DesignRow[];
|
||||||
|
unscheduled: DesignRow[];
|
||||||
|
} {
|
||||||
|
const scheduled: DesignRow[] = [];
|
||||||
|
const unscheduled: DesignRow[] = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
if (!row.scheduledOn) {
|
||||||
|
unscheduled.push(row);
|
||||||
|
} else {
|
||||||
|
scheduled.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { scheduled, unscheduled };
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
|
||||||
|
return {
|
||||||
|
id: "1",
|
||||||
|
woNumber: "24991",
|
||||||
|
site: "CLV2",
|
||||||
|
type: "PM",
|
||||||
|
dispatcherId: "u1",
|
||||||
|
dispatcherName: "Alice",
|
||||||
|
scheduledOn: "2026-06-02",
|
||||||
|
targetWeek: "",
|
||||||
|
dayGroup: "06/02",
|
||||||
|
dayLabel: "TUE · 06/02",
|
||||||
|
location: "Cleveland, OH",
|
||||||
|
locationId: "",
|
||||||
|
pm: "Backflow",
|
||||||
|
serviceNotes: "",
|
||||||
|
extraServices: [],
|
||||||
|
company: "",
|
||||||
|
tech: "",
|
||||||
|
techPhone: "",
|
||||||
|
apptStart: "",
|
||||||
|
apptEnd: "",
|
||||||
|
apptTime: "—",
|
||||||
|
status: "Incomplete",
|
||||||
|
docStatus: "No",
|
||||||
|
pocName: "",
|
||||||
|
pocPhone: "",
|
||||||
|
pocNotes: "",
|
||||||
|
dueDate: "",
|
||||||
|
rescheduleCount: 0,
|
||||||
|
carriedOver: 0,
|
||||||
|
originalWeek: "",
|
||||||
|
originalDate: "",
|
||||||
|
isPastDue: false,
|
||||||
|
scheduleWeekOnly: false,
|
||||||
|
mediaCount: 0,
|
||||||
|
title: "",
|
||||||
|
priority: "",
|
||||||
|
vendorId: "",
|
||||||
|
primaryDispatchId: null,
|
||||||
|
rowVersion: "",
|
||||||
|
dispatchRowVersion: "",
|
||||||
|
flagColor: null,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function idsOf(rows: { id: string | number }[]): string[] {
|
||||||
|
return rows.map((row) => String(row.id)).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("board Unscheduled design parity", () => {
|
||||||
|
const weekMonday = "2026-06-01";
|
||||||
|
const [weekStart, weekEnd] = weekRange(weekMonday);
|
||||||
|
|
||||||
|
const fixtures: WorkOrderTableRow[] = [
|
||||||
|
makeRow({
|
||||||
|
id: "u-dated",
|
||||||
|
dispatcherId: "",
|
||||||
|
dispatcherName: "",
|
||||||
|
scheduledOn: "2026-06-02",
|
||||||
|
...deriveDayFields("2026-06-02"),
|
||||||
|
}),
|
||||||
|
makeRow({
|
||||||
|
id: "us-undated",
|
||||||
|
dispatcherId: "",
|
||||||
|
dispatcherName: "",
|
||||||
|
scheduledOn: "",
|
||||||
|
dayGroup: "",
|
||||||
|
dayLabel: "",
|
||||||
|
}),
|
||||||
|
makeRow({
|
||||||
|
id: "ua-assigned-undated",
|
||||||
|
dispatcherId: "sarah",
|
||||||
|
dispatcherName: "Sarah",
|
||||||
|
scheduledOn: "",
|
||||||
|
dayGroup: "",
|
||||||
|
dayLabel: "",
|
||||||
|
}),
|
||||||
|
makeRow({
|
||||||
|
id: "tw-other-week",
|
||||||
|
dispatcherId: "sarah",
|
||||||
|
dispatcherName: "Sarah",
|
||||||
|
scheduledOn: "",
|
||||||
|
targetWeek: "2026-07-06",
|
||||||
|
dayGroup: "",
|
||||||
|
dayLabel: "",
|
||||||
|
}),
|
||||||
|
makeRow({
|
||||||
|
id: "dated-other-week",
|
||||||
|
scheduledOn: "2026-06-15",
|
||||||
|
...deriveDayFields("2026-06-15"),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const bar: WorkOrderBarFilters = {
|
||||||
|
weekMonday,
|
||||||
|
dispatcherIds: new Set(),
|
||||||
|
type: "All",
|
||||||
|
};
|
||||||
|
|
||||||
|
it("matches design weekScoped ID set via filterByBarAndSearch", () => {
|
||||||
|
const designIds = idsOf(designWeekScoped(fixtures, weekStart, weekEnd));
|
||||||
|
const feIds = idsOf(filterByBarAndSearch(fixtures, bar, ""));
|
||||||
|
expect(feIds).toEqual(designIds);
|
||||||
|
expect(feIds).toContain("tw-other-week");
|
||||||
|
expect(feIds).not.toContain("dated-other-week");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches design split: dated unassigned in scheduled, undated in Unscheduled", () => {
|
||||||
|
const scoped = filterByBarAndSearch(fixtures, bar, "");
|
||||||
|
const feSplit = splitScheduledUnscheduled(scoped);
|
||||||
|
const designSplitResult = designSplit(designWeekScoped(fixtures, weekStart, weekEnd));
|
||||||
|
|
||||||
|
expect(idsOf(feSplit.unscheduled)).toEqual(idsOf(designSplitResult.unscheduled));
|
||||||
|
expect(idsOf(feSplit.scheduled)).toEqual(idsOf(designSplitResult.scheduled));
|
||||||
|
|
||||||
|
expect(idsOf(feSplit.scheduled)).toContain("u-dated");
|
||||||
|
expect(idsOf(feSplit.unscheduled)).not.toContain("u-dated");
|
||||||
|
expect(idsOf(feSplit.unscheduled)).toContain("tw-other-week");
|
||||||
|
expect(idsOf(feSplit.unscheduled)).toEqual(
|
||||||
|
expect.arrayContaining(["us-undated", "ua-assigned-undated", "tw-other-week"]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||||
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
|
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
|
||||||
|
import type { WorkOrderBarFilters } from "@/domain/work-orders/types/work-order-list-filters";
|
||||||
import {
|
import {
|
||||||
buildDayGroups,
|
buildDayGroups,
|
||||||
|
filterByBarAndSearch,
|
||||||
matchesAdvancedFilters,
|
matchesAdvancedFilters,
|
||||||
splitScheduledUnscheduled,
|
splitScheduledUnscheduled,
|
||||||
} from "@/domain/work-orders/utils/table-filter-utils";
|
} from "@/domain/work-orders/utils/table-filter-utils";
|
||||||
|
|
@ -57,6 +59,18 @@ function makeRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bar(
|
||||||
|
weekMonday: string,
|
||||||
|
overrides: Partial<WorkOrderBarFilters> = {},
|
||||||
|
): WorkOrderBarFilters {
|
||||||
|
return {
|
||||||
|
weekMonday,
|
||||||
|
dispatcherIds: new Set(),
|
||||||
|
type: "All",
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("deriveDayFields", () => {
|
describe("deriveDayFields", () => {
|
||||||
it("derives MM/DD group and weekday label", () => {
|
it("derives MM/DD group and weekday label", () => {
|
||||||
expect(deriveDayFields("2026-06-23")).toEqual({
|
expect(deriveDayFields("2026-06-23")).toEqual({
|
||||||
|
|
@ -66,6 +80,40 @@ describe("deriveDayFields", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("filterByBarAndSearch", () => {
|
||||||
|
const weekMonday = "2026-06-01";
|
||||||
|
|
||||||
|
it("keeps undated rows for any week, including targetWeek outside the active week", () => {
|
||||||
|
const undated = makeRow({ id: "us1", scheduledOn: "", targetWeek: "" });
|
||||||
|
const undatedOtherTarget = makeRow({
|
||||||
|
id: "tw1",
|
||||||
|
scheduledOn: "",
|
||||||
|
targetWeek: "2026-07-06",
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = filterByBarAndSearch([undated, undatedOtherTarget], bar(weekMonday), "").map(
|
||||||
|
(row) => row.id,
|
||||||
|
);
|
||||||
|
expect(ids).toEqual(["us1", "tw1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("excludes dated rows outside the active week", () => {
|
||||||
|
const inWeek = makeRow({
|
||||||
|
id: "in",
|
||||||
|
scheduledOn: "2026-06-03",
|
||||||
|
...deriveDayFields("2026-06-03"),
|
||||||
|
});
|
||||||
|
const outWeek = makeRow({
|
||||||
|
id: "out",
|
||||||
|
scheduledOn: "2026-06-15",
|
||||||
|
...deriveDayFields("2026-06-15"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = filterByBarAndSearch([inWeek, outWeek], bar(weekMonday), "").map((row) => row.id);
|
||||||
|
expect(ids).toEqual(["in"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("splitScheduledUnscheduled", () => {
|
describe("splitScheduledUnscheduled", () => {
|
||||||
it("splits rows without scheduledOn into unscheduled", () => {
|
it("splits rows without scheduledOn into unscheduled", () => {
|
||||||
const scheduled = makeRow({ id: "a", scheduledOn: "2026-06-23" });
|
const scheduled = makeRow({ id: "a", scheduledOn: "2026-06-23" });
|
||||||
|
|
@ -75,6 +123,23 @@ describe("splitScheduledUnscheduled", () => {
|
||||||
expect(result.unscheduled).toHaveLength(1);
|
expect(result.unscheduled).toHaveLength(1);
|
||||||
expect(result.unscheduled[0].id).toBe("b");
|
expect(result.unscheduled[0].id).toBe("b");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps dated unassigned in scheduled and undated in unscheduled without duplicate ids", () => {
|
||||||
|
const datedUnassigned = makeRow({
|
||||||
|
id: "u-dated",
|
||||||
|
dispatcherId: "",
|
||||||
|
dispatcherName: "",
|
||||||
|
scheduledOn: "2026-06-02",
|
||||||
|
...deriveDayFields("2026-06-02"),
|
||||||
|
});
|
||||||
|
const undated = makeRow({ id: "us-undated", scheduledOn: "", dispatcherId: "" });
|
||||||
|
const result = splitScheduledUnscheduled([datedUnassigned, undated]);
|
||||||
|
|
||||||
|
expect(result.scheduled.map((row) => row.id)).toEqual(["u-dated"]);
|
||||||
|
expect(result.unscheduled.map((row) => row.id)).toEqual(["us-undated"]);
|
||||||
|
const allIds = [...result.scheduled, ...result.unscheduled].map((row) => row.id);
|
||||||
|
expect(new Set(allIds).size).toBe(allIds.length);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildDayGroups", () => {
|
describe("buildDayGroups", () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue