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:
Luca Sacchi Ricciardi
2026-04-07 14:58:46 +02:00
parent b18728f0f9
commit 991908ba62
41 changed files with 5482 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const DropdownMenu = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ children, ...props }, ref) => {
const [open, setOpen] = React.useState(false)
return (
<div ref={ref} {...props}>
{React.Children.map(children, (child) =>
React.isValidElement(child)
? React.cloneElement(child as React.ReactElement<any>, {
open,
setOpen,
})
: child
)}
</div>
)
})
DropdownMenu.displayName = "DropdownMenu"
const DropdownMenuTrigger = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement> & { open?: boolean; setOpen?: (open: boolean) => void }
>(({ className, open, setOpen, ...props }, ref) => (
<button
ref={ref}
onClick={() => setOpen?.(!open)}
className={cn(className)}
{...props}
/>
))
DropdownMenuTrigger.displayName = "DropdownMenuTrigger"
const DropdownMenuContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & { open?: boolean; align?: "start" | "center" | "end" }
>(({ className, open, align = "center", ...props }, ref) => {
if (!open) return null
const alignClasses = {
start: "left-0",
center: "left-1/2 -translate-x-1/2",
end: "right-0",
}
return (
<div
ref={ref}
className={cn(
"absolute z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
alignClasses[align],
className
)}
{...props}
/>
)
})
DropdownMenuContent.displayName = "DropdownMenuContent"
const DropdownMenuItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & { inset?: boolean }
>(({ className, inset, ...props }, ref) => (
<div
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = "DropdownMenuItem"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
}