Compare commits
No commits in common. "e33f513975dc10c23a89ea8717cbea47ac3668cb" and "e323b0b2ce29f5f11e2b213b9fc59bf8bd74237e" have entirely different histories.
e33f513975
...
e323b0b2ce
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 185 KiB |
|
|
@ -204,12 +204,6 @@ async function openWorkOrderPage(page: Page, mode: "default" | "empty" | "error"
|
|||
});
|
||||
}
|
||||
|
||||
async function expectWorkOrderPageReady(page: Page) {
|
||||
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
|
||||
timeout: 90_000,
|
||||
});
|
||||
}
|
||||
|
||||
async function expectStableScreenshot(page: Page, name: string) {
|
||||
await page.waitForTimeout(250);
|
||||
await page.evaluate(
|
||||
|
|
@ -223,18 +217,15 @@ async function expectStableScreenshot(page: Page, name: string) {
|
|||
}
|
||||
|
||||
test.describe("Work Orders deterministic pixel regression", () => {
|
||||
test.setTimeout(120_000);
|
||||
|
||||
test("list", async ({ page }) => {
|
||||
await openWorkOrderPage(page);
|
||||
await expectWorkOrderPageReady(page);
|
||||
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible();
|
||||
await expect(page.getByText("WO-501").first()).toBeVisible();
|
||||
await expectStableScreenshot(page, "wo-list.png");
|
||||
});
|
||||
|
||||
test("filters", async ({ page }) => {
|
||||
await openWorkOrderPage(page);
|
||||
await expectWorkOrderPageReady(page);
|
||||
await page.getByRole("button", { name: "Advanced Filters" }).click();
|
||||
await expect(page.getByRole("dialog", { name: "Advanced Filters" })).toBeVisible();
|
||||
await expectStableScreenshot(page, "wo-filters.png");
|
||||
|
|
@ -242,7 +233,6 @@ test.describe("Work Orders deterministic pixel regression", () => {
|
|||
|
||||
test("new", async ({ page }) => {
|
||||
await openWorkOrderPage(page);
|
||||
await expectWorkOrderPageReady(page);
|
||||
await page.getByRole("button", { name: "New WO" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Type & schedule" })).toBeVisible();
|
||||
await expectStableScreenshot(page, "wo-new.png");
|
||||
|
|
@ -250,7 +240,6 @@ test.describe("Work Orders deterministic pixel regression", () => {
|
|||
|
||||
test("detail", async ({ page }) => {
|
||||
await openWorkOrderPage(page);
|
||||
await expectWorkOrderPageReady(page);
|
||||
const row = page.locator("#wo-row-1");
|
||||
await row.hover();
|
||||
await row.getByRole("button", { name: "View details" }).click();
|
||||
|
|
@ -260,7 +249,7 @@ test.describe("Work Orders deterministic pixel regression", () => {
|
|||
|
||||
test("empty", async ({ page }) => {
|
||||
await openWorkOrderPage(page, "empty");
|
||||
await expectWorkOrderPageReady(page);
|
||||
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible();
|
||||
await page.getByLabel("Search work orders").fill("zz");
|
||||
await expect(page.getByText("No work orders match your search")).toBeVisible();
|
||||
await expectStableScreenshot(page, "wo-empty.png");
|
||||
|
|
@ -268,7 +257,7 @@ test.describe("Work Orders deterministic pixel regression", () => {
|
|||
|
||||
test("error", async ({ page }) => {
|
||||
await openWorkOrderPage(page, "error");
|
||||
await expectWorkOrderPageReady(page);
|
||||
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible();
|
||||
const alert = page.getByRole("main").getByRole("alert");
|
||||
await expect(alert).toBeVisible();
|
||||
await expect(alert).toContainText(/server error/i);
|
||||
|
|
@ -278,7 +267,6 @@ test.describe("Work Orders deterministic pixel regression", () => {
|
|||
test("mobile", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await openWorkOrderPage(page);
|
||||
await expectWorkOrderPageReady(page);
|
||||
await expect(page.getByText("WO-501").first()).toBeVisible();
|
||||
await expectStableScreenshot(page, "wo-mobile.png");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type Dispatch,
|
||||
type ReactNode,
|
||||
|
|
@ -265,20 +264,14 @@ function useTeamMemberEditor(
|
|||
const [form, setForm] = useState<TeamMemberForm>(emptyForm);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const updateTeamMember = useUpdateTeamMember();
|
||||
const currentMemberRef = useRef(currentMember);
|
||||
|
||||
useEffect(() => {
|
||||
currentMemberRef.current = currentMember;
|
||||
}, [currentMember]);
|
||||
|
||||
useEffect(() => {
|
||||
const member = currentMemberRef.current;
|
||||
if (member) {
|
||||
setForm(formFromMember(member));
|
||||
if (currentMember) {
|
||||
setForm(formFromMember(currentMember));
|
||||
setEditing(false);
|
||||
setSubmitted(false);
|
||||
}
|
||||
}, [currentMember?.id]);
|
||||
}, [currentMember]);
|
||||
|
||||
useEffect(() => {
|
||||
setEditing(false);
|
||||
|
|
@ -358,11 +351,7 @@ function useTeamMemberEditor(
|
|||
toggleArea,
|
||||
togglePermission,
|
||||
save,
|
||||
cancel: () => {
|
||||
if (currentMember) setForm(formFromMember(currentMember));
|
||||
setSubmitted(false);
|
||||
setEditing(false);
|
||||
},
|
||||
cancel: () => setEditing(false),
|
||||
beginEdit: () => setEditing(true),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,11 @@ function memberStatusLabel(member: TeamMemberListItem) {
|
|||
export function TeamMembersTable({
|
||||
rows,
|
||||
isLoading,
|
||||
hasError,
|
||||
tab,
|
||||
onOpen,
|
||||
}: {
|
||||
rows: TeamMemberListItem[];
|
||||
isLoading: boolean;
|
||||
hasError: boolean;
|
||||
tab: "active" | "inactive";
|
||||
onOpen: (member: TeamMemberListItem) => void;
|
||||
}) {
|
||||
|
|
@ -54,15 +52,6 @@ export function TeamMembersTable({
|
|||
<CircularProgress size={28} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : hasError ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center" sx={{ py: 8 }}>
|
||||
<Typography sx={{ fontWeight: 600 }}>Unable to load team members</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Check your connection and try again.
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : rows.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center" sx={{ py: 8 }}>
|
||||
|
|
|
|||
|
|
@ -85,13 +85,7 @@ export default function TeamMembersListPage() {
|
|||
)}
|
||||
|
||||
<Box sx={{ flex: 1, overflow: "auto", p: 2 }}>
|
||||
<TeamMembersTable
|
||||
rows={rows}
|
||||
isLoading={isLoading}
|
||||
hasError={Boolean(error)}
|
||||
tab={tab}
|
||||
onOpen={setSelectedMember}
|
||||
/>
|
||||
<TeamMembersTable rows={rows} isLoading={isLoading} tab={tab} onOpen={setSelectedMember} />
|
||||
</Box>
|
||||
|
||||
<Stack
|
||||
|
|
|
|||
|
|
@ -118,44 +118,4 @@ describe("TeamMemberDetailDrawer", () => {
|
|||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps edits when the detail query refreshes", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { rerender } = renderWithProviders(
|
||||
<TeamMemberDetailDrawer member={member} onClose={vi.fn()} />,
|
||||
{ withAuth: false },
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Edit" }));
|
||||
const name = screen.getByRole("textbox", { name: "Name" });
|
||||
await user.clear(name);
|
||||
await user.type(name, "Taylor Updated");
|
||||
|
||||
vi.mocked(useTeamMemberDetail).mockReturnValue({
|
||||
data: { ...detail, phone: "555-0101" },
|
||||
isLoading: false,
|
||||
error: null,
|
||||
} as unknown as ReturnType<typeof useTeamMemberDetail>);
|
||||
rerender(<TeamMemberDetailDrawer member={member} onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.getByRole("textbox", { name: "Name" })).toHaveValue("Taylor Updated");
|
||||
expect(screen.getByRole("textbox", { name: "Phone" })).toHaveValue("555-0100");
|
||||
});
|
||||
|
||||
it("restores the saved detail when editing is cancelled", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<TeamMemberDetailDrawer member={member} onClose={vi.fn()} />, {
|
||||
withAuth: false,
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Edit" }));
|
||||
const name = screen.getByRole("textbox", { name: "Name" });
|
||||
await user.clear(name);
|
||||
await user.type(name, "Discarded draft");
|
||||
await user.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
expect(screen.getByRole("button", { name: "Edit" })).toBeInTheDocument();
|
||||
expect(screen.getAllByText("Taylor Dispatcher")).not.toHaveLength(0);
|
||||
expect(screen.queryByDisplayValue("Discarded draft")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||