feat(ui): list parity — row tinting, received-orders status filter, more sortable cols, search-aware empties, skeleton suppression
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate, Link as RouterLink } from "react-router-dom";
|
||||
import Box from "@mui/material/Box";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
@@ -101,6 +101,9 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
const debouncedSearch = useDebounce(search, 300);
|
||||
const [status, setStatus] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
// Track first successful load so later refetches (filter/status/page change)
|
||||
// keep the table visible instead of flashing the full-page skeleton.
|
||||
const hasLoadedOnce = useRef(false);
|
||||
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<{
|
||||
show: boolean;
|
||||
@@ -138,6 +141,12 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
}),
|
||||
);
|
||||
|
||||
// Mark first load done in an effect (not during render) to avoid a
|
||||
// render-phase ref mutation.
|
||||
useEffect(() => {
|
||||
if (!isPending) hasLoadedOnce.current = true;
|
||||
}, [isPending]);
|
||||
|
||||
if (!hasPermission("orders.view")) return <Forbidden />;
|
||||
|
||||
const handleDelete = async () => {
|
||||
@@ -173,7 +182,10 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
}
|
||||
};
|
||||
|
||||
if (isPending) {
|
||||
// Only show the full-page skeleton on the very first load; on subsequent
|
||||
// refetches (filter/status/page change) keep the table visible (the Card
|
||||
// dims via isFetching) so it doesn't flash.
|
||||
if (isPending && !hasLoadedOnce.current) {
|
||||
return <LoadingState />;
|
||||
}
|
||||
|
||||
@@ -274,6 +286,32 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
},
|
||||
];
|
||||
|
||||
// Per-status row tints — subtle channel-alpha washes (never a solid `.light`
|
||||
// fill, which is invisible-text in dark mode). Completed = success wash,
|
||||
// cancelled = faded/muted, matching the Offers/Invoices intensity.
|
||||
const rowSx = (o: IssuedOrder) => {
|
||||
if (o.status === "cancelled") {
|
||||
return {
|
||||
opacity: 0.6,
|
||||
"& td": { color: "var(--mui-palette-text-secondary)" },
|
||||
};
|
||||
}
|
||||
if (o.status === "completed") {
|
||||
return {
|
||||
backgroundColor: "rgba(var(--mui-palette-success-mainChannel) / 0.12)",
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
"rgba(var(--mui-palette-success-mainChannel) / 0.18)",
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// Search/status filter is active → an empty list means "nothing matches"
|
||||
// (no create CTA); a genuinely empty list keeps the create-CTA empty state.
|
||||
const isFiltered = !!debouncedSearch || !!status;
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterBar>
|
||||
@@ -305,18 +343,23 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
columns={columns}
|
||||
rows={orders}
|
||||
rowKey={(o) => o.id}
|
||||
rowSx={rowSx}
|
||||
sortBy={sort}
|
||||
sortDir={order}
|
||||
onSort={handleSort}
|
||||
empty={
|
||||
<EmptyState
|
||||
title="Zatím žádné vydané objednávky."
|
||||
description={
|
||||
hasPermission("orders.create")
|
||||
? "Vytvořte první tlačítkem „Vytvořit objednávku vydanou“."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
isFiltered ? (
|
||||
<EmptyState title="Žádné objednávky neodpovídají filtru." />
|
||||
) : (
|
||||
<EmptyState
|
||||
title="Zatím žádné vydané objednávky."
|
||||
description={
|
||||
hasPermission("orders.create")
|
||||
? "Vytvořte první tlačítkem „Vytvořit objednávku vydanou“."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Pagination
|
||||
|
||||
Reference in New Issue
Block a user