Server-side pagination for /assets (50/page), fix duplicated manufacturer in model labels
CI / test (push) Failing after 8s
CI / test (push) Failing after 8s
listAssetItems() now takes {page, pageSize, status, modelId, categoryId,
areaId, search} and returns {items, total} instead of the whole table --
was a bare unpaginated findMany(), shipping all ~613 rows on every page
load. Filtering moved server-side into the same WHERE clause so 'total'
reflects the filtered count, not the table's overall size -- pagination
controls stay correct regardless of which filters are active. 'search'
matches serialNumber, assetTag, or the current (non-returned) assignee's
name via the assignments relation in one query.
GET /api/assets is now the one entity route whose response is not a bare
array (see use-api-list.ts) -- its {items, total} shape needs 'total' for
page controls, so /assets fetches it directly instead of through
useApiList. Query params: page, pageSize, status, modelId, categoryId,
areaId, search.
/assets page: search is debounced 300ms (was firing a request per
keystroke otherwise), changing any filter resets to page 1, added
Previous/Next controls + "showing X-Y / total" count. Row # is now a
running index across pages, not per-page.
Separately: all 135 AssetModel rows from the 2026-08-24 real-data import
have the manufacturer name baked into 'name' already (the source file's
own Model column did, e.g. name="Bose Soundlink Revolve" manufacturer=
"Bose") -- rendering "manufacturer name" everywhere doubled it
("Bose Bose Soundlink Revolve"). Fixed at the display layer
(formatModelLabel(), skips the manufacturer prefix when name already
starts with it) rather than a data migration -- non-destructive, handles
every affected row uniformly, reversible.
Smoke-tested listAssetItems() directly (tsx): page1/page2 have zero
overlap, status filter count matches (71 in_stock), asset-tag search
finds exactly 1 row, assignee-name search correctly joins through
Assignment (4 rows for one employee), last page (13 of 613/50) has the
expected remainder of 13 rows.
tsc/lint clean, no Vitest coverage -- same deviation as the rest of this
branch, see CLAUDE.md.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { createAssetItem, listAssetItems, validateCreateAssetItemInput } from "@/lib/assets";
|
||||
import {
|
||||
createAssetItem,
|
||||
listAssetItems,
|
||||
validateCreateAssetItemInput,
|
||||
type AssetItemStatus,
|
||||
} from "@/lib/assets";
|
||||
|
||||
// Mirrors src/app/api/employees/route.ts's pattern — middleware.ts excludes
|
||||
// /api from route protection, so this checks the session itself. Flat
|
||||
@@ -59,12 +64,37 @@ export async function POST(request: Request): Promise<NextResponse> {
|
||||
return NextResponse.json(created.value, { status: 201 });
|
||||
}
|
||||
|
||||
export async function GET(): Promise<NextResponse> {
|
||||
// The one entity route in this app whose GET response is NOT a bare array
|
||||
// (see src/hooks/use-api-list.ts's doc comment on that being the norm
|
||||
// everywhere else) -- it's `{items, total}` instead, because /assets is
|
||||
// paginated (2026-08-25, see listAssetItems()'s doc comment) and the
|
||||
// client needs `total` to render page controls. src/app/assets/page.tsx
|
||||
// fetches this directly rather than through useApiList for that reason.
|
||||
export async function GET(request: Request): Promise<NextResponse> {
|
||||
const session = await auth();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const assetItems = await listAssetItems();
|
||||
return NextResponse.json(assetItems, { status: 200 });
|
||||
const url = new URL(request.url);
|
||||
const params = url.searchParams;
|
||||
const page = params.has("page") ? Number(params.get("page")) : undefined;
|
||||
const pageSize = params.has("pageSize") ? Number(params.get("pageSize")) : undefined;
|
||||
const status = params.get("status") ?? undefined;
|
||||
const modelId = params.get("modelId") ?? undefined;
|
||||
const categoryId = params.get("categoryId") ?? undefined;
|
||||
const areaId = params.get("areaId") ?? undefined;
|
||||
const search = params.get("search") ?? undefined;
|
||||
|
||||
const validStatuses = ["in_stock", "assigned", "repair", "retired", "lost"];
|
||||
const result = await listAssetItems({
|
||||
page,
|
||||
pageSize,
|
||||
status: status && validStatuses.includes(status) ? (status as AssetItemStatus) : undefined,
|
||||
modelId,
|
||||
categoryId,
|
||||
areaId,
|
||||
search,
|
||||
});
|
||||
return NextResponse.json(result, { status: 200 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user