mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 15:09:02 +00:00
fix(work-orders): align sidebar and advanced filters with AAP (#185)
Some checks are pending
Some checks are pending
* fix(work-orders): align sidebar and advanced filters with AAP * test(vendors): scope Site locator exactly * fix(work-orders): keep sidebar scope unchanged * test(work-orders): refresh SH-220 filter baseline --------- Co-authored-by: Codex Review Integration <codex-review@local.invalid>
This commit is contained in:
parent
d88a2e7854
commit
da86297fb4
9 changed files with 87 additions and 21 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 144 KiB |
|
|
@ -13,11 +13,7 @@ import {
|
||||||
import { AdvDocPill, AdvStatusPill } from "./adv-filter-pill";
|
import { AdvDocPill, AdvStatusPill } from "./adv-filter-pill";
|
||||||
import { AdvDateRangeSection } from "./adv-date-range-section";
|
import { AdvDateRangeSection } from "./adv-date-range-section";
|
||||||
import { AdvMultiSelect } from "./adv-multi-select";
|
import { AdvMultiSelect } from "./adv-multi-select";
|
||||||
import {
|
import { AdvFlagInternalUpliftSections, AdvIndicatorsSection } from "./adv-parity-filter-sections";
|
||||||
AdvAvetaSection,
|
|
||||||
AdvFlagInternalUpliftSections,
|
|
||||||
AdvIndicatorsSection,
|
|
||||||
} from "./adv-parity-filter-sections";
|
|
||||||
import { AdvSection } from "./adv-section";
|
import { AdvSection } from "./adv-section";
|
||||||
import { AdvSeverityList } from "./adv-severity-list";
|
import { AdvSeverityList } from "./adv-severity-list";
|
||||||
import { AdvTypeGrid } from "./adv-type-grid";
|
import { AdvTypeGrid } from "./adv-type-grid";
|
||||||
|
|
@ -131,8 +127,6 @@ export function AdvancedSearchFilterSections({
|
||||||
|
|
||||||
<AdvIndicatorsSection draft={draft} setDraft={setDraft} />
|
<AdvIndicatorsSection draft={draft} setDraft={setDraft} />
|
||||||
|
|
||||||
<AdvAvetaSection draft={draft} setDraft={setDraft} />
|
|
||||||
|
|
||||||
<AdvSection icon={Flame} label="Service" count={draft.pmTypes.size}>
|
<AdvSection icon={Flame} label="Service" count={draft.pmTypes.size}>
|
||||||
<AdvMultiSelect
|
<AdvMultiSelect
|
||||||
options={pmTypes.map((pmType) => ({ value: pmType, label: pmType }))}
|
options={pmTypes.map((pmType) => ({ value: pmType, label: pmType }))}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,12 @@ export function AdvIndicatorsSection({ draft, setDraft }: DraftProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FILTER_TOGGLE_CHECKBOX_SX = {
|
||||||
|
padding: 0,
|
||||||
|
color: "var(--color-border-hover)",
|
||||||
|
"&.Mui-checked": { color: "var(--color-primary)" },
|
||||||
|
} as const;
|
||||||
|
|
||||||
export function AdvAvetaSection({ draft, setDraft }: DraftProps) {
|
export function AdvAvetaSection({ draft, setDraft }: DraftProps) {
|
||||||
return (
|
return (
|
||||||
<AdvSection icon={ShieldCheck} label="Aveta Compliance" count={draft.avetaOnly ? 1 : 0}>
|
<AdvSection icon={ShieldCheck} label="Aveta Compliance" count={draft.avetaOnly ? 1 : 0}>
|
||||||
|
|
@ -133,11 +139,7 @@ export function AdvAvetaSection({ draft, setDraft }: DraftProps) {
|
||||||
size="small"
|
size="small"
|
||||||
checked={draft.avetaOnly}
|
checked={draft.avetaOnly}
|
||||||
onChange={() => setDraft((prev) => ({ ...prev, avetaOnly: !prev.avetaOnly }))}
|
onChange={() => setDraft((prev) => ({ ...prev, avetaOnly: !prev.avetaOnly }))}
|
||||||
sx={{
|
sx={FILTER_TOGGLE_CHECKBOX_SX}
|
||||||
padding: 0,
|
|
||||||
color: "var(--color-border-hover)",
|
|
||||||
"&.Mui-checked": { color: "var(--warning)" },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-muted-foreground">Aveta required only</span>
|
<span className="text-sm text-muted-foreground">Aveta required only</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -195,6 +197,8 @@ export function AdvFlagInternalUpliftSections({
|
||||||
</button>
|
</button>
|
||||||
</AdvSection>
|
</AdvSection>
|
||||||
|
|
||||||
|
<AdvAvetaSection draft={draft} setDraft={setDraft} />
|
||||||
|
|
||||||
<AdvSection icon={DollarSign} label="Uplift" count={draft.hasUplift ? 1 : 0}>
|
<AdvSection icon={DollarSign} label="Uplift" count={draft.hasUplift ? 1 : 0}>
|
||||||
<label className="flex cursor-pointer items-center gap-1.5 select-none">
|
<label className="flex cursor-pointer items-center gap-1.5 select-none">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
@ -207,11 +211,7 @@ export function AdvFlagInternalUpliftSections({
|
||||||
upliftStatuses: prev.hasUplift ? new Set() : prev.upliftStatuses,
|
upliftStatuses: prev.hasUplift ? new Set() : prev.upliftStatuses,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
sx={{
|
sx={FILTER_TOGGLE_CHECKBOX_SX}
|
||||||
padding: 0,
|
|
||||||
color: "var(--color-border-hover)",
|
|
||||||
"&.Mui-checked": { color: "var(--color-primary)" },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-muted-foreground">Has uplift</span>
|
<span className="text-sm text-muted-foreground">Has uplift</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,17 @@ export function useAdvancedSearchDraft({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearAll = () => {
|
const handleClearAll = () => {
|
||||||
|
if (activeCount === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setDraft(cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS));
|
setDraft(cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS));
|
||||||
onClearAll();
|
onClearAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleApply = () => {
|
const handleApply = () => {
|
||||||
|
if (activeCount === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onApply(cloneAdvancedFilters(draft));
|
onApply(cloneAdvancedFilters(draft));
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,12 @@ export function WorkOrdersAdvancedSearchSheet({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex shrink-0 items-center gap-2 border-t border-border px-4 py-3">
|
<div className="flex shrink-0 items-center gap-2 border-t border-border px-4 py-3">
|
||||||
<Button variant="ghost" className="text-xs" onClick={handleClearAll}>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="text-xs"
|
||||||
|
disabled={activeCount === 0}
|
||||||
|
onClick={handleClearAll}
|
||||||
|
>
|
||||||
Clear all
|
Clear all
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex flex-1 justify-center">
|
<div className="flex flex-1 justify-center">
|
||||||
|
|
@ -74,7 +79,7 @@ export function WorkOrdersAdvancedSearchSheet({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button className="px-5 text-xs" onClick={handleApply}>
|
<Button className="px-5 text-xs" disabled={activeCount === 0} onClick={handleApply}>
|
||||||
Apply filters
|
Apply filters
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ export function CollapsibleSection({
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{Icon != null && <Icon size={16} className="text-primary" />}
|
{Icon != null && <Icon size={16} className="text-primary" />}
|
||||||
<span className="text-sm font-semibold text-foreground">{label}</span>
|
<span className="text-[length:var(--text-base)] font-semibold text-foreground">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CountBadge count={count} />
|
<CountBadge count={count} />
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,8 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
|
||||||
expect(within(sheet).getByText("HVAC")).toBeInTheDocument();
|
expect(within(sheet).getByText("HVAC")).toBeInTheDocument();
|
||||||
expect(within(sheet).getByText("Vendor Co")).toBeInTheDocument();
|
expect(within(sheet).getByText("Vendor Co")).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(within(sheet).getByRole("button", { name: /Aveta Compliance/i }));
|
||||||
|
fireEvent.click(within(sheet).getByRole("checkbox", { name: /Aveta required only/i }));
|
||||||
fireEvent.click(within(sheet).getByRole("button", { name: /apply filters/i }));
|
fireEvent.click(within(sheet).getByRole("button", { name: /apply filters/i }));
|
||||||
expect(applyAdvancedFilters).toHaveBeenCalled();
|
expect(applyAdvancedFilters).toHaveBeenCalled();
|
||||||
expect(setAdvOpen).toHaveBeenCalledWith(false);
|
expect(setAdvOpen).toHaveBeenCalledWith(false);
|
||||||
|
|
@ -341,6 +343,45 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
|
||||||
expect(screen.getByRole("button", { name: /work order types, all types/i })).toBeEnabled();
|
expect(screen.getByRole("button", { name: /work order types, all types/i })).toBeEnabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("places Aveta Compliance immediately above Uplift", () => {
|
||||||
|
const view = renderWithProviders(<WorkOrdersListPage />, {
|
||||||
|
route: "/workorders",
|
||||||
|
withAuth: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Advanced Filters" }));
|
||||||
|
advOpen = true;
|
||||||
|
view.rerender(<WorkOrdersListPage />);
|
||||||
|
|
||||||
|
const sheet = screen.getByRole("dialog", { name: "Advanced Filters" });
|
||||||
|
const aveta = within(sheet).getByRole("button", { name: /Aveta Compliance/i });
|
||||||
|
const uplift = within(sheet).getByRole("button", { name: /^Uplift/i });
|
||||||
|
expect(aveta.closest(".border-b")?.nextElementSibling).toBe(uplift.closest(".border-b"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps Clear all and Apply filters disabled until a filter is selected", () => {
|
||||||
|
const view = renderWithProviders(<WorkOrdersListPage />, {
|
||||||
|
route: "/workorders",
|
||||||
|
withAuth: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Advanced Filters" }));
|
||||||
|
advOpen = true;
|
||||||
|
view.rerender(<WorkOrdersListPage />);
|
||||||
|
|
||||||
|
const sheet = screen.getByRole("dialog", { name: "Advanced Filters" });
|
||||||
|
const clearAll = within(sheet).getByRole("button", { name: /clear all/i });
|
||||||
|
const apply = within(sheet).getByRole("button", { name: /apply filters/i });
|
||||||
|
expect(clearAll).toBeDisabled();
|
||||||
|
expect(apply).toBeDisabled();
|
||||||
|
|
||||||
|
fireEvent.click(within(sheet).getByRole("button", { name: /Aveta Compliance/i }));
|
||||||
|
fireEvent.click(within(sheet).getByRole("checkbox", { name: /Aveta required only/i }));
|
||||||
|
|
||||||
|
expect(clearAll).toBeEnabled();
|
||||||
|
expect(apply).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
it("applies Aveta required only through Advanced Filters", () => {
|
it("applies Aveta required only through Advanced Filters", () => {
|
||||||
const view = renderWithProviders(<WorkOrdersListPage />, {
|
const view = renderWithProviders(<WorkOrdersListPage />, {
|
||||||
route: "/workorders",
|
route: "/workorders",
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,9 @@ describe("WorkOrdersListHeader slice affordances", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: /new wo/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /new wo/i })).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText("Advanced Filters")).toBeInTheDocument();
|
const filters = screen.getByRole("button", { name: "Advanced Filters" });
|
||||||
|
expect(filters).toBeInTheDocument();
|
||||||
|
expect(filters).toHaveTextContent("Filters");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
16
src/test/components/ui/collapsible-section.test.tsx
Normal file
16
src/test/components/ui/collapsible-section.test.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { screen } from "@testing-library/react";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { CollapsibleSection } from "@/components/ui/collapsible-section";
|
||||||
|
import { renderWithProviders } from "@/test/test-utils";
|
||||||
|
|
||||||
|
describe("CollapsibleSection", () => {
|
||||||
|
it("renders the section title at the 14px body size token", () => {
|
||||||
|
renderWithProviders(<CollapsibleSection label="Aveta Compliance">child</CollapsibleSection>, {
|
||||||
|
withAuth: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const title = screen.getByRole("button", { name: /Aveta Compliance/i }).querySelector("span");
|
||||||
|
expect(title).toHaveClass("text-[length:var(--text-base)]");
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue