Implement fullstack education catalog with disease detail pages, API routes, and shared types

- Add disease detail page component with data fetching and error handling
- Create shared types for diseases and classifications
- Implement API routes for diseases, classifications, and dashboard summary
- Develop reusable components for risk badge and disease card
- Build catalog page with search and filter functionality
- Update dashboard page with data-backed summary and manual classification form
- Register new routes in the web application
- Ensure type safety and consistency across shared modules
This commit is contained in:
Asep Haryana Saputra
2026-05-22 14:26:13 +00:00
parent d0c17d67b2
commit 84c1f46841
24 changed files with 2378 additions and 54 deletions
+59
View File
@@ -0,0 +1,59 @@
import type {
DiseaseSlug,
DiseaseCatalogItem,
ManualClassificationRequest,
ManualClassificationRecord,
DashboardSummary,
} from '@zeavis/shared';
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? '';
async function fetchApi<T>(endpoint: string, options?: RequestInit): Promise<T> {
const url = `${apiBaseUrl}${endpoint}`;
const response = await fetch(url, options);
if (!response.ok) {
let errorMessage = `HTTP ${response.status}`;
try {
const errorData = await response.json();
if (errorData.error) {
errorMessage = errorData.error;
}
} catch {
// Response is not JSON, use default error message
}
throw new Error(errorMessage);
}
return response.json();
}
export const apiClient = {
async getDiseases(): Promise<DiseaseCatalogItem[]> {
return fetchApi('/api/v1/diseases');
},
async getDisease(slug: DiseaseSlug): Promise<DiseaseCatalogItem> {
return fetchApi(`/api/v1/diseases/${slug}`);
},
async getManualClassifications(): Promise<ManualClassificationRecord[]> {
return fetchApi('/api/v1/classifications/manual');
},
async createManualClassification(
payload: ManualClassificationRequest,
): Promise<ManualClassificationRecord> {
return fetchApi('/api/v1/classifications/manual', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
},
async getDashboardSummary(): Promise<DashboardSummary> {
return fetchApi('/api/v1/dashboard/summary');
},
};