shoc-frontend-new/src/pages/uplifts/UpliftQueuePage.js

183 lines
6.3 KiB
JavaScript
Raw Normal View History

import React, { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router";
import { toast } from "react-toastify";
import { upliftApi } from "../../services/api";
import "./UpliftQueuePage.css";
function formatDateTime(value) {
if (!value) return "";
const d = new Date(value);
if (isNaN(d)) return "";
const pad = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
const STATUS_OPTIONS = ["Pending", "Approved", "Denied", "Cancelled"];
export default function UpliftQueuePage() {
const navigate = useNavigate();
const [status, setStatus] = useState("Pending");
const [tier, setTier] = useState("");
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize] = useState(25);
const [loading, setLoading] = useState(false);
const load = useCallback(async () => {
setLoading(true);
try {
const params = { status, page, pageSize };
if (tier) params.tier = tier;
const res = await upliftApi.list(params);
const data = res?.data ?? res;
setItems(data?.items || []);
setTotal(data?.total || 0);
} catch (err) {
toast.error(err.message || "Failed to load uplift queue");
} finally {
setLoading(false);
}
}, [status, tier, page, pageSize]);
useEffect(() => { load(); }, [load]);
async function approve(req) {
const note = window.prompt("Optional approval note:", "") || "";
if (!window.confirm(`Approve uplift to $${Number(req.requestedNTE).toFixed(2)}?`)) return;
try {
await upliftApi.approve(req.id, note);
toast.success("Uplift approved — NTE updated");
load();
} catch (err) {
toast.error(err.message || "Failed to approve");
}
}
async function deny(req) {
const note = window.prompt("Denial reason (required):", "");
if (!note || !note.trim()) {
toast.error("A reason is required when denying.");
return;
}
try {
await upliftApi.deny(req.id, note.trim());
toast.success("Uplift denied");
load();
} catch (err) {
toast.error(err.message || "Failed to deny");
}
}
const totalPages = Math.max(1, Math.ceil(total / pageSize));
return (
<section className="upliftQueuePage">
<div className="upliftQueueHeader">
<h2>Uplift Queue</h2>
<div className="upliftQueueFilters">
<label>
Status:
<select value={status} onChange={(e) => { setStatus(e.target.value); setPage(1); }}>
{STATUS_OPTIONS.map((s) => <option key={s} value={s}>{s}</option>)}
<option value="">All</option>
</select>
</label>
<label>
Tier:
<select value={tier} onChange={(e) => { setTier(e.target.value); setPage(1); }}>
<option value="">All</option>
<option value="1">Tier 1</option>
<option value="2">Tier 2</option>
</select>
</label>
<span className="upliftQueueCount">{total} total</span>
</div>
</div>
{loading ? (
<div className="upliftQueueLoading">Loading…</div>
) : items.length === 0 ? (
<div className="upliftQueueEmpty">No uplift requests match these filters.</div>
) : (
<table className="upliftQueueTable">
<thead>
<tr>
<th>Dispatch</th>
<th>PO</th>
<th>Vendor</th>
<th>Current NTE</th>
<th>Requested</th>
<th>Delta</th>
<th>Tier</th>
<th>Status</th>
<th>Requested At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{items.map((r) => (
<tr key={r.id}>
<td>{r.dispatchNumber}</td>
<td>{r.poNumber}</td>
<td>{r.vendorCompanyName}</td>
<td>${Number(r.currentNTE ?? 0).toFixed(2)}</td>
<td>${Number(r.requestedNTE).toFixed(2)}</td>
<td>${Number(r.delta).toFixed(2)}</td>
<td>
<span className={`upliftTier upliftTier-${r.requiredTier}`}>
Tier {r.requiredTier}
</span>
</td>
<td>
<span className={`upliftStatusPill upliftStatusPill-${String(r.status).toLowerCase()}`}>
{r.status}
</span>
</td>
<td>{r.requestedAt ? formatDateTime(r.requestedAt) : ""}</td>
<td>
<div className="upliftActions">
{r.workOrderId && (
<button className="btn btn-default" onClick={() => navigate(`/workorders/${r.workOrderId}`)}>
View
</button>
)}
{r.status === "Pending" && (
<>
<button
className="btn btn-success"
disabled={!r.canDecide}
title={!r.canDecide ? `Requires Tier ${r.requiredTier} role` : ""}
onClick={() => approve(r)}
>
Approve
</button>
<button
className="btn btn-danger"
disabled={!r.canDecide}
title={!r.canDecide ? `Requires Tier ${r.requiredTier} role` : ""}
onClick={() => deny(r)}
>
Deny
</button>
</>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
)}
{totalPages > 1 && (
<div className="upliftQueuePager">
<button disabled={page <= 1} onClick={() => setPage(page - 1)}>← Prev</button>
<span>Page {page} of {totalPages}</span>
<button disabled={page >= totalPages} onClick={() => setPage(page + 1)}>Next →</button>
</div>
)}
</section>
);
}