feat(09-01): add classification API route, client hook, badge component, and setup detail wiring

- Add PATCH /:id/items/:itemId/classification endpoint with Zod validation
- Add apiPatch helper to client API library
- Add useUpdateItemClassification mutation hook
- Add classification field to SetupItemWithCategory interface
- Create ClassificationBadge click-to-cycle component (base/worn/consumable)
- Wire ClassificationBadge into setup detail page item grid
- Add integration tests for PATCH classification route (valid + invalid)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 15:13:08 +01:00
parent 4491e4c6f1
commit fb738d7cc2
6 changed files with 169 additions and 13 deletions

View File

@@ -205,6 +205,67 @@ describe("Setup Routes", () => {
});
});
describe("PATCH /api/setups/:id/items/:itemId/classification", () => {
it("updates item classification and persists it", async () => {
const setup = await createSetupViaAPI(app, "Kit");
const item = await createItemViaAPI(app, {
name: "Jacket",
categoryId: 1,
});
// Sync item to setup
await app.request(`/api/setups/${setup.id}/items`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ itemIds: [item.id] }),
});
// Patch classification to "worn"
const res = await app.request(
`/api/setups/${setup.id}/items/${item.id}/classification`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ classification: "worn" }),
},
);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.success).toBe(true);
// Verify classification persisted
const getRes = await app.request(`/api/setups/${setup.id}`);
const getBody = await getRes.json();
expect(getBody.items[0].classification).toBe("worn");
});
it("returns 400 for invalid classification value", async () => {
const setup = await createSetupViaAPI(app, "Kit");
const item = await createItemViaAPI(app, {
name: "Tent",
categoryId: 1,
});
await app.request(`/api/setups/${setup.id}/items`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ itemIds: [item.id] }),
});
const res = await app.request(
`/api/setups/${setup.id}/items/${item.id}/classification`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ classification: "invalid-value" }),
},
);
expect(res.status).toBe(400);
});
});
describe("DELETE /api/setups/:id/items/:itemId", () => {
it("removes single item from setup", async () => {
const setup = await createSetupViaAPI(app, "Kit");