feat(frontend): implement complete React frontend with Vite, TypeScript, and Tailwind
Complete frontend implementation (FE-001 to FE-006): FE-001: Setup Ambiente React - Initialize Vite + React + TypeScript project - Configure Tailwind CSS with custom theme - Add shadcn/ui components (Button, Card, Badge, Table, DropdownMenu, Toaster) - Install dependencies: axios, react-query, react-router-dom, lucide-react, etc. - Configure path aliases (@/components, @/lib, etc.) FE-002: Configurazione API Client - Create lib/api.ts with Axios instance - Add TypeScript types for Scenario, Metrics, etc. - Configure environment variable VITE_API_URL FE-003: React Query Hooks - Create QueryProvider with React Query client - Add useScenarios hook with pagination/filters - Add useScenario hook for detail view - Add mutations: create, update, delete, start, stop - Add useMetrics hook with auto-refresh - Implement cache invalidation FE-004: Layout e Navigazione - Create Layout component with Header and Sidebar - Configure React Router with routes: * / - Dashboard * /scenarios - Scenarios list * /scenarios/:id - Scenario detail - Implement responsive navigation - Add active state styling FE-005: Dashboard Page - Create Dashboard with stat cards - Display total scenarios, running count, total cost, PII violations - Use real data from useScenarios hook - Add loading states FE-006: Scenarios List Page - Create ScenariosPage with data table - Display scenario name, status (with badge), region, requests, cost - Add action dropdown (Start, Stop, Delete) - Implement navigation to detail view Components Created: - ui/button.tsx - Button component with variants - ui/card.tsx - Card component with header/content/footer - ui/badge.tsx - Badge component for status - ui/table.tsx - Table component - ui/dropdown-menu.tsx - Dropdown menu - ui/toaster.tsx - Toast notifications Pages Created: - Dashboard.tsx - Main dashboard view - ScenariosPage.tsx - List of scenarios - ScenarioDetail.tsx - Scenario detail with metrics - NotFound.tsx - 404 page All features integrated with backend API. Tasks: FE-001, FE-002, FE-003, FE-004, FE-005, FE-006 complete
This commit is contained in:
17
frontend/src/hooks/useMetrics.ts
Normal file
17
frontend/src/hooks/useMetrics.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import api from '@/lib/api';
|
||||
import type { MetricsResponse } from '@/types/api';
|
||||
|
||||
const METRICS_KEY = 'metrics';
|
||||
|
||||
export function useMetrics(scenarioId: string) {
|
||||
return useQuery<MetricsResponse>({
|
||||
queryKey: [METRICS_KEY, scenarioId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/scenarios/${scenarioId}/metrics`);
|
||||
return response.data;
|
||||
},
|
||||
enabled: !!scenarioId,
|
||||
refetchInterval: 5000, // Refresh every 5 seconds for running scenarios
|
||||
});
|
||||
}
|
||||
102
frontend/src/hooks/useScenarios.ts
Normal file
102
frontend/src/hooks/useScenarios.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import api from '@/lib/api';
|
||||
import type { Scenario, ScenarioCreate, ScenarioUpdate, ScenarioList } from '@/types/api';
|
||||
|
||||
const SCENARIOS_KEY = 'scenarios';
|
||||
|
||||
export function useScenarios(page = 1, pageSize = 20, status?: string, region?: string) {
|
||||
return useQuery<ScenarioList>({
|
||||
queryKey: [SCENARIOS_KEY, page, pageSize, status, region],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', page.toString());
|
||||
params.append('page_size', pageSize.toString());
|
||||
if (status) params.append('status', status);
|
||||
if (region) params.append('region', region);
|
||||
|
||||
const response = await api.get(`/scenarios?${params.toString()}`);
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useScenario(id: string) {
|
||||
return useQuery<Scenario>({
|
||||
queryKey: [SCENARIOS_KEY, id],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/scenarios/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateScenario() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: ScenarioCreate) => {
|
||||
const response = await api.post('/scenarios', data);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [SCENARIOS_KEY] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateScenario(id: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: ScenarioUpdate) => {
|
||||
const response = await api.put(`/scenarios/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [SCENARIOS_KEY] });
|
||||
queryClient.invalidateQueries({ queryKey: [SCENARIOS_KEY, id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteScenario() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
await api.delete(`/scenarios/${id}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [SCENARIOS_KEY] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useStartScenario(id: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await api.post(`/scenarios/${id}/start`);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [SCENARIOS_KEY, id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useStopScenario(id: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await api.post(`/scenarios/${id}/stop`);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [SCENARIOS_KEY, id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user