mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 12:22:03 +00:00
fix(uplifts): show an unavailable count pill while a queue is loading or failed
This commit is contained in:
parent
70e52937aa
commit
70501693c8
2 changed files with 49 additions and 13 deletions
|
|
@ -18,6 +18,7 @@ import { useAuthContext } from "@/providers/auth-context";
|
||||||
import { useUpliftApprovalController } from "@/domain/uplifts/use-cases/use-uplift-approval-controller";
|
import { useUpliftApprovalController } from "@/domain/uplifts/use-cases/use-uplift-approval-controller";
|
||||||
import { useUpliftsQueue } from "@/domain/uplifts/use-cases/use-uplifts-queue";
|
import { useUpliftsQueue } from "@/domain/uplifts/use-cases/use-uplifts-queue";
|
||||||
import { Text } from "@/components/ui/text";
|
import { Text } from "@/components/ui/text";
|
||||||
|
import type { UpliftsQueueResult } from "@/domain/uplifts/types/uplift";
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
|
|
||||||
|
|
@ -47,13 +48,33 @@ const TAB_LABELS: Record<UpliftApprovalTab, string> = {
|
||||||
rejected: "Rejected",
|
rejected: "Rejected",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A tab's total is only known once its queue has loaded without error; until then the
|
||||||
|
// pill must not claim a count (a loading or failed queue is not an empty one).
|
||||||
|
function knownTotal(query: {
|
||||||
|
data?: UpliftsQueueResult | undefined;
|
||||||
|
error: unknown;
|
||||||
|
}): number | null {
|
||||||
|
if (query.error || !query.data) return null;
|
||||||
|
return query.data.totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabCountPill({ tab, count }: { tab: UpliftApprovalTab; count: number | null }) {
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
size="small"
|
||||||
|
label={count ?? "—"}
|
||||||
|
aria-label={count == null ? `${tab} uplift count unavailable` : `${count} ${tab} uplifts`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function UpliftApprovalsTabs({
|
function UpliftApprovalsTabs({
|
||||||
tab,
|
tab,
|
||||||
counts,
|
counts,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
tab: UpliftApprovalTab;
|
tab: UpliftApprovalTab;
|
||||||
counts: Record<UpliftApprovalTab, number>;
|
counts: Record<UpliftApprovalTab, number | null>;
|
||||||
onChange: (_event: unknown, value: string) => void;
|
onChange: (_event: unknown, value: string) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -65,11 +86,7 @@ function UpliftApprovalsTabs({
|
||||||
label={
|
label={
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
||||||
<span>{TAB_LABELS[value]}</span>
|
<span>{TAB_LABELS[value]}</span>
|
||||||
<Chip
|
<TabCountPill tab={value} count={counts[value]} />
|
||||||
size="small"
|
|
||||||
label={counts[value]}
|
|
||||||
aria-label={`${counts[value]} ${value} uplifts`}
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -152,9 +169,9 @@ export default function UpliftQueuePage() {
|
||||||
const activeQuery = queries[tab];
|
const activeQuery = queries[tab];
|
||||||
const rows = activeQuery.data?.items ?? [];
|
const rows = activeQuery.data?.items ?? [];
|
||||||
const counts = {
|
const counts = {
|
||||||
pending: pendingQuery.data?.totalCount ?? 0,
|
pending: knownTotal(pendingQuery),
|
||||||
approved: approvedQuery.data?.totalCount ?? 0,
|
approved: knownTotal(approvedQuery),
|
||||||
rejected: rejectedQuery.data?.totalCount ?? 0,
|
rejected: knownTotal(rejectedQuery),
|
||||||
};
|
};
|
||||||
const pendingExposureTotal = pendingQuery.data?.pendingExposureTotal ?? null;
|
const pendingExposureTotal = pendingQuery.data?.pendingExposureTotal ?? null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import { formatDateTime } from "@/lib/time-utils";
|
||||||
import { renderWithProviders } from "@/test/test-utils";
|
import { renderWithProviders } from "@/test/test-utils";
|
||||||
|
|
||||||
const queueState = vi.hoisted(() => ({
|
const queueState = vi.hoisted(() => ({
|
||||||
byStatus: {} as Record<string, UpliftsQueueResult>,
|
byStatus: {} as Record<string, UpliftsQueueResult | undefined>,
|
||||||
|
errorByStatus: {} as Record<string, Error | undefined>,
|
||||||
requestedStatuses: [] as Array<string | undefined>,
|
requestedStatuses: [] as Array<string | undefined>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -92,11 +93,13 @@ function page(items: UpliftQueueItem[], totalCount = items.length): UpliftsQueue
|
||||||
vi.mock("@/domain/uplifts/use-cases/use-uplifts-queue", () => ({
|
vi.mock("@/domain/uplifts/use-cases/use-uplifts-queue", () => ({
|
||||||
useUpliftsQueue: (params: UpliftsQueueParams) => {
|
useUpliftsQueue: (params: UpliftsQueueParams) => {
|
||||||
queueState.requestedStatuses.push(params.status);
|
queueState.requestedStatuses.push(params.status);
|
||||||
|
const data = queueState.byStatus[params.status ?? ""];
|
||||||
|
const error = queueState.errorByStatus[params.status ?? ""] ?? null;
|
||||||
return {
|
return {
|
||||||
data: queueState.byStatus[params.status ?? ""] ?? null,
|
data,
|
||||||
isLoading: false,
|
isLoading: data === undefined && error === null,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
error: null,
|
error,
|
||||||
refetch: vi.fn(),
|
refetch: vi.fn(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -127,6 +130,7 @@ async function openRejectedTab() {
|
||||||
describe("Uplift Approvals rejected tab", () => {
|
describe("Uplift Approvals rejected tab", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
queueState.requestedStatuses = [];
|
queueState.requestedStatuses = [];
|
||||||
|
queueState.errorByStatus = {};
|
||||||
queueState.byStatus = {
|
queueState.byStatus = {
|
||||||
Pending: page([pendingItem]),
|
Pending: page([pendingItem]),
|
||||||
Approved: page([approvedItem]),
|
Approved: page([approvedItem]),
|
||||||
|
|
@ -145,6 +149,21 @@ describe("Uplift Approvals rejected tab", () => {
|
||||||
expect(queueState.requestedStatuses).toContain("Rejected");
|
expect(queueState.requestedStatuses).toContain("Rejected");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("never claims a count for a queue that is still loading or failed to load", async () => {
|
||||||
|
queueState.byStatus.Rejected = undefined;
|
||||||
|
queueState.errorByStatus.Rejected = new Error("Forbidden");
|
||||||
|
queueState.byStatus.Approved = undefined;
|
||||||
|
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
|
||||||
|
renderWithProviders(<UpliftQueuePage />);
|
||||||
|
|
||||||
|
const rejectedPill = screen.getByLabelText("rejected uplift count unavailable");
|
||||||
|
expect(rejectedPill).toHaveTextContent("—");
|
||||||
|
expect(screen.getByLabelText("approved uplift count unavailable")).toHaveTextContent("—");
|
||||||
|
expect(screen.queryByLabelText("0 rejected uplifts")).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByLabelText("0 approved uplifts")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText("1 pending uplifts")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("lists only rejected uplifts, in the server's most-recently-rejected order", async () => {
|
it("lists only rejected uplifts, in the server's most-recently-rejected order", async () => {
|
||||||
const table = await openRejectedTab();
|
const table = await openRejectedTab();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue