fix(uplifts): complete approval queue review fixes (SH-208)

This commit is contained in:
Codex Review Integration 2026-09-16 22:44:37 -03:00
parent 6f8b502116
commit 5aed31392c
4 changed files with 60 additions and 27 deletions

View file

@ -15,7 +15,7 @@ import {
import { formatUpliftMoney } from "@/app/(protected)/uplifts/_components/format-uplift-money";
import { Text } from "@/components/ui/text";
import type { UpliftQueueItem } from "@/domain/uplifts/types/uplift";
import { getWaitTimeTextClass, timeSince, waitTimeColor } from "@/lib/time-utils";
import { formatDateTime, getWaitTimeTextClass, timeSince, waitTimeColor } from "@/lib/time-utils";
export type UpliftApprovalTab = "pending" | "approved";
@ -223,9 +223,16 @@ function UpliftApprovalRow({
<AttachmentsCell row={row} onOpenAttachment={onOpenAttachment} />
</TableCell>
<TableCell>{row.requestedByVendorName || "—"}</TableCell>
<TableCell>
<WaitingCell row={row} />
</TableCell>
{tab === "pending" ? (
<TableCell>
<WaitingCell row={row} />
</TableCell>
) : (
<>
<TableCell>{row.decidedByName || "—"}</TableCell>
<TableCell>{row.decidedAt ? formatDateTime(row.decidedAt) : "—"}</TableCell>
</>
)}
<TableCell>
{tab === "pending" ? (
<PendingRowActions
@ -242,7 +249,7 @@ function UpliftApprovalRow({
);
}
const HEADER_CELLS = [
const PENDING_HEADER_CELLS = [
"Work Order",
"Service / Vendor",
"Amount",
@ -254,6 +261,19 @@ const HEADER_CELLS = [
"Actions",
] as const;
const APPROVED_HEADER_CELLS = [
"Work Order",
"Service / Vendor",
"Amount",
"Approved on WO",
"Notes",
"Attachments",
"Requested By",
"Approved By",
"Approved At",
"Actions",
] as const;
export function UpliftApprovalsTable({
tab,
isLoading,
@ -275,12 +295,13 @@ export function UpliftApprovalsTable({
onOpenAttachment: (row: UpliftQueueItem) => void;
isDecisionPending: boolean;
}) {
const headerCells = tab === "pending" ? PENDING_HEADER_CELLS : APPROVED_HEADER_CELLS;
return (
<TableContainer component={Paper} variant="outlined">
<Table size="small" aria-label={`${tab} uplift requests`}>
<TableHead>
<TableRow>
{HEADER_CELLS.map((label) => (
{headerCells.map((label) => (
<TableCell key={label} className="font-semibold">
{label}
</TableCell>
@ -290,13 +311,13 @@ export function UpliftApprovalsTable({
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={HEADER_CELLS.length} align="center" className="py-10">
<TableCell colSpan={headerCells.length} align="center" className="py-10">
<CircularProgress size={28} />
</TableCell>
</TableRow>
) : rows.length === 0 ? (
<TableRow>
<TableCell colSpan={HEADER_CELLS.length} align="center" className="py-10">
<TableCell colSpan={headerCells.length} align="center" className="py-10">
{tab === "pending" ? (
<Text variant="description" tone="muted">
No uplifts waiting for approval.

View file

@ -25,6 +25,7 @@ type ModalCallbacks = {
onApprove: (row: UpliftQueueItem) => void;
onReject: (row: UpliftQueueItem) => void;
onRevoke: (row: UpliftQueueItem) => void;
onOpenAttachment: (row: UpliftQueueItem) => void;
};
function DetailField({ label, children }: { label: string; children: ReactNode }) {
@ -49,7 +50,13 @@ function DetailSection({ title, children }: { title: string; children: ReactNode
);
}
function UpliftAttachments({ item }: { item: UpliftQueueItem }) {
function UpliftAttachments({
item,
onOpenAttachment,
}: {
item: UpliftQueueItem;
onOpenAttachment: (row: UpliftQueueItem) => void;
}) {
if (item.evidenceDocumentId == null && !item.evidenceFileName) {
return (
<Text variant="description" tone="muted">
@ -63,7 +70,12 @@ function UpliftAttachments({ item }: { item: UpliftQueueItem }) {
: 0;
return (
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5, flexWrap: "wrap" }}>
<Chip size="small" label={item.evidenceFileName || "Attachment"} />
<Chip
size="small"
label={item.evidenceFileName || "Attachment"}
onClick={item.evidenceDocumentId != null ? () => onOpenAttachment(item) : undefined}
clickable={item.evidenceDocumentId != null}
/>
{extraCount > 0 && <Chip size="small" label={`+${extraCount}`} />}
</Box>
);
@ -91,7 +103,13 @@ function WorkOrderSection({ item, info }: { item: UpliftQueueItem; info?: WorkOr
);
}
function RequestSection({ item }: { item: UpliftQueueItem }) {
function RequestSection({
item,
onOpenAttachment,
}: {
item: UpliftQueueItem;
onOpenAttachment: (row: UpliftQueueItem) => void;
}) {
const waitingClass = item.requestedAt
? getWaitTimeTextClass(waitTimeColor(item.requestedAt))
: undefined;
@ -114,7 +132,7 @@ function RequestSection({ item }: { item: UpliftQueueItem }) {
<Text variant="description">{item.vendorReason || "No justification provided."}</Text>
</DetailField>
<DetailField label="Attachments">
<UpliftAttachments item={item} />
<UpliftAttachments item={item} onOpenAttachment={onOpenAttachment} />
</DetailField>
</DetailSection>
);
@ -269,6 +287,7 @@ export function UpliftDetailModal({
onApprove,
onReject,
onRevoke,
onOpenAttachment,
}: {
item: UpliftQueueItem | null;
approvePending: boolean;
@ -326,7 +345,7 @@ export function UpliftDetailModal({
<DialogContent>
<Box sx={{ display: "grid", gap: 3 }}>
<WorkOrderSection item={item} info={info} />
<RequestSection item={item} />
<RequestSection item={item} onOpenAttachment={onOpenAttachment} />
<DetailSection title="Approved on work order">
<ApprovedOnWoBreakdown
auto={breakdown.auto}

View file

@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useState } from "react";
import { Alert, Box, Chip, Stack, Tab, Tabs, TablePagination } from "@mui/material";
import { toast } from "react-toastify";
import { RejectDialog } from "@/app/(protected)/uplifts/_components/reject-dialog";
@ -17,18 +17,6 @@ import { Text } from "@/components/ui/text";
const PAGE_SIZE = 25;
function sortRows(rows: UpliftQueueItem[], tab: UpliftApprovalTab): UpliftQueueItem[] {
const copy = [...rows];
if (tab === "pending") {
copy.sort((a, b) =>
a.requestedAt < b.requestedAt ? -1 : a.requestedAt > b.requestedAt ? 1 : 0,
);
} else {
copy.sort((a, b) => (a.decidedAt > b.decidedAt ? -1 : a.decidedAt < b.decidedAt ? 1 : 0));
}
return copy;
}
function UpliftApprovalsHeader({ pendingExposureTotal }: { pendingExposureTotal: number | null }) {
return (
<Box>
@ -151,7 +139,7 @@ export default function UpliftQueuePage() {
} = useUpliftApprovalController();
const activeQuery = tab === "pending" ? pendingQuery : approvedQuery;
const rows = useMemo(() => sortRows(activeQuery.data?.items ?? [], tab), [activeQuery.data, tab]);
const rows = activeQuery.data?.items ?? [];
const pendingCount = pendingQuery.data?.totalCount ?? 0;
const pendingExposureTotal = pendingQuery.data?.pendingExposureTotal ?? null;
@ -204,6 +192,7 @@ export default function UpliftQueuePage() {
onApprove={handleApprove}
onReject={handleRejectRequest}
onRevoke={handleRevokeRequest}
onOpenAttachment={handleOpenAttachment}
/>
)}
<RejectDialog

View file

@ -85,6 +85,10 @@ describe("Uplift Approvals approved tab", () => {
renderWithProviders(<UpliftQueuePage />);
fireEvent.click(screen.getByRole("tab", { name: "Approved" }));
expect(screen.getByRole("columnheader", { name: "Approved By" })).toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: "Approved At" })).toBeInTheDocument();
expect(screen.queryByRole("columnheader", { name: "Waiting" })).not.toBeInTheDocument();
expect(screen.getByText("Admin User")).toBeInTheDocument();
const revoke = await screen.findByRole("button", { name: "Revoke" });
expect(revoke).toBeDisabled();
fireEvent.mouseOver(revoke);