Use NCR business number in URLs (/ncrs/NCR-2026-0021)
Detail routes, queue navigation, and notification email links now use the business identifier instead of the database id — friendlier for end users quoting NCR numbers. The API resolves both forms (case-insensitive number, or legacy numeric id) so existing bookmarks and email links keep working. Adds regression tests incl. a guard that /ncrs/export.csv isn't shadowed by the path parameter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,11 +54,13 @@ export function useQueue(queue: string, filters: QueueFilters, page: number, pag
|
||||
});
|
||||
}
|
||||
|
||||
export function useNcr(id: number | undefined) {
|
||||
/** `ref` is the NCR business number (NCR-2026-0021) or, for older links,
|
||||
* the numeric database id — the API resolves both. */
|
||||
export function useNcr(ref: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ["ncr", id],
|
||||
queryFn: () => api<NcrDetail>(`/api/ncrs/${id}`),
|
||||
enabled: id !== undefined,
|
||||
queryKey: ["ncr", ref],
|
||||
queryFn: () => api<NcrDetail>(`/api/ncrs/${encodeURIComponent(ref!)}`),
|
||||
enabled: !!ref,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,7 +99,9 @@ export function useNcrMutation<TVars>(
|
||||
return useMutation({
|
||||
mutationFn,
|
||||
onSuccess: (data) => {
|
||||
qc.setQueryData(["ncr", data.ncr.id], data.ncr);
|
||||
// The detail page may be keyed by business number or legacy id.
|
||||
qc.setQueryData(["ncr", data.ncr.ncr_number], data.ncr);
|
||||
qc.setQueryData(["ncr", String(data.ncr.id)], data.ncr);
|
||||
qc.invalidateQueries({ queryKey: ["ncrs"] });
|
||||
qc.invalidateQueries({ queryKey: ["ncr-audit", data.ncr.id] });
|
||||
if (data.warnings.length && onWarnings) onWarnings(data.warnings);
|
||||
|
||||
@@ -72,7 +72,7 @@ export function QueueTable({ items, total, page, pageSize, onPageChange, loading
|
||||
<Stack spacing={1}>
|
||||
{items.map((n) => (
|
||||
<Card key={n.id} variant="outlined">
|
||||
<CardActionArea onClick={() => navigate(`/ncrs/${n.id}`)}>
|
||||
<CardActionArea onClick={() => navigate(`/ncrs/${n.ncr_number}`)}>
|
||||
<CardContent sx={{ py: 1.5 }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
@@ -121,7 +121,7 @@ export function QueueTable({ items, total, page, pageSize, onPageChange, loading
|
||||
key={n.id}
|
||||
hover
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => navigate(`/ncrs/${n.id}`)}
|
||||
onClick={() => navigate(`/ncrs/${n.ncr_number}`)}
|
||||
>
|
||||
<TableCell sx={{ fontWeight: 700 }}>{n.ncr_number}</TableCell>
|
||||
<TableCell>{n.job_number}</TableCell>
|
||||
|
||||
@@ -325,9 +325,10 @@ function DetailBody({ ncr }: { ncr: NcrDetail }) {
|
||||
}
|
||||
|
||||
export function NcrDetailPage() {
|
||||
// URL carries the NCR business number (e.g. /ncrs/NCR-2026-0021); numeric
|
||||
// ids from older links still resolve server-side.
|
||||
const { id } = useParams();
|
||||
const ncrId = Number(id);
|
||||
const ncrQuery = useNcr(Number.isFinite(ncrId) ? ncrId : undefined);
|
||||
const ncrQuery = useNcr(id);
|
||||
const me = useMe();
|
||||
const { toast } = useToast();
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
@@ -98,7 +98,7 @@ export function NewNcrPage() {
|
||||
</Box>
|
||||
|
||||
<Stack direction="row" spacing={1} justifyContent="center" sx={{ mt: 3 }}>
|
||||
<Button variant="contained" onClick={() => navigate(`/ncrs/${created.id}`)}>
|
||||
<Button variant="contained" onClick={() => navigate(`/ncrs/${created.ncr_number}`)}>
|
||||
View NCR
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user