feat(T06): Mietkatalog frontend – equipment list, detail, search/filter, SSR
- composables/useApi.ts: base API client with runtimeConfig - composables/useEquipment.ts: equipment API wrapper (list, detail, categories) - components/EquipmentCard.vue: card with image/placeholder, badge, name, price, add-to-cart - pages/mietkatalog.vue: SSR list with search, category chips, sort, pagination, skeleton, empty/error states - pages/mietkatalog/[id].vue: SSR detail with specs table, related items, breadcrumb, add-to-cart - nuxt.config.ts: runtimeConfig with apiBase - 101 vitest tests passing (5 files) - Build: 0 errors, 2.09 MB
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Equipment API composable.
|
||||
* Wraps useApi to provide typed equipment list, detail, and categories endpoints.
|
||||
*/
|
||||
|
||||
export interface EquipmentItem {
|
||||
id: number;
|
||||
rentman_id: string;
|
||||
name: string;
|
||||
number: string | null;
|
||||
category: string | null;
|
||||
description: string | null;
|
||||
image_url: string | null;
|
||||
rental_price: number | null;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export interface EquipmentDetail {
|
||||
id: number;
|
||||
rentman_id: string;
|
||||
name: string;
|
||||
number: string | null;
|
||||
category: string | null;
|
||||
description: string | null;
|
||||
specifications: Record<string, unknown> | null;
|
||||
images: string[] | null;
|
||||
rental_price: number | null;
|
||||
brand: string | null;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export interface PaginatedEquipment {
|
||||
items: EquipmentItem[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
export type SortOption = "name_asc" | "name_desc";
|
||||
|
||||
export function useEquipment() {
|
||||
const api = useApi();
|
||||
|
||||
/**
|
||||
* Fetch paginated equipment list with search, category filter, and sort.
|
||||
*/
|
||||
async function list(params: {
|
||||
search?: string;
|
||||
category?: string;
|
||||
sort?: SortOption;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
} = {}): Promise<PaginatedEquipment> {
|
||||
return api.get<PaginatedEquipment>("/api/equipment", {
|
||||
search: params.search || undefined,
|
||||
category: params.category || undefined,
|
||||
sort: params.sort || "name_asc",
|
||||
page: params.page || 1,
|
||||
page_size: params.page_size || 20,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single equipment detail by ID.
|
||||
*/
|
||||
async function detail(id: number | string): Promise<EquipmentDetail> {
|
||||
return api.get<EquipmentDetail>(`/api/equipment/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all available equipment categories.
|
||||
*/
|
||||
async function categories(): Promise<string[]> {
|
||||
return api.get<string[]>("/api/equipment/categories");
|
||||
}
|
||||
|
||||
return {
|
||||
list,
|
||||
detail,
|
||||
categories,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user