# πŸš€ @frontend-dev - React Frontend Implementation ## πŸ“Š Stato Progetto **Data:** 2026-04-07 **Fase:** 2 - Frontend Development **Backend:** βœ… COMPLETATO ### βœ… Cosa Γ¨ pronto - **Backend API** completa con tutti gli endpoint - **Database** PostgreSQL con dati di esempio - **Documentazione API** disponibile su `/docs` - **CORS** abilitato per comunicazione frontend-backend ### 🎯 I tuoi task (PrioritΓ  P1) --- ## FE-001: Setup Ambiente React **Stima:** M (1-2 ore) ### Obiettivo Configurare l'ambiente di sviluppo React con Vite, TypeScript, Tailwind CSS e shadcn/ui. ### Files da creare/configurare ``` frontend/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ components/ β”‚ β”œβ”€β”€ hooks/ β”‚ β”œβ”€β”€ lib/ β”‚ β”œβ”€β”€ pages/ β”‚ β”œβ”€β”€ services/ β”‚ β”œβ”€β”€ types/ β”‚ β”œβ”€β”€ App.tsx β”‚ └── main.tsx β”œβ”€β”€ public/ β”œβ”€β”€ index.html β”œβ”€β”€ package.json β”œβ”€β”€ tsconfig.json β”œβ”€β”€ vite.config.ts β”œβ”€β”€ tailwind.config.js └── components.json ``` ### Implementazione richiesta **1. Setup iniziale:** ```bash cd /home/google/Sources/LucaSacchiNet/mockupAWS mkdir -p frontend cd frontend # Inizializza progetto Vite con React + TypeScript npm create vite@latest . -- --template react-ts # Installa dipendenze base npm install # Installa Tailwind CSS npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p # Installa shadcn/ui npx shadcn-ui@latest init # Installa librerie necessarie npm install @tanstack/react-query axios recharts lucide-react clsx tailwind-merge npm install react-router-dom npm install -D @types/node ``` **2. `tailwind.config.js`:** ```javascript /** @type {import('tailwindcss').Config} */ export default { darkMode: ["class"], content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))", }, card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, }, }, plugins: [require("tailwindcss-animate")], } ``` **3. `src/index.css`:** ```css @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 212.7 26.8% 83.9%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } } ``` **4. Installa componenti shadcn/ui necessari:** ```bash npx shadcn-ui@latest add button npx shadcn-ui@latest add card npx shadcn-ui@latest add input npx shadcn-ui@latest add label npx shadcn-ui@latest add select npx shadcn-ui@latest add badge npx shadcn-ui@latest add table npx shadcn-ui@latest add dialog npx shadcn-ui@latest add dropdown-menu npx shadcn-ui@latest add tabs npx shadcn-ui@latest add toast n``` ### Criteri di accettazione - [ ] Vite + React + TypeScript configurato - [ ] Tailwind CSS funzionante - [ ] shadcn/ui inizializzato - [ ] Componenti base installati - [ ] `npm run dev` avvia senza errori --- ## FE-002: Configurazione API Client **Stima:** S (30-60 min) **Dipende da:** FE-001 ### Obiettivo Configurare Axios per chiamate API al backend. ### Files da creare ``` frontend/src/ β”œβ”€β”€ lib/ β”‚ └── api.ts β”œβ”€β”€ types/ β”‚ └── api.ts └── .env ``` ### Implementazione richiesta **1. `frontend/.env`:** ```env VITE_API_URL=http://localhost:8000/api/v1 ``` **2. `frontend/src/lib/api.ts`:** ```typescript import axios from 'axios'; const api = axios.create({ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1', headers: { 'Content-Type': 'application/json', }, }); // Request interceptor api.interceptors.request.use( (config) => { // Add auth headers here if needed return config; }, (error) => { return Promise.reject(error); } ); // Response interceptor api.interceptors.response.use( (response) => response, (error) => { // Handle errors globally console.error('API Error:', error.response?.data || error.message); return Promise.reject(error); } ); export default api; ``` **3. `frontend/src/types/api.ts`:** ```typescript export interface Scenario { id: string; name: string; description?: string; tags: string[]; status: 'draft' | 'running' | 'completed' | 'archived'; region: string; created_at: string; updated_at: string; completed_at?: string; started_at?: string; total_requests: number; total_cost_estimate: number; } export interface ScenarioCreate { name: string; description?: string; tags?: string[]; region: string; } export interface ScenarioUpdate { name?: string; description?: string; tags?: string[]; } export interface ScenarioList { items: Scenario[]; total: number; page: number; page_size: number; } export interface MetricSummary { total_requests: number; total_cost_usd: number; sqs_blocks: number; lambda_invocations: number; llm_tokens: number; pii_violations: number; } export interface CostBreakdown { service: string; cost_usd: number; percentage: number; } export interface MetricsResponse { scenario_id: string; summary: MetricSummary; cost_breakdown: CostBreakdown[]; timeseries: { timestamp: string; metric_type: string; value: number; }[]; } ``` ### Criteri di accettazione - [ ] Axios configurato con base URL - [ ] Interceptors per errori - [ ] TypeScript types definiti - [ ] Test chiamata API funzionante --- ## FE-003: React Query Hooks **Stima:** M (1-2 ore) **Dipende da:** FE-002 ### Obiettivo Creare hooks per fetchare dati dal backend usando React Query. ### Files da creare ``` frontend/src/ β”œβ”€β”€ hooks/ β”‚ β”œβ”€β”€ useScenarios.ts β”‚ β”œβ”€β”€ useScenario.ts β”‚ └── useMetrics.ts └── providers/ └── QueryProvider.tsx ``` ### Implementazione richiesta **1. `frontend/src/providers/QueryProvider.tsx`:** ```typescript import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactNode } from 'react'; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 1, refetchOnWindowFocus: false, }, }, }); export function QueryProvider({ children }: { children: ReactNode }) { return ( {children} ); } ``` **2. `frontend/src/hooks/useScenarios.ts`:** ```typescript 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({ 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({ 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] }); }, }); } ``` **3. `frontend/src/hooks/useMetrics.ts`:** ```typescript 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({ 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 }); } ``` ### Criteri di accettazione - [ ] QueryProvider configurato - [ ] useScenarios con pagination/filters - [ ] useScenario per dettaglio - [ ] Mutations per CRUD operations - [ ] Cache invalidation funzionante --- ## FE-004: Layout e Navigazione **Stima:** M (1-2 ore) **Dipende da:** FE-003 ### Obiettivo Creare layout principale con header, sidebar e routing. ### Files da creare ``` frontend/src/ β”œβ”€β”€ components/ β”‚ β”œβ”€β”€ layout/ β”‚ β”‚ β”œβ”€β”€ Layout.tsx β”‚ β”‚ β”œβ”€β”€ Header.tsx β”‚ β”‚ └── Sidebar.tsx β”‚ └── ui/ # shadcn components β”œβ”€β”€ pages/ β”‚ β”œβ”€β”€ Dashboard.tsx β”‚ β”œβ”€β”€ ScenariosPage.tsx β”‚ β”œβ”€β”€ ScenarioDetail.tsx β”‚ └── NotFound.tsx β”œβ”€β”€ App.tsx └── main.tsx ``` ### Implementazione richiesta **1. `frontend/src/components/layout/Layout.tsx`:** ```typescript import { Outlet } from 'react-router-dom'; import { Header } from './Header'; import { Sidebar } from './Sidebar'; export function Layout() { return (
); } ``` **2. `frontend/src/components/layout/Header.tsx`:** ```typescript import { Link } from 'react-router-dom'; import { Cloud, Menu } from 'lucide-react'; import { Button } from '@/components/ui/button'; export function Header() { return (
mockupAWS
AWS Cost Simulator
); } ``` **3. `frontend/src/components/layout/Sidebar.tsx`:** ```typescript import { NavLink } from 'react-router-dom'; import { LayoutDashboard, List } from 'lucide-react'; const navItems = [ { to: '/', label: 'Dashboard', icon: LayoutDashboard }, { to: '/scenarios', label: 'Scenarios', icon: List }, ]; export function Sidebar() { return ( ); } ``` **4. `frontend/src/App.tsx`:** ```typescript import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { QueryProvider } from './providers/QueryProvider'; import { Toaster } from '@/components/ui/toaster'; import { Layout } from './components/layout/Layout'; import { Dashboard } from './pages/Dashboard'; import { ScenariosPage } from './pages/ScenariosPage'; import { ScenarioDetail } from './pages/ScenarioDetail'; import { NotFound } from './pages/NotFound'; function App() { return ( }> } /> } /> } /> } /> ); } export default App; ``` **5. `frontend/src/main.tsx`:** ```typescript import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './index.css'; ReactDOM.createRoot(document.getElementById('root')!).render( ); ``` ### Criteri di accettazione - [ ] Layout con Header e Sidebar - [ ] React Router configurato - [ ] Navigazione funzionante - [ ] Responsive design - [ ] Active state su menu --- ## FE-005: Dashboard Page **Stima:** L (2-4 ore) **Dipende da:** FE-004 ### Obiettivo Creare la pagina dashboard con overview e statistiche. ### Files da creare ``` frontend/src/pages/Dashboard.tsx frontend/src/components/dashboard/ β”œβ”€β”€ StatCard.tsx β”œβ”€β”€ RecentScenarios.tsx └── CostOverview.tsx ``` ### Implementazione richiesta **1. `frontend/src/components/dashboard/StatCard.tsx`:** ```typescript import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { LucideIcon } from 'lucide-react'; interface StatCardProps { title: string; value: string | number; description?: string; icon: LucideIcon; trend?: { value: number; label: string; }; } export function StatCard({ title, value, description, icon: Icon, trend }: StatCardProps) { return ( {title}
{value}
{description && (

{description}

)} {trend && (
= 0 ? 'text-green-600' : 'text-red-600'}`}> {trend.value >= 0 ? '+' : ''}{trend.value}% {trend.label}
)}
); } ``` **2. `frontend/src/pages/Dashboard.tsx`:** ```typescript import { useScenarios } from '@/hooks/useScenarios'; import { StatCard } from '@/components/dashboard/StatCard'; import { RecentScenarios } from '@/components/dashboard/RecentScenarios'; import { Activity, DollarSign, Server, AlertTriangle } from 'lucide-react'; export function Dashboard() { const { data: scenarios, isLoading } = useScenarios(1, 100); const totalScenarios = scenarios?.total || 0; const runningScenarios = scenarios?.items.filter(s => s.status === 'running').length || 0; const totalCost = scenarios?.items.reduce((sum, s) => sum + s.total_cost_estimate, 0) || 0; if (isLoading) { return
Loading...
; } return (

Dashboard

Overview of your AWS cost simulation scenarios

{/* Add CostChart component here */}
); } ``` ### Criteri di accettazione - [ ] Stat cards con dati reali - [ ] Loading states - [ ] Lista scenari recenti - [ ] Layout responsive --- ## FE-006: Scenarios List Page **Stima:** L (2-4 ore) **Dipende da:** FE-005 ### Obiettivo Pagina lista scenari con filtri, pagination e azioni. ### Files da creare ``` frontend/src/pages/ScenariosPage.tsx frontend/src/components/scenarios/ β”œβ”€β”€ ScenarioTable.tsx β”œβ”€β”€ ScenarioFilters.tsx β”œβ”€β”€ CreateScenarioDialog.tsx └── DeleteScenarioDialog.tsx ``` ### Implementazione richiesta **1. `frontend/src/components/scenarios/ScenarioTable.tsx`:** ```typescript import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { MoreHorizontal, Play, Square, Archive, Trash2 } from 'lucide-react'; import type { Scenario } from '@/types/api'; interface ScenarioTableProps { scenarios: Scenario[]; onStart: (id: string) => void; onStop: (id: string) => void; onArchive: (id: string) => void; onDelete: (id: string) => void; } const statusColors = { draft: 'secondary', running: 'default', completed: 'outline', archived: 'destructive', } as const; export function ScenarioTable({ scenarios, onStart, onStop, onArchive, onDelete, }: ScenarioTableProps) { const navigate = useNavigate(); return ( Name Status Region Requests Cost Actions {scenarios.map((scenario) => ( navigate(`/scenarios/${scenario.id}`)} > {scenario.name} {scenario.status} {scenario.region} {scenario.total_requests} ${scenario.total_cost_estimate.toFixed(6)} e.stopPropagation()}> {scenario.status === 'draft' && ( onStart(scenario.id)}> Start )} {scenario.status === 'running' && ( onStop(scenario.id)}> Stop )} {scenario.status === 'completed' && ( onArchive(scenario.id)}> Archive )} onDelete(scenario.id)} > Delete ))}
); } ``` ### Criteri di accettazione - [ ] Tabella con dati reali - [ ] Filtri per status e regione - [ ] Pagination - [ ] Azioni per ogni scenario - [ ] Confirm dialog per delete --- ## πŸ“‹ Checklist Completamento Prima di procedere, verifica: - [ ] Tutti i componenti creati - [ ] TypeScript types corretti - [ ] Error handling appropriato - [ ] Loading states - [ ] Responsive design - [ ] API integration funzionante --- ## πŸ§ͺ Testing ```bash # Installa dipendenze cd frontend npm install # Avvia dev server npm run dev # Build produzione npm run build ``` --- ## πŸš€ Comandi Utili ```bash # Aggiungi nuovo componente shadcn npx shadcn-ui@latest add [component-name] # Build npm run build # Preview build npm run preview ``` --- **@frontend-dev: INIZIA ORA! πŸš€** Procedi in ordine: FE-001 β†’ FE-002 β†’ FE-003 β†’ FE-004 β†’ FE-005 β†’ FE-006 **Domande?** Riferiti alla documentazione API su `http://localhost:8000/docs` **Commit convenzioni:** - `feat: setup React environment with Vite and Tailwind` - `feat: add API client with Axios` - `feat: implement React Query hooks` - `feat: add layout and navigation` - `feat: create dashboard page` - `feat: add scenarios list page` **Buon lavoro! πŸ’ͺ**