Conversation
…backend and added update item endpoint hook
There was a problem hiding this comment.
Pull request overview
This PR refactors the frontend item typings and filtering to prefer undefined over null, expands the items API client to support admin CRUD endpoints, and adds a small backend enhancement to expose box_code in public item serialization (plus some minor UI/CSS tweaks).
Changes:
- Update catalogue filters to use
undefinedfor “unset” values and align filter typing. - Replace
PublicCollectionItemusage in some components with a newCollectionItemtype and expanditemsApiwith CRUD methods. - Backend: expose
box_codeonPublicCollectionItemSerializer.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/pages/public/CataloguePage.tsx | Initializes catalogue filters using undefined values. |
| frontend/src/lib/types.ts | Replaces placeholder item type(s) with ItemType/ItemStatus + new CollectionItem shape. |
| frontend/src/lib/filters.ts | Changes filter field types and removes null from several filter params. |
| frontend/src/index.css | Adds light-blue accent CSS variables. |
| frontend/src/components/layout/Header.css | Hides .header-nav on small screens. |
| frontend/src/components/items/ItemList.tsx | Switches item list typing to CollectionItem[]. |
| frontend/src/components/items/ItemCard.tsx | Switches item card typing to CollectionItem. |
| frontend/src/components/items/ItemDetailCard.tsx | Adds a new item details card component. |
| frontend/src/components/items/DetailRow.tsx | Adds a new reusable detail row component. |
| frontend/src/components/items/CatalogueSearchBar.tsx | Uses undefined instead of null when clearing filters; adjusts search input handling. |
| frontend/src/components/common/Button.tsx | Adds hideMobile prop to hide buttons on small screens. |
| frontend/src/api/items.api.ts | Adds create/update/patch methods and changes item response typing/endpoint usage. |
| frontend/src/actions/useItems.ts | Minor cleanup of queryFn comment. |
| backend/inventory/serializers.py | Adds box_code to the public item serializer output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export interface CollectionItem { | ||
| id: number; | ||
| item_code: string; | ||
| title: string; | ||
| platform: string; | ||
| description: string; | ||
| item_type?: ItemType; | ||
| condition?: ConditionType; | ||
| is_complete?: CompletenessType; | ||
| is_functional?: CompletenessType; | ||
| date_of_entry?: string; | ||
| working_condition?: boolean; | ||
| status?: 'AVAILABLE' | 'IN_TRANSIT' | 'CHECKED_OUT' | 'MAINTENANCE'; | ||
| item_type: ItemType; | ||
| working_condition: boolean; | ||
| status: ItemStatus; | ||
| current_location: LocationInfo | number | null; | ||
| is_public_visible: boolean; | ||
| is_on_floor: boolean; | ||
| location_name?: string; | ||
| current_location?: LocationInfo; | ||
| box_code?: string; | ||
| created_at?: string; | ||
| updated_at?: string; | ||
| // Software-specific fields | ||
| creator_publisher?: string; | ||
| release_year?: string; | ||
| version_edition?: string; | ||
| media_type?: string; | ||
| // Hardware-specific fields | ||
| manufacturer?: string; | ||
| model_number?: string; | ||
| year_manufactured?: string; | ||
| serial_number?: string; | ||
| hardware_type?: string; | ||
| // Non-Electronic-specific fields | ||
| item_subtype?: string; | ||
| date_published?: string; | ||
| publisher?: string; | ||
| volume_number?: string; | ||
| isbn_catalogue_number?: string; | ||
| box: BoxSummary | number | null; | ||
| } |
There was a problem hiding this comment.
CollectionItem is now used as the frontend item shape, but it doesn’t match the backend responses consumed by this PR: /inventory/public/items/ (PublicCollectionItemSerializer) does not include is_public_visible or box, and instead includes fields like location_name/box_code. This makes required fields (is_public_visible, box) effectively undefined at runtime and also removes the PublicCollectionItem type that existing pages still import. Consider reintroducing a separate PublicCollectionItem type (matching PublicCollectionItemSerializer) and/or making the extra fields optional, and update API typings accordingly so public vs admin endpoints are clearly separated.
| getAll: async (params?: ItemFilter): Promise<CollectionItem[]> => { | ||
| const queryParams: Record<string, string> = {}; | ||
|
|
||
| if (params?.platform) queryParams.platform = params.platform; | ||
| if (params?.is_on_floor !== undefined && params?.is_on_floor !== null) | ||
| if (params?.is_on_floor !== undefined && params?.is_on_floor !== null) { | ||
| queryParams.is_on_floor = params.is_on_floor ? 'true' : 'false'; | ||
| } | ||
| if (params?.search) queryParams.search = params.search; | ||
| if (params?.ordering) queryParams.ordering = params.ordering; | ||
| if (params?.item_type) queryParams.item_type = params.item_type; | ||
| if (params?.status) queryParams.status = params.status; | ||
| if (params?.location_type) queryParams.location_type = params.location_type; | ||
|
|
||
| // Fetch all items by setting a large page size | ||
|
|
||
| queryParams.page_size = '10000'; | ||
|
|
||
| const response = await apiClient.get('/inventory/public/items/', { params: queryParams }); | ||
| return response.data.results ?? response.data; | ||
| const response = await apiClient.get('/inventory/public/items/', { | ||
| params: queryParams, | ||
| }); | ||
|
|
||
| const data = response.data; | ||
|
|
||
| if (Array.isArray(data)) return data; | ||
| if (data?.results && Array.isArray(data.results)) return data.results; | ||
| return []; |
There was a problem hiding this comment.
getAll() is calling the public endpoint (/inventory/public/items/) but is typed to return CollectionItem[] (which includes admin-only fields like is_public_visible and box). Either change the return type to a public item type (matching the public serializer), or route admin callers to /inventory/items/ and keep public/admin APIs separate to avoid incorrect assumptions in the UI.
| getById: async (id: string | number): Promise<PublicCollectionItem> => { | ||
| const response = await apiClient.get(`/inventory/public/items/${id}/`); | ||
| getById: async (id: string | number): Promise<CollectionItem> => { | ||
| const response = await apiClient.get(`/inventory/items/${id}/`); |
There was a problem hiding this comment.
getById() now targets /inventory/items/{id}/, but the backend CollectionItemViewSet uses CollectionItemSerializer for retrieve, which returns current_location (and box) as primary keys rather than nested objects. If any UI expects current_location.name etc., this will break. Consider either (a) updating the backend to use AdminCollectionItemSerializer for retrieve as well, or (b) normalizing the response client-side and typing getById accordingly.
| const response = await apiClient.get(`/inventory/items/${id}/`); | |
| const response = await apiClient.get(`/inventory/public/items/${id}/`); |
| current_location = LocationSerializer(read_only=True) | ||
| location_name = serializers.SerializerMethodField() | ||
| box_code = serializers.CharField(source="box.box_code", read_only=True) | ||
|
|
There was a problem hiding this comment.
Adding box_code = serializers.CharField(source='box.box_code', ...) will cause an extra DB query per item unless the queryset selects the related box. The current public viewset queryset selects current_location but not box, so this introduces an N+1 query pattern for public catalogue list/detail responses. Update the public queryset to select_related('current_location', 'box') (or otherwise prefetch) to keep performance stable.
| export interface ItemFilter { | ||
| search?: string | null; | ||
| platform?: string | null; | ||
| platform?: string; | ||
| is_on_floor?: boolean | null; | ||
| ordering?: string | null; | ||
| item_type?: 'SOFTWARE' | 'HARDWARE' | null; | ||
| status?: 'AVAILABLE' | 'IN_TRANSIT' | 'CHECKED_OUT' | 'MAINTENANCE' | null; | ||
| location_type?: 'FLOOR' | 'STORAGE' | 'EVENT' | 'OTHER' | null; | ||
| search?: string; | ||
| ordering?: string; | ||
| item_type?: string; | ||
| status?: string; | ||
| location_type?: string; |
There was a problem hiding this comment.
ItemFilter widened item_type, status, and location_type to plain string, even though this repo already defines constrained unions (ItemType, ItemStatus, and the location_type union in LocationInfo). This removes compile-time validation for filter values and makes it easier to accidentally send unsupported params to the API. Prefer reusing those existing union types (and keeping | undefined instead of | null where needed).
No description provided.