mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 09:13:11 +00:00
fix(uplifts): complete approval queue review fixes (SH-208)
This commit is contained in:
parent
6f8b502116
commit
5aed31392c
4 changed files with 60 additions and 27 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue