mirror of
https://github.com/logos-storage/das-research.git
synced 2026-01-07 07:33:09 +00:00
286 lines
11 KiB
TypeScript
286 lines
11 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { format, parseISO } from "date-fns"
|
|
import { Calendar, ChevronRight, Filter, Search } from "lucide-react"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
import { useSimulation } from "./simulation-provider"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
|
import { Calendar as CalendarComponent } from "@/components/ui/calendar"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Checkbox } from "@/components/ui/checkbox"
|
|
|
|
export function SimulationList() {
|
|
const router = useRouter()
|
|
const { simulations, loading, error } = useSimulation()
|
|
const [searchTerm, setSearchTerm] = useState("")
|
|
const [dateRange, setDateRange] = useState<{ from: Date | undefined; to: Date | undefined }>({
|
|
from: undefined,
|
|
to: undefined,
|
|
})
|
|
const [showFilters, setShowFilters] = useState(false)
|
|
const [successFilter, setSuccessFilter] = useState<string | null>(null)
|
|
|
|
const handleSimulationSelect = (id: string) => {
|
|
router.push(`/simulations/${id}`)
|
|
}
|
|
|
|
// Función auxiliar para formatear fechas de manera segura
|
|
const formatDate = (dateString: string) => {
|
|
try {
|
|
// Corregir formato incorrecto con :00Z al final
|
|
if (dateString.includes('T') && dateString.endsWith(':00Z')) {
|
|
dateString = dateString.replace(':00Z', 'Z');
|
|
}
|
|
|
|
// Intenta parsear la fecha desde ISO
|
|
const date = parseISO(dateString);
|
|
return format(date, "PPP");
|
|
} catch (error) {
|
|
console.error("Error formatting date:", dateString, error);
|
|
|
|
// Si falla, intenta otro enfoque: crear una fecha a partir del ID de simulación
|
|
try {
|
|
if (dateString.includes("_")) {
|
|
// Si la fecha es parte del ID (como en "2025-02-11_00-11-23_825")
|
|
const parts = dateString.split("_");
|
|
if (parts.length >= 2) {
|
|
const datePart = parts[0];
|
|
const timePart = parts[1].replace(/-/g, ":");
|
|
return `${datePart} ${timePart}`;
|
|
}
|
|
}
|
|
|
|
// Si todo lo anterior falla, intentar crear una fecha simple
|
|
return new Date(dateString).toLocaleDateString();
|
|
} catch (e) {
|
|
// Si todo falla, devuelve un marcador de posición
|
|
return "Date unavailable";
|
|
}
|
|
}
|
|
};
|
|
|
|
const filteredSimulations = simulations.filter((sim) => {
|
|
// Search filter
|
|
if (searchTerm && !sim.id.toLowerCase().includes(searchTerm.toLowerCase())) {
|
|
return false
|
|
}
|
|
|
|
// Date filter
|
|
if (dateRange.from) {
|
|
try {
|
|
const simDate = new Date(sim.date);
|
|
if (simDate < dateRange.from) return false;
|
|
} catch (e) {
|
|
// Si la fecha no se puede parsear, mantenemos el elemento
|
|
console.warn("Could not parse date for filtering:", sim.date);
|
|
}
|
|
}
|
|
if (dateRange.to) {
|
|
try {
|
|
const simDate = new Date(sim.date);
|
|
if (simDate > dateRange.to) return false;
|
|
} catch (e) {
|
|
// Si la fecha no se puede parsear, mantenemos el elemento
|
|
console.warn("Could not parse date for filtering:", sim.date);
|
|
}
|
|
}
|
|
|
|
// Success rate filter
|
|
if (successFilter === "high" && sim.successRate < 75) {
|
|
return false
|
|
}
|
|
if (successFilter === "medium" && (sim.successRate < 50 || sim.successRate >= 75)) {
|
|
return false
|
|
}
|
|
if (successFilter === "low" && sim.successRate >= 50) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center p-8">
|
|
<h2 className="text-xl font-semibold text-red-500 mb-2">Error</h2>
|
|
<p className="text-muted-foreground">{error}</p>
|
|
<Button className="mt-4" onClick={() => window.location.reload()}>
|
|
Try Again
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between">
|
|
<h2 className="text-2xl font-bold tracking-tight">Simulation Runs</h2>
|
|
<div className="flex gap-2 w-full sm:w-auto">
|
|
<div className="relative w-full sm:w-auto">
|
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
<Input
|
|
type="search"
|
|
placeholder="Search simulations..."
|
|
className="w-full sm:w-[250px] pl-8"
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
<Popover open={showFilters} onOpenChange={setShowFilters}>
|
|
<PopoverTrigger asChild>
|
|
<Button variant="outline" size="icon">
|
|
<Filter className="h-4 w-4" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80">
|
|
<div className="grid gap-4">
|
|
<div className="space-y-2">
|
|
<h4 className="font-medium leading-none">Filters</h4>
|
|
<p className="text-sm text-muted-foreground">Filter simulation results by date and success rate</p>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>Date Range</Label>
|
|
<CalendarComponent
|
|
mode="range"
|
|
selected={dateRange}
|
|
onSelect={setDateRange as any}
|
|
className="rounded-md border"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>Success Rate</Label>
|
|
<div className="grid gap-2">
|
|
<div className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id="success-high"
|
|
checked={successFilter === "high"}
|
|
onCheckedChange={() => setSuccessFilter(successFilter === "high" ? null : "high")}
|
|
/>
|
|
<label htmlFor="success-high" className="text-sm">
|
|
High (75-100%)
|
|
</label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id="success-medium"
|
|
checked={successFilter === "medium"}
|
|
onCheckedChange={() => setSuccessFilter(successFilter === "medium" ? null : "medium")}
|
|
/>
|
|
<label htmlFor="success-medium" className="text-sm">
|
|
Medium (50-75%)
|
|
</label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id="success-low"
|
|
checked={successFilter === "low"}
|
|
onCheckedChange={() => setSuccessFilter(successFilter === "low" ? null : "low")}
|
|
/>
|
|
<label htmlFor="success-low" className="text-sm">
|
|
Low (0-50%)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
onClick={() => {
|
|
setDateRange({ from: undefined, to: undefined })
|
|
setSuccessFilter(null)
|
|
}}
|
|
>
|
|
Reset Filters
|
|
</Button>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{[1, 2, 3, 4, 5, 6].map((i) => (
|
|
<Card key={i} className="overflow-hidden">
|
|
<CardHeader className="pb-2">
|
|
<Skeleton className="h-5 w-1/2 mb-2" />
|
|
<Skeleton className="h-4 w-3/4" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<Skeleton className="h-4 w-full" />
|
|
<Skeleton className="h-4 w-full" />
|
|
<Skeleton className="h-4 w-full" />
|
|
<Skeleton className="h-4 w-full" />
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter>
|
|
<Skeleton className="h-9 w-full" />
|
|
</CardFooter>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
) : filteredSimulations.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center p-8 border rounded-lg">
|
|
<h3 className="text-lg font-semibold mb-2">No simulations found</h3>
|
|
<p className="text-muted-foreground text-center">
|
|
{searchTerm || dateRange.from || dateRange.to || successFilter
|
|
? "Try adjusting your filters to see more results."
|
|
: "No simulation runs are available. Run a simulation to get started."}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{filteredSimulations.map((simulation) => (
|
|
<Card key={simulation.id} className="overflow-hidden">
|
|
<CardHeader className="pb-2">
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className="h-4 w-4 text-muted-foreground" />
|
|
<CardDescription>{formatDate(simulation.date || simulation.id)}</CardDescription>
|
|
</div>
|
|
<CardTitle className="text-lg">{simulation.id}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 gap-y-2 text-sm">
|
|
<div className="text-muted-foreground">Nodes:</div>
|
|
<div>
|
|
{simulation.parameters.numberNodes.min}-{simulation.parameters.numberNodes.max}
|
|
</div>
|
|
<div className="text-muted-foreground">Failure Rate:</div>
|
|
<div>
|
|
{simulation.parameters.failureRate.min}%-{simulation.parameters.failureRate.max}%
|
|
</div>
|
|
<div className="text-muted-foreground">Success Rate:</div>
|
|
<div className="flex items-center">
|
|
<Badge
|
|
className={
|
|
simulation.successRate >= 75
|
|
? "bg-green-500"
|
|
: simulation.successRate >= 50
|
|
? "bg-yellow-500"
|
|
: "bg-red-500"
|
|
}
|
|
>
|
|
{simulation.successRate}%
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter>
|
|
<Button className="w-full" onClick={() => handleSimulationSelect(simulation.id)}>
|
|
View Details
|
|
<ChevronRight className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
} |