mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 09:13:11 +00:00
Merge remote-tracking branch 'origin/dev' into feat/ab/sh-281-company-directory
This commit is contained in:
commit
c8c7533ee7
11 changed files with 94 additions and 32 deletions
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
|
|
@ -83,11 +83,12 @@ jobs:
|
|||
echo "prefix=${prefix}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Sentry release is shoc-frontend@${GITHUB_SHA} via VITE_APP_COMMIT_SHA,
|
||||
# distinct from the S3/Terraform version_label.
|
||||
- name: Upload private source maps
|
||||
run: bash scripts/upload-sourcemaps.sh
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_RELEASE: ${{ steps.release.outputs.version_label }}
|
||||
|
||||
- name: Read previous release pointer
|
||||
id: pointer
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 144 KiB |
|
|
@ -6,18 +6,13 @@ set -euo pipefail
|
|||
SENTRY_ORG="${SENTRY_ORG:-seahaven}"
|
||||
SENTRY_PROJECT="${SENTRY_PROJECT:-shoc-frontend}"
|
||||
COMMIT_SHA="${VITE_APP_COMMIT_SHA:-${GITHUB_SHA:-}}"
|
||||
RELEASE_LABEL="${SENTRY_RELEASE:-${RELEASE_LABEL:-}}"
|
||||
|
||||
if [[ -n "${RELEASE_LABEL}" ]]; then
|
||||
RELEASE="${RELEASE_LABEL}"
|
||||
else
|
||||
if [[ ! "${COMMIT_SHA}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "::error::Source-map upload requires a 40-character VITE_APP_COMMIT_SHA or GITHUB_SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
COMMIT_SHA="$(printf '%s' "${COMMIT_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
RELEASE="shoc-frontend@${COMMIT_SHA}"
|
||||
if [[ ! "${COMMIT_SHA}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "::error::Source-map upload requires a 40-character VITE_APP_COMMIT_SHA or GITHUB_SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
COMMIT_SHA="$(printf '%s' "${COMMIT_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
RELEASE="shoc-frontend@${COMMIT_SHA}"
|
||||
|
||||
npm exec --no -- sentry-cli sourcemaps upload \
|
||||
--org "${SENTRY_ORG}" \
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@ import {
|
|||
import { AdvDocPill, AdvStatusPill } from "./adv-filter-pill";
|
||||
import { AdvDateRangeSection } from "./adv-date-range-section";
|
||||
import { AdvMultiSelect } from "./adv-multi-select";
|
||||
import {
|
||||
AdvAvetaSection,
|
||||
AdvFlagInternalUpliftSections,
|
||||
AdvIndicatorsSection,
|
||||
} from "./adv-parity-filter-sections";
|
||||
import { AdvFlagInternalUpliftSections, AdvIndicatorsSection } from "./adv-parity-filter-sections";
|
||||
import { AdvSection } from "./adv-section";
|
||||
import { AdvSeverityList } from "./adv-severity-list";
|
||||
import { AdvTypeGrid } from "./adv-type-grid";
|
||||
|
|
@ -131,8 +127,6 @@ export function AdvancedSearchFilterSections({
|
|||
|
||||
<AdvIndicatorsSection draft={draft} setDraft={setDraft} />
|
||||
|
||||
<AdvAvetaSection draft={draft} setDraft={setDraft} />
|
||||
|
||||
<AdvSection icon={Flame} label="Service" count={draft.pmTypes.size}>
|
||||
<AdvMultiSelect
|
||||
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) {
|
||||
return (
|
||||
<AdvSection icon={ShieldCheck} label="Aveta Compliance" count={draft.avetaOnly ? 1 : 0}>
|
||||
|
|
@ -133,11 +139,7 @@ export function AdvAvetaSection({ draft, setDraft }: DraftProps) {
|
|||
size="small"
|
||||
checked={draft.avetaOnly}
|
||||
onChange={() => setDraft((prev) => ({ ...prev, avetaOnly: !prev.avetaOnly }))}
|
||||
sx={{
|
||||
padding: 0,
|
||||
color: "var(--color-border-hover)",
|
||||
"&.Mui-checked": { color: "var(--warning)" },
|
||||
}}
|
||||
sx={FILTER_TOGGLE_CHECKBOX_SX}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">Aveta required only</span>
|
||||
</label>
|
||||
|
|
@ -195,6 +197,8 @@ export function AdvFlagInternalUpliftSections({
|
|||
</button>
|
||||
</AdvSection>
|
||||
|
||||
<AdvAvetaSection draft={draft} setDraft={setDraft} />
|
||||
|
||||
<AdvSection icon={DollarSign} label="Uplift" count={draft.hasUplift ? 1 : 0}>
|
||||
<label className="flex cursor-pointer items-center gap-1.5 select-none">
|
||||
<Checkbox
|
||||
|
|
@ -207,11 +211,7 @@ export function AdvFlagInternalUpliftSections({
|
|||
upliftStatuses: prev.hasUplift ? new Set() : prev.upliftStatuses,
|
||||
}))
|
||||
}
|
||||
sx={{
|
||||
padding: 0,
|
||||
color: "var(--color-border-hover)",
|
||||
"&.Mui-checked": { color: "var(--color-primary)" },
|
||||
}}
|
||||
sx={FILTER_TOGGLE_CHECKBOX_SX}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">Has uplift</span>
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -55,11 +55,17 @@ export function useAdvancedSearchDraft({
|
|||
};
|
||||
|
||||
const handleClearAll = () => {
|
||||
if (activeCount === 0) {
|
||||
return;
|
||||
}
|
||||
setDraft(cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS));
|
||||
onClearAll();
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
if (activeCount === 0) {
|
||||
return;
|
||||
}
|
||||
onApply(cloneAdvancedFilters(draft));
|
||||
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">
|
||||
<Button variant="ghost" className="text-xs" onClick={handleClearAll}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-xs"
|
||||
disabled={activeCount === 0}
|
||||
onClick={handleClearAll}
|
||||
>
|
||||
Clear all
|
||||
</Button>
|
||||
<div className="flex flex-1 justify-center">
|
||||
|
|
@ -74,7 +79,7 @@ export function WorkOrdersAdvancedSearchSheet({
|
|||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Button className="px-5 text-xs" onClick={handleApply}>
|
||||
<Button className="px-5 text-xs" disabled={activeCount === 0} onClick={handleApply}>
|
||||
Apply filters
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ export function CollapsibleSection({
|
|||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{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 className="flex items-center gap-2">
|
||||
<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("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 }));
|
||||
expect(applyAdvancedFilters).toHaveBeenCalled();
|
||||
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();
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const view = renderWithProviders(<WorkOrdersListPage />, {
|
||||
route: "/workorders",
|
||||
|
|
|
|||
|
|
@ -211,7 +211,9 @@ describe("WorkOrdersListHeader slice affordances", () => {
|
|||
);
|
||||
|
||||
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