Item Catalog — Routes¶
URL structure under src/routes/_auth/catalog/. All routes are lazy-loaded per docs/standards.md § Code-Splitting and Lazy Routes. Route files are entry points only (≤20 lines) — the page implementation lives in features/item-catalog/pages/ and features/item-catalog/components/. Every path string belongs in src/lib/routes.ts; route files and components read from that module rather than hard-coding literals.
Route tree¶
| URL | File | Search-param schema | Page (from pages.md) |
Route gate |
|---|---|---|---|---|
/catalog |
src/routes/_auth/catalog/index.tsx |
— | redirects to /catalog/items |
Permission.ITEM_READ |
/catalog/items |
src/routes/_auth/catalog/items/index.tsx |
itemListSearchSchema |
ItemListPage |
Permission.ITEM_READ |
/catalog/items/$itemId |
src/routes/_auth/catalog/items/$itemId/index.tsx |
— | ItemDetailPage |
Permission.ITEM_READ |
/catalog/items/new |
src/routes/_auth/catalog/items/new.tsx |
— | ItemCreatePage |
Permission.ITEM_CREATE |
/catalog/items/$itemId/edit |
src/routes/_auth/catalog/items/$itemId/edit.tsx |
— | ItemEditPage |
Permission.ITEM_UPDATE (composite — see pages.md) |
/catalog/groups |
src/routes/_auth/catalog/groups/index.tsx |
groupListSearchSchema |
GroupListPage |
Permission.ITEM_GROUP_READ |
/catalog/groups/$groupId |
src/routes/_auth/catalog/groups/$groupId.tsx |
groupDetailItemsSchema |
GroupDetailPage |
Permission.ITEM_GROUP_READ |
/catalog/import |
src/routes/_auth/catalog/import.tsx |
— | ItemImportPage |
Permission.ITEM_BULK_IMPORT |
Permission.X values are deferred until the identity module ships. Until then routes render unconditionally (per CLAUDE.md § Authentication and authorization). The intent above is wired today via comments / TODOs so the eventual gate is unambiguous.
Search-param schemas¶
Schemas live next to the route file that uses them (features/item-catalog/schemas.ts if multiple routes share a shape, otherwise inline at the route file). Defaults mirror BE defaults so the URL stays clean — non-default values only are written to the URL per docs/standards.md § URL as Source of Truth (lists).
itemListSearchSchema¶
import { z } from 'zod'
import {
itemTypeSchema, // generated enum: ingredient | product | intermediate | packaging | consumable | service | resale
itemStatusFilterSchema, // generated enum: active | inactive | all
itemListSortSchema, // generated enum: name | -name | code | -code | created_at | -created_at | updated_at | -updated_at | item_type | -item_type | status | -status | storage_unit | -storage_unit | shelf_life_days | -shelf_life_days
} from '@/generated/schemas/item-catalog'
export const itemListSearchSchema = z.object({
q: z.string().max(200).optional(),
item_type: z.array(itemTypeSchema).optional(), // repeated query param
status: itemStatusFilterSchema.default('active'),
group_id: z.string().uuid().optional(),
page: z.coerce.number().int().min(1).default(1),
page_size: z.coerce.number().int().min(1).max(100).default(20),
sort: itemListSortSchema.default('-created_at'),
snapshot_at: z.string().datetime({ offset: true }).optional(),
})
export type ItemListSearch = z.infer<typeof itemListSearchSchema>
groupListSearchSchema¶
export const groupListSearchSchema = z.object({
q: z.string().max(200).optional(),
item_type: itemTypeSchema.optional(),
page: z.coerce.number().int().min(1).default(1),
page_size: z.coerce.number().int().min(1).max(100).default(20),
})
groupDetailItemsSchema (items-in-group pagination on the group-detail page)¶
export const groupDetailItemsSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
page_size: z.coerce.number().int().min(1).max(100).default(20),
})
src/lib/routes.ts entries (added by M0 ticket 12)¶
export const itemCatalogRoutes = {
root: '/catalog',
itemList: '/catalog/items',
itemDetail: (itemId: string) => `/catalog/items/${itemId}`,
itemCreate: '/catalog/items/new',
itemEdit: (itemId: string) => `/catalog/items/${itemId}/edit`,
groupList: '/catalog/groups',
groupDetail: (groupId: string) => `/catalog/groups/${groupId}`,
import: '/catalog/import',
} as const
src/lib/navigation.ts entry (added by M0 ticket 12)¶
One top-level NavItem with two children, sorted so "Items" is the default landing under "Catalog":
{
i18nKey: 'common:nav.catalog',
icon: PackageIcon, // lucide-react
path: itemCatalogRoutes.itemList,
requiredPermission: Permission.ITEM_READ,
children: [
{ i18nKey: 'common:nav.items', path: itemCatalogRoutes.itemList, requiredPermission: Permission.ITEM_READ },
{ i18nKey: 'common:nav.groups', path: itemCatalogRoutes.groupList, requiredPermission: Permission.ITEM_GROUP_READ },
],
}
The Import flow is not in the sidebar — it's reached from the Item list page header.
Lazy-load policy¶
All routes use lazyRouteComponent. None of this module's pages render on first paint (sidebar nav, dashboard, and login all live elsewhere), so no exceptions are needed.