diff --git a/e2e/work-orders/week-day-navigation.spec.ts b/e2e/work-orders/week-day-navigation.spec.ts
new file mode 100644
index 00000000..5ec5402b
--- /dev/null
+++ b/e2e/work-orders/week-day-navigation.spec.ts
@@ -0,0 +1,124 @@
+import { expect, test, type Page, type Route } from "@playwright/test";
+
+const FROZEN_NOW = "2026-08-19T15:00:00.000Z";
+
+const TEST_USER = {
+ token: "wo-week-day-e2e-token",
+ expiration: "2030-01-01T00:00:00.000Z",
+ email: "admin@seahavenind.com",
+ userRoles: "Admin",
+ phoneNumber: "5551234567",
+ fullname: "Work Order E2E Admin",
+ id: "1",
+};
+
+async function fulfillJson(route: Route, body: unknown, status = 200) {
+ await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body) });
+}
+
+function boardRow(id: number, woNumber: string, scheduledDate: string | null) {
+ return {
+ id,
+ woNumber,
+ workOrderType: 2,
+ siteCode: woNumber,
+ lifecycleStatusLabel: "Scheduled",
+ scheduledDate,
+ dispatcherId: "1",
+ dispatcherName: "Pat Dispatcher",
+ pm: "HVAC",
+ };
+}
+
+async function mockWorkOrderBoard(page: Page) {
+ await page.route("**/api/**", (route) => {
+ const pathname = new URL(route.request().url()).pathname;
+ if (!pathname.startsWith("/api/")) {
+ return route.fallback();
+ }
+ if (pathname.includes("/workorders/board")) {
+ return route.fallback();
+ }
+ return fulfillJson(route, { data: [] });
+ });
+ await page.route("**/api/workorders/board*", (route) => {
+ if (route.request().url().includes("board/search")) {
+ return route.fallback();
+ }
+ const url = new URL(route.request().url());
+ const weekStart = url.searchParams.get("weekStart") ?? "2026-08-17";
+ const scheduled =
+ weekStart === "2026-08-17"
+ ? [boardRow(1, "MON-WO", "2026-08-17"), boardRow(2, "WED-WO", "2026-08-19")]
+ : [boardRow(4, "PREV-MON-WO", "2026-08-10")];
+ return fulfillJson(route, {
+ weekStart,
+ weekEnd: weekStart === "2026-08-17" ? "2026-08-21" : "2026-08-14",
+ counts: { returned: scheduled.length + 1, total: scheduled.length + 1 },
+ unscheduled: [boardRow(3, "UNSCHED-WO", null)],
+ scheduled,
+ });
+ });
+ await page.route("**/api/workorders/board/search*", (route) =>
+ fulfillJson(route, { items: [], totalCount: 0, page: 0, pageSize: 50 }),
+ );
+}
+
+test.describe("Work Orders week/day navigation (SH-240)", () => {
+ test.beforeEach(async ({ page }) => {
+ await page.clock.install({ time: new Date(FROZEN_NOW) });
+ await page.clock.resume();
+ await page.addInitScript(
+ (user) => localStorage.setItem("auth", JSON.stringify(user)),
+ TEST_USER,
+ );
+ await mockWorkOrderBoard(page);
+ });
+
+ test("scopes the table to one day and excludes Unscheduled", async ({ page }) => {
+ await page.goto("/workorders");
+ await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
+ timeout: 15_000,
+ });
+ await expect(page.getByRole("button", { name: "Week 8/17–8/21" })).toBeVisible();
+ await expect(page.getByRole("button", { name: "Week 8/17–8/21" })).toContainText("Live");
+
+ await expect(page.getByRole("tab", { name: "Mon · 08/17" })).toBeVisible();
+ await expect(page.getByRole("tab", { name: "Wed · 08/19" })).toHaveAttribute(
+ "aria-selected",
+ "true",
+ );
+
+ await expect(page.getByText("WED-WO").first()).toBeVisible();
+ await expect(page.getByText("MON-WO")).toHaveCount(0);
+ await expect(page.getByText("UNSCHED-WO")).toHaveCount(0);
+ await expect(page.getByRole("button", { name: /Unscheduled/ })).toHaveCount(0);
+
+ await page.getByRole("tab", { name: "Mon · 08/17" }).click();
+ await expect(page.getByRole("tab", { name: "Mon · 08/17" })).toHaveAttribute(
+ "aria-selected",
+ "true",
+ );
+ await expect(page.getByText("MON-WO").first()).toBeVisible();
+ await expect(page.getByText("WED-WO")).toHaveCount(0);
+ await expect(page.getByText("UNSCHED-WO")).toHaveCount(0);
+ });
+
+ test("updates day tabs and selects Monday when the week changes", async ({ page }) => {
+ await page.goto("/workorders");
+ await expect(page.getByRole("tab", { name: "Wed · 08/19" })).toBeVisible({ timeout: 15_000 });
+
+ await page.getByRole("button", { name: "Week 8/17–8/21" }).click();
+ await page.getByRole("button", { name: /Aug 10/ }).click();
+
+ await expect(page.getByRole("button", { name: "Week 8/10–8/14" })).toBeVisible();
+ await expect(page.getByRole("tab", { name: "Mon · 08/10" })).toHaveAttribute(
+ "aria-selected",
+ "true",
+ );
+ await expect(page.getByRole("tab", { name: "Fri · 08/14" })).toBeVisible();
+ await expect(page.getByRole("tab", { name: "Mon · 08/17" })).toHaveCount(0);
+ await expect(page.getByText("PREV-MON-WO").first()).toBeVisible();
+ await expect(page.getByText("UNSCHED-WO")).toHaveCount(0);
+ });
+});
diff --git a/src/app/(auth)/_components/auth-brand-logo.tsx b/src/app/(auth)/_components/auth-brand-logo.tsx
new file mode 100644
index 00000000..fd23aac3
--- /dev/null
+++ b/src/app/(auth)/_components/auth-brand-logo.tsx
@@ -0,0 +1,25 @@
+import { useState } from "react";
+
+import { BrandLockup } from "@/components/common/brand-lockup";
+import { cn } from "@/lib/utils";
+
+export type AuthBrandLogoProps = {
+ className?: string;
+};
+
+export function AuthBrandLogo({ className }: AuthBrandLogoProps) {
+ const [useFallback, setUseFallback] = useState(false);
+
+ if (useFallback) {
+ return ;
+ }
+
+ return (
+
setUseFallback(true)}
+ className={cn("mx-auto block h-10 w-auto", className)}
+ />
+ );
+}
diff --git a/src/app/(auth)/_components/auth-card-header.tsx b/src/app/(auth)/_components/auth-card-header.tsx
index aaf95cf6..51d2b147 100644
--- a/src/app/(auth)/_components/auth-card-header.tsx
+++ b/src/app/(auth)/_components/auth-card-header.tsx
@@ -11,14 +11,17 @@ export type AuthCardHeaderProps = ComponentPropsWithoutRef<"div"> & {
export function AuthCardHeader({ title, subtitle, className, ...props }: AuthCardHeaderProps) {
return (
-
+
{title}
{subtitle}
diff --git a/src/app/(auth)/login.tsx b/src/app/(auth)/login.tsx
index 7d1c4c74..a98e5b3d 100644
--- a/src/app/(auth)/login.tsx
+++ b/src/app/(auth)/login.tsx
@@ -1,10 +1,10 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
+import { AuthBrandLogo } from "@/app/(auth)/_components/auth-brand-logo";
import { AuthCardHeader } from "@/app/(auth)/_components/auth-card-header";
import { AuthPageShell } from "@/app/(auth)/_components/auth-page-shell";
import { LoginForm } from "@/app/(auth)/_components/login-form";
-import { BrandLockup } from "@/components/common/brand-lockup";
import { Text } from "@/components/ui/text";
import { loginSchema, type LoginFormValues } from "@/domain/auth/schemas/login-schema";
import { useAuthContext } from "@/providers/auth-context";
@@ -27,7 +27,7 @@ export default function LoginPage() {
return (
-
+
void;
+ disabled?: boolean;
+};
+
+export function DayTabs({ weekMonday, selectedDayIso, onChange, disabled = false }: DayTabsProps) {
+ const days = weekdaysMonFri(weekMonday);
+
+ return (
+
+ {days.map((iso) => {
+ const label = formatDayTabLabel(iso);
+ const active = iso === selectedDayIso;
+ return (
+ onChange(iso)}
+ >
+ {label}
+
+ );
+ })}
+
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/list/filter-bar/week-nav.tsx b/src/app/(protected)/workorders/_components/list/filter-bar/week-nav.tsx
deleted file mode 100644
index ab8fc786..00000000
--- a/src/app/(protected)/workorders/_components/list/filter-bar/week-nav.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import { useEffect, useMemo, useState } from "react";
-
-import { ChevronLeft, ChevronRight } from "lucide-react";
-
-import { NavArrowButton } from "@/components/ui/nav-arrow-button";
-
-import { NavPillButton } from "@/components/ui/nav-pill-button";
-
-import {
- buildWeekWindow,
- isFutureWeek,
- isLiveWeek,
-} from "@/domain/work-orders/utils/list-week-utils";
-
-type WeekNavProps = {
- weekMonday: string;
-
- onChange: (mondayIso: string) => void;
-
- disabled?: boolean;
-};
-
-const VISIBLE_COUNT = 3;
-
-const WINDOW_BEFORE = 12;
-
-const WINDOW_AFTER = 1;
-
-function WeekLiveBadge() {
- return (
-
- Live
-
- );
-}
-
-export function WeekNav({ weekMonday, onChange, disabled = false }: WeekNavProps) {
- const weeks = useMemo(
- () => buildWeekWindow(weekMonday, WINDOW_BEFORE, WINDOW_AFTER),
-
- [weekMonday],
- );
-
- const activeIndex = useMemo(
- () => weeks.findIndex((week) => week.mondayIso === weekMonday),
-
- [weeks, weekMonday],
- );
-
- const [offset, setOffset] = useState(() => Math.max(0, activeIndex >= 0 ? activeIndex - 1 : 0));
-
- useEffect(() => {
- if (activeIndex < 0) return;
-
- const maxOffset = Math.max(0, weeks.length - VISIBLE_COUNT);
-
- setOffset((current) => {
- if (activeIndex < current) return Math.min(activeIndex, maxOffset);
-
- if (activeIndex > current + VISIBLE_COUNT - 1) {
- return Math.min(Math.max(0, activeIndex - (VISIBLE_COUNT - 1)), maxOffset);
- }
-
- return current;
- });
- }, [activeIndex, weeks.length]);
-
- const visible = weeks.slice(offset, offset + VISIBLE_COUNT);
-
- const canGoBack = offset > 0;
-
- const canGoForward = offset + VISIBLE_COUNT < weeks.length;
-
- return (
-
- setOffset((current) => current - 1)}
- aria-label="Previous weeks"
- >
-
-
-
- {visible.map((week) => {
- const isActive = weekMonday === week.mondayIso;
-
- const isDisabled = isFutureWeek(week.mondayIso);
-
- const showLiveBadge = isLiveWeek(week.mondayIso);
-
- return (
- onChange(week.mondayIso)}
- >
- {week.label}
-
- {showLiveBadge && }
-
- );
- })}
-
- setOffset((current) => current + 1)}
- aria-label="Next weeks"
- >
-
-
-
- );
-}
diff --git a/src/app/(protected)/workorders/_components/list/filter-bar/week-selector.tsx b/src/app/(protected)/workorders/_components/list/filter-bar/week-selector.tsx
new file mode 100644
index 00000000..49f985f4
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/list/filter-bar/week-selector.tsx
@@ -0,0 +1,81 @@
+import { Popover } from "@mui/material";
+import { ChevronDown } from "lucide-react";
+import { WeekPicker } from "@/app/(protected)/workorders/_components/wizard/week-picker";
+import { MenuAnchorButton } from "@/components/ui/menu-anchor-button";
+import {
+ compactWeekLabel,
+ isFutureWeek,
+ isLiveWeek,
+} from "@/domain/work-orders/utils/list-week-utils";
+import { useMenuAnchor } from "@/hooks/use-menu-anchor";
+import { cn } from "@/lib/utils";
+
+type WeekSelectorProps = {
+ weekMonday: string;
+ onChange: (mondayIso: string) => void;
+ disabled?: boolean;
+};
+
+function WeekLiveBadge() {
+ return (
+
+ Live
+
+ );
+}
+
+export function WeekSelector({ weekMonday, onChange, disabled = false }: WeekSelectorProps) {
+ const { anchorEl, open, setAnchor, close } = useMenuAnchor();
+ const live = isLiveWeek(weekMonday);
+ const label = compactWeekLabel(weekMonday);
+
+ const handlePick = (mondayIso: string) => {
+ if (isFutureWeek(mondayIso)) {
+ return;
+ }
+ onChange(mondayIso);
+ close();
+ };
+
+ return (
+ <>
+
+ {label}
+ {Boolean(live) && }
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar.tsx b/src/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar.tsx
index 3a1c114f..3f0bdb13 100644
--- a/src/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar.tsx
+++ b/src/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar.tsx
@@ -1,12 +1,16 @@
import type { WorkOrderUser } from "@/domain/work-orders/types/work-order";
import type { WorkOrderTypeFilter } from "@/domain/work-orders/types/work-order-list-filters";
+import { DayTabs } from "@/app/(protected)/workorders/_components/list/filter-bar/day-tabs";
import { DispatcherFilter } from "@/app/(protected)/workorders/_components/list/filter-bar/dispatcher-filter";
-import { WeekNav } from "@/app/(protected)/workorders/_components/list/filter-bar/week-nav";
+import { WeekSelector } from "@/app/(protected)/workorders/_components/list/filter-bar/week-selector";
import { WoTypeSegment } from "@/app/(protected)/workorders/_components/list/filter-bar/wo-type-segment";
+import { Text } from "@/components/ui/text";
type WorkOrdersFilterBarProps = {
weekMonday: string;
onWeekChange: (mondayIso: string) => void;
+ selectedDayIso: string;
+ onSelectedDayChange: (dayIso: string) => void;
dispatcherIds: Set;
onDispatcherChange: (ids: Set) => void;
typeFilter: WorkOrderTypeFilter;
@@ -22,6 +26,8 @@ type WorkOrdersFilterBarProps = {
export function WorkOrdersFilterBar({
weekMonday,
onWeekChange,
+ selectedDayIso,
+ onSelectedDayChange,
dispatcherIds,
onDispatcherChange,
typeFilter,
@@ -37,21 +43,35 @@ export function WorkOrdersFilterBar({
return (
-
-
-
-
-
{resultLabel}
+
+
+
+
+ {resultLabel}
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/app/(protected)/workorders/_components/list/table/use-wo-table-row-handlers.ts b/src/app/(protected)/workorders/_components/list/table/use-wo-table-row-handlers.ts
index 1a6b51c5..aa6b8599 100644
--- a/src/app/(protected)/workorders/_components/list/table/use-wo-table-row-handlers.ts
+++ b/src/app/(protected)/workorders/_components/list/table/use-wo-table-row-handlers.ts
@@ -5,7 +5,6 @@ import type {
} from "@/domain/work-orders/types/work-order-table-row";
import { applySchedulePatch } from "@/domain/work-orders/use-cases/use-work-order-table-mutations";
import { shouldNotifyCrossWeek } from "@/domain/work-orders/utils/should-notify-cross-week";
-import { mondayOf } from "@/domain/work-orders/utils/wizard-date-utils";
import type { ReturnTypeOfUseWorkOrdersTableData } from "./wo-table-types";
import { rescheduleNotify, weekMoveNotify } from "./reschedule-notify";
import type { WoTableRowHandlers } from "./wo-table-row";
@@ -13,7 +12,8 @@ import type { WoTableRowHandlers } from "./wo-table-row";
type UseWoTableRowHandlersOptions = {
tableData: ReturnTypeOfUseWorkOrdersTableData;
weekMonday: string;
- onWeekChange: (mondayIso: string) => void;
+ onNavigateToDay: (dateIso: string) => void;
+ onOpenAdvancedFilters?: () => void;
onOpenSlideOver: (row: WorkOrderTableRow, edit?: boolean) => void;
onOpenCompDoc?: (row: WorkOrderTableRow) => void;
onOpenUplifts?: (row: WorkOrderTableRow) => void;
@@ -26,7 +26,8 @@ type UseWoTableRowHandlersOptions = {
export function useWoTableRowHandlers({
tableData,
weekMonday,
- onWeekChange,
+ onNavigateToDay,
+ onOpenAdvancedFilters,
onOpenSlideOver,
onOpenCompDoc,
onOpenUplifts,
@@ -35,14 +36,7 @@ export function useWoTableRowHandlers({
onScheduleHighlight,
onDuplicateFound,
}: UseWoTableRowHandlersOptions): WoTableRowHandlers {
- const {
- dragState: dragStateRef,
- setDragViz,
- reorderRows,
- endDrag,
- setUnschedOpen,
- triggerFlash,
- } = tableData;
+ const { dragState: dragStateRef, setDragViz, reorderRows, endDrag, triggerFlash } = tableData;
return useMemo(
() => ({
@@ -80,14 +74,16 @@ export function useWoTableRowHandlers({
onScheduleDate: (id: string | number, date: string) => {
onPatch(id, applySchedulePatch(date, false));
if (shouldNotifyCrossWeek(date, weekMonday)) {
- const targetMonday = mondayOf(date);
rescheduleNotify(date, {
onNavigate: () => {
- onWeekChange(targetMonday);
+ onNavigateToDay(date);
setTimeout(() => triggerFlash(id), 180);
},
});
+ return;
}
+ onNavigateToDay(date);
+ triggerFlash(id);
},
onScheduleWeek: (id: string | number, week: string) => {
onPatch(id, {
@@ -97,12 +93,10 @@ export function useWoTableRowHandlers({
dayGroup: "",
dayLabel: "",
});
- setUnschedOpen(true);
- triggerFlash(id);
weekMoveNotify(week, {
onNavigate: () => {
- setUnschedOpen(true);
- setTimeout(() => triggerFlash(id), 180);
+ onNavigateToDay(week);
+ onOpenAdvancedFilters?.();
},
});
},
@@ -138,10 +132,10 @@ export function useWoTableRowHandlers({
setDragViz,
reorderRows,
endDrag,
- setUnschedOpen,
triggerFlash,
weekMonday,
- onWeekChange,
+ onNavigateToDay,
+ onOpenAdvancedFilters,
onOpenSlideOver,
onOpenCompDoc,
onOpenUplifts,
diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-body.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-body.tsx
index 7e51c960..270943a4 100644
--- a/src/app/(protected)/workorders/_components/list/table/wo-table-body.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/wo-table-body.tsx
@@ -2,7 +2,6 @@ import { Fragment, type ReactNode } from "react";
import { Text } from "@/components/ui/text";
import {
UNASSIGNED_DAY_KEY,
- UNSCHEDULED_DAY_KEY,
type WorkOrderTableRow,
} from "@/domain/work-orders/types/work-order-table-row";
import type { ReturnTypeOfUseWorkOrdersTableData } from "./wo-table-types";
@@ -21,11 +20,6 @@ export function WoTableBody({ tableData, renderRow, renderInlineRow }: WoTableBo
}
const unassignedRows = tableData.unassigned ?? [];
- const showUnscheduled =
- (tableData.unscheduled?.length ?? 0) > 0 || tableData.draftDayKey === UNSCHEDULED_DAY_KEY;
- // SH-165: "still loading" and "zero unassigned" are distinct states. Without
- // this split the pin announced "No unassigned work orders." while the queue
- // was still paging board/search on first mount.
const showUnassignedLoading =
tableData.unassignedOpen &&
unassignedRows.length === 0 &&
@@ -79,24 +73,6 @@ export function WoTableBody({ tableData, renderRow, renderInlineRow }: WoTableBo
{unassignedRows.map((row) => renderRow(row))}
>
)}
- {showUnscheduled && (
- <>
- tableData.setUnschedOpen((v) => !v)}
- onAddWo={() => tableData.startDayDraft(UNSCHEDULED_DAY_KEY)}
- />
- {tableData.unschedOpen && (
- <>
- {renderInlineRow(UNSCHEDULED_DAY_KEY)}
- {tableData.unscheduled.map((row) => renderRow(row, true))}
- >
- )}
- >
- )}
{tableData.displayGroups.map((group) => (
void;
+ onNavigateToDay: (dateIso: string) => void;
onOpenSlideOver: (row: WorkOrderTableRow, edit?: boolean) => void;
/** Omit until completion-doc slice mounts CompDocDialog. */
onOpenCompDoc?: (row: WorkOrderTableRow) => void;
@@ -40,6 +41,34 @@ type WoTableProps = {
onPatch: (id: string | number, patch: WorkOrderTablePatch) => void;
};
+type WoTableEmptyShellProps = {
+ message: string;
+ actionLabel: string;
+ onAction?: () => void;
+};
+
+function WoTableLoading({
+ weekMonday,
+ selectedDayIso,
+}: {
+ weekMonday: string;
+ selectedDayIso?: string;
+}) {
+ return (
+
+
+
+ );
+}
+
+function WoTableEmptyShell({ message, actionLabel, onAction }: WoTableEmptyShellProps) {
+ return (
+
+
+
+ );
+}
+
export function WoTable({
tableData,
users,
@@ -47,7 +76,7 @@ export function WoTable({
vendors,
pmTypes,
weekMonday,
- onWeekChange,
+ onNavigateToDay,
onOpenSlideOver,
onOpenCompDoc,
onOpenUplifts,
@@ -70,7 +99,8 @@ export function WoTable({
const handlers = useWoTableRowHandlers({
tableData,
weekMonday,
- onWeekChange,
+ onNavigateToDay,
+ onOpenAdvancedFilters,
onOpenSlideOver,
onOpenCompDoc,
onOpenUplifts,
@@ -125,9 +155,7 @@ export function WoTable({
if (tableData.isLoading) {
return (
-
-
-
+
);
}
@@ -135,25 +163,26 @@ export function WoTable({
if (emptyAdv) {
return (
-
-
-
+
);
}
- if (!tableData.advActive && tableData.noSearchResults) {
+ const showSearchEmpty =
+ !tableData.advActive &&
+ tableData.noSearchResults &&
+ isUnassignedQueueReady(tableData.unassignedLoading, tableData.unassignedError);
+
+ if (showSearchEmpty) {
return (
-
-
-
+
);
}
diff --git a/src/app/(protected)/workorders/_components/loading/wo-table-skeleton.tsx b/src/app/(protected)/workorders/_components/loading/wo-table-skeleton.tsx
index 85cee325..d5c3e8fa 100644
--- a/src/app/(protected)/workorders/_components/loading/wo-table-skeleton.tsx
+++ b/src/app/(protected)/workorders/_components/loading/wo-table-skeleton.tsx
@@ -1,7 +1,7 @@
import { Skeleton } from "@mui/material";
import { LoadingRegion } from "@/components/ui/loading-region";
import { TableSkeletonRows } from "@/components/ui/table-skeleton-rows";
-import { buildWeekSkeleton } from "@/domain/work-orders/utils/table-day-utils";
+import { formatDayTabLabel, weekdaysMonFri } from "@/domain/work-orders/utils/week-day-nav-utils";
import { WoTableHead } from "@/app/(protected)/workorders/_components/list/table/wo-table-head";
import {
WO_TABLE_COLS,
@@ -11,13 +11,26 @@ import {
type WoTableSkeletonProps = {
weekMonday: string;
+ selectedDayIso?: string;
rowsPerDay?: number;
};
const SKELETON_COLS = WO_TABLE_COLS.map((col) => ({ key: col.key, width: col.w }));
-export function WoTableSkeleton({ weekMonday, rowsPerDay = 2 }: WoTableSkeletonProps) {
- const days = buildWeekSkeleton(weekMonday);
+function skeletonDayIso(weekMonday: string, selectedDayIso?: string): string {
+ const days = weekdaysMonFri(weekMonday);
+ if (selectedDayIso && days.includes(selectedDayIso)) {
+ return selectedDayIso;
+ }
+ return weekMonday;
+}
+
+export function WoTableSkeleton({
+ weekMonday,
+ selectedDayIso,
+ rowsPerDay = 2,
+}: WoTableSkeletonProps) {
+ const dayIso = skeletonDayIso(weekMonday, selectedDayIso);
return (
@@ -30,9 +43,8 @@ export function WoTableSkeleton({ weekMonday, rowsPerDay = 2 }: WoTableSkeletonP
>
- {days.map((day) => (
-
- ))}
+
+
diff --git a/src/app/(protected)/workorders/_components/wizard/week-picker.tsx b/src/app/(protected)/workorders/_components/wizard/week-picker.tsx
index 233939b0..faf128b1 100644
--- a/src/app/(protected)/workorders/_components/wizard/week-picker.tsx
+++ b/src/app/(protected)/workorders/_components/wizard/week-picker.tsx
@@ -11,9 +11,10 @@ import {
type WeekPickerProps = {
selected: string;
onPick: (mondayIso: string) => void;
+ isWeekDisabled?: (mondayIso: string) => boolean;
};
-export function WeekPicker({ selected, onPick }: WeekPickerProps) {
+export function WeekPicker({ selected, onPick, isWeekDisabled }: WeekPickerProps) {
const init = /^\d{4}-\d{2}-\d{2}$/.test(selected) ? selected : todayIso();
const [iy, im] = init.split("-").map(Number);
const [viewY, setViewY] = useState(iy);
@@ -66,12 +67,14 @@ export function WeekPicker({ selected, onPick }: WeekPickerProps) {
{weeks.map((mon) => {
const isSel = Boolean(selected) && selected === mon;
const isThis = mon === thisMonday;
+ const isDisabled = Boolean(isWeekDisabled?.(mon));
return (