Skip to content
3 changes: 3 additions & 0 deletions backend/inventory/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class PublicCollectionItemSerializer(serializers.ModelSerializer):

current_location = LocationSerializer(read_only=True)
location_name = serializers.SerializerMethodField()
box_code = serializers.CharField(source="box.box_code", read_only=True)

class Meta:
model = CollectionItem
Expand All @@ -140,6 +141,7 @@ class Meta:
"is_on_floor",
"current_location",
"location_name",
"box_code",
"created_at",
"updated_at",
]
Expand All @@ -153,6 +155,7 @@ class Meta:
"working_condition",
"status",
"is_on_floor",
"box_code",
"current_location",
"location_name",
"created_at",
Expand Down
15 changes: 11 additions & 4 deletions frontend/src/actions/useItems.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { useQuery } from '@tanstack/react-query';
import { itemsApi } from '../api/items.api';
import { publicItemsApi, itemsApi } from '../api/items.api';
import type { ItemFilter } from '../lib/filters';

export const useItems = (filters?: ItemFilter) => {
export const usePublicItems = (filters?: ItemFilter) => {
return useQuery({
queryKey: ['items', 'list', filters],
queryFn: () => itemsApi.getAll(filters), // Use the updated `itemsApi.getAll`
queryKey: ['public-items', filters],
queryFn: () => publicItemsApi.getAll(filters),
});
};

export const useAdminItems = (filters?: ItemFilter) => {
return useQuery({
queryKey: ['admin-items', filters],
queryFn: () => itemsApi.getAll(filters),
});
};
94 changes: 86 additions & 8 deletions frontend/src/api/items.api.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,107 @@
// Collection Items API calls
import apiClient from './apiClient';
import type { PublicCollectionItem } from '../lib/types';
import type {
ItemType,
ItemStatus,
PublicCollectionItem,
AdminCollectionItem,
} from '../lib/types';

import type { ItemFilter } from '../lib/filters';

export interface CreateItemData {
item_code: string;
title: string;
platform: string;
description: string;
item_type: ItemType;
working_condition: boolean;
status: ItemStatus;
current_location: number;
is_public_visible: boolean;
is_on_floor: boolean;
box: number | null;
}

export type UpdateItemData = Partial<CreateItemData>;

export const itemsApi = {
// ADMIN ONLY

getAll: async (params?: ItemFilter): Promise<AdminCollectionItem[]> => {
const queryParams: Record<string, string> = {};

if (params?.platform) queryParams.platform = params.platform;
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;

queryParams.page_size = '10000';

const response = await apiClient.get('/inventory/items/', {
params: queryParams,
});

const data = response.data;

if (Array.isArray(data)) return data;
if (data?.results && Array.isArray(data.results)) return data.results;
return [];
},

getById: async (id: string | number): Promise<AdminCollectionItem> => {
const response = await apiClient.get(`/inventory/items/${id}/`);
return response.data;
},

create: async (data: CreateItemData): Promise<AdminCollectionItem> => {
const response = await apiClient.post('/inventory/items/', data);
return response.data;
},

partialUpdate: async (
id: string | number,
data: UpdateItemData
): Promise<AdminCollectionItem> => {
const response = await apiClient.patch(`/inventory/items/${id}/`, data);
return response.data;
},
};


export const publicItemsApi = {
getAll: async (params?: ItemFilter): Promise<PublicCollectionItem[]> => {
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 [];
},

getById: async (id: string | number): Promise<PublicCollectionItem> => {
const response = await apiClient.get(`/inventory/public/items/${id}/`);
return response.data;
}
},
};
6 changes: 4 additions & 2 deletions frontend/src/components/common/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
radius?: "xs" | "sm" | "md" | "lg" | "xl"
icon?: keyof typeof iconMap
layout?: "default" | "stacked"
hideMobile?: boolean
}

export function Button({
Expand All @@ -32,19 +33,20 @@ export function Button({
layout = "default",
className,
type,
hideMobile = false,
children,
...props
}: ButtonProps) {

const resolvedType = type ?? "button"

const classes: string[] = [
"inline-flex items-center justify-center font-medium transition",
hideMobile ? "hidden md:inline-flex" : "inline-flex",
"items-center justify-center font-medium transition",
"outline-none",
"focus-visible:ring-2 focus-visible:ring-offset-2",
"disabled:opacity-50 disabled:pointer-events-none",
]

/* ---------- Radius ---------- */
if (radius) classes.push(`rounded-${radius}`)
else classes.push("rounded")
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/components/items/CatalogueSearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const CatalogueSearchBar: React.FC<SearchBarProps> = ({ filters, setFilters }) =
const handler = setTimeout(() => {
setFilters((prev) => ({
...prev,
search: searchValue.trim() || null
search: searchValue.trim() || undefined
}));
}, 300);

Expand All @@ -24,13 +24,12 @@ const CatalogueSearchBar: React.FC<SearchBarProps> = ({ filters, setFilters }) =


const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.trim();
setSearchValue(value);
setSearchValue(e.target.value);
};

const handlePlatformChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value.trim();
setFilters(prev => ({ ...prev, platform: value || null }));
setFilters(prev => ({ ...prev, platform: value || undefined }));
};

const handleFloorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -42,7 +41,7 @@ const CatalogueSearchBar: React.FC<SearchBarProps> = ({ filters, setFilters }) =

const handleOrderingChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value.trim();
setFilters(prev => ({ ...prev, ordering: value || null }));
setFilters(prev => ({ ...prev, ordering: value || undefined }));
};


Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/items/DetailRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function DetailRow({ label, value }: { label: string; value: string }) {
return (
<div className="flex items-center justify-between px-4 py-3">
<span className="text-xs font-semibold text-secondary tracking-wide">{label}</span>
<span className="text-sm text-primary">{value}</span>
</div>
)
}

export default DetailRow;
Loading
Loading