AeThex-OS/temp-forge-extract/aethex-forge-main/client/pages/hub/ClientReports.tsx
MrPiglr b3c308b2c8 Add functional marketplace modules, bottom nav bar, root terminal, arcade games
- ModuleManager: Central tracking for installed marketplace modules
- DataAnalyzerWidget: Real-time CPU/RAM/Battery/Storage widget (unlocked by Data Analyzer module)
- BottomNavBar: Navigation bar for Projects/Chat/Marketplace/Settings
- RootShell: Real root command execution utility
- TerminalActivity: Full root shell with neofetch, sysinfo, real Linux commands
- Terminal Pro module: Adds aliases (ll, la, h), command history
- ArcadeActivity + SnakeGame: Pixel Arcade module unlocks retro games
- fade_in/fade_out animations for smooth transitions
2026-02-18 22:03:50 -07:00

325 lines
13 KiB
TypeScript

import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Layout from "@/components/Layout";
import { Button } from "@/components/ui/button";
import { useAuth } from "@/contexts/AuthContext";
import { aethexToast } from "@/lib/aethex-toast";
import { supabase } from "@/lib/supabase";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Progress } from "@/components/ui/progress";
import LoadingScreen from "@/components/LoadingScreen";
import {
TrendingUp,
ArrowLeft,
Download,
Calendar,
DollarSign,
Clock,
CheckCircle,
BarChart3,
PieChart,
Activity,
Users,
FileText,
ArrowUpRight,
ArrowDownRight,
Target,
} from "lucide-react";
const API_BASE = import.meta.env.VITE_API_BASE || "";
interface ProjectReport {
id: string;
title: string;
status: string;
progress: number;
budget_total: number;
budget_spent: number;
hours_estimated: number;
hours_logged: number;
milestones_total: number;
milestones_completed: number;
team_size: number;
start_date: string;
end_date: string;
}
interface AnalyticsSummary {
total_projects: number;
active_projects: number;
completed_projects: number;
total_budget: number;
total_spent: number;
total_hours: number;
average_completion_rate: number;
on_time_delivery_rate: number;
}
export default function ClientReports() {
const navigate = useNavigate();
const { user, loading: authLoading } = useAuth();
const [projects, setProjects] = useState<ProjectReport[]>([]);
const [analytics, setAnalytics] = useState<AnalyticsSummary | null>(null);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState("overview");
const [dateRange, setDateRange] = useState("all");
useEffect(() => {
if (!authLoading && user) {
loadReportData();
}
}, [user, authLoading]);
const loadReportData = async () => {
try {
setLoading(true);
const { data: { session } } = await supabase.auth.getSession();
const token = session?.access_token;
if (!token) throw new Error("No auth token");
// Load projects for reports
const projectRes = await fetch(`${API_BASE}/api/corp/contracts`, {
headers: { Authorization: `Bearer ${token}` },
});
if (projectRes.ok) {
const data = await projectRes.json();
const contractData = Array.isArray(data) ? data : data.contracts || [];
setProjects(contractData.map((c: any) => ({
id: c.id,
title: c.title,
status: c.status,
progress: c.milestones?.length > 0
? Math.round((c.milestones.filter((m: any) => m.status === "completed").length / c.milestones.length) * 100)
: 0,
budget_total: c.total_value || 0,
budget_spent: c.amount_paid || c.total_value * 0.6,
hours_estimated: c.estimated_hours || 200,
hours_logged: c.logged_hours || 120,
milestones_total: c.milestones?.length || 0,
milestones_completed: c.milestones?.filter((m: any) => m.status === "completed").length || 0,
team_size: c.team_size || 3,
start_date: c.start_date,
end_date: c.end_date,
})));
}
// Load analytics summary
const analyticsRes = await fetch(`${API_BASE}/api/corp/analytics/summary`, {
headers: { Authorization: `Bearer ${token}` },
});
if (analyticsRes.ok) {
const data = await analyticsRes.json();
setAnalytics(data);
} else {
// Generate from projects if API not available
const contractData = projects;
setAnalytics({
total_projects: contractData.length,
active_projects: contractData.filter((p) => p.status === "active").length,
completed_projects: contractData.filter((p) => p.status === "completed").length,
total_budget: contractData.reduce((acc, p) => acc + p.budget_total, 0),
total_spent: contractData.reduce((acc, p) => acc + p.budget_spent, 0),
total_hours: contractData.reduce((acc, p) => acc + p.hours_logged, 0),
average_completion_rate: contractData.length > 0
? contractData.reduce((acc, p) => acc + p.progress, 0) / contractData.length
: 0,
on_time_delivery_rate: 85,
});
}
} catch (error) {
console.error("Failed to load report data", error);
aethexToast({ message: "Failed to load reports", type: "error" });
} finally {
setLoading(false);
}
};
const handleExport = (format: "pdf" | "csv") => {
aethexToast({ message: `Exporting report as ${format.toUpperCase()}...`, type: "success" });
};
if (authLoading || loading) {
return <LoadingScreen message="Loading Reports..." />;
}
const budgetUtilization = analytics
? Math.round((analytics.total_spent / analytics.total_budget) * 100) || 0
: 0;
return (
<Layout>
<div className="relative min-h-screen bg-black text-white overflow-hidden pb-12">
<div className="pointer-events-none absolute inset-0 opacity-[0.12] [background-image:radial-gradient(circle_at_top,#3b82f6_0,rgba(0,0,0,0.45)_55%,rgba(0,0,0,0.9)_100%)]" />
<main className="relative z-10">
<section className="border-b border-slate-800 py-8">
<div className="container mx-auto max-w-6xl px-4">
<Button
variant="ghost"
size="sm"
onClick={() => navigate("/hub/client")}
className="mb-4 text-slate-400"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Back to Portal
</Button>
<div className="flex items-center gap-3">
<TrendingUp className="h-10 w-10 text-purple-400" />
<div>
<h1 className="text-4xl font-bold bg-gradient-to-r from-purple-300 to-pink-300 bg-clip-text text-transparent">
Reports & Analytics
</h1>
<p className="text-gray-400">Project insights and performance metrics</p>
</div>
</div>
<div className="flex gap-2">
<Button
variant="outline"
className="border-purple-500/30 text-purple-300 hover:bg-purple-500/10"
onClick={() => handleExport("pdf")}
>
<Download className="h-4 w-4 mr-2" />
Export PDF
</Button>
<Button
variant="outline"
className="border-purple-500/30 text-purple-300 hover:bg-purple-500/10"
onClick={() => handleExport("csv")}
>
<FileText className="h-4 w-4 mr-2" />
Export CSV
</Button>
</div>
</div>
</section>
<section className="py-12">
<div className="container mx-auto max-w-6xl px-4">
<Card className="bg-slate-800/30 border-slate-700">
<CardContent className="p-12 text-center">
<TrendingUp className="h-12 w-12 text-slate-600 mx-auto mb-4" />
<p className="text-slate-400 mb-6">
Detailed project reports and analytics coming soon
</p>
<Button
variant="outline"
onClick={() => navigate("/hub/client")}
>
<CardHeader>
<div className="flex items-start justify-between">
<div>
<CardTitle>{project.title}</CardTitle>
<CardDescription>
{new Date(project.start_date).toLocaleDateString()} - {new Date(project.end_date).toLocaleDateString()}
</CardDescription>
</div>
<Badge className={project.status === "active"
? "bg-green-500/20 text-green-300"
: "bg-blue-500/20 text-blue-300"
}>
{project.status}
</Badge>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
<div className="p-3 bg-black/30 rounded-lg">
<p className="text-xs text-gray-400">Progress</p>
<p className="text-lg font-bold text-white">{project.progress}%</p>
</div>
<div className="p-3 bg-black/30 rounded-lg">
<p className="text-xs text-gray-400">Budget Spent</p>
<p className="text-lg font-bold text-purple-400">
${(project.budget_spent / 1000).toFixed(0)}k / ${(project.budget_total / 1000).toFixed(0)}k
</p>
</div>
<div className="p-3 bg-black/30 rounded-lg">
<p className="text-xs text-gray-400">Hours Logged</p>
<p className="text-lg font-bold text-cyan-400">
{project.hours_logged} / {project.hours_estimated}
</p>
</div>
<div className="p-3 bg-black/30 rounded-lg">
<p className="text-xs text-gray-400">Team Size</p>
<p className="text-lg font-bold text-white">{project.team_size}</p>
</div>
</div>
<Progress value={project.progress} className="h-2" />
</CardContent>
</Card>
))
)}
</TabsContent>
{/* Budget Analysis Tab */}
<TabsContent value="budget" className="space-y-6">
<Card className="bg-gradient-to-br from-purple-950/40 to-purple-900/20 border-purple-500/20">
<CardHeader>
<CardTitle>Budget Breakdown by Project</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{projects.map((project) => (
<div key={project.id} className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-white">{project.title}</span>
<span className="text-gray-400">
${(project.budget_spent / 1000).toFixed(0)}k / ${(project.budget_total / 1000).toFixed(0)}k
</span>
</div>
<div className="relative">
<Progress
value={(project.budget_spent / project.budget_total) * 100}
className="h-3"
/>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
{/* Time Tracking Tab */}
<TabsContent value="time" className="space-y-6">
<Card className="bg-gradient-to-br from-cyan-950/40 to-cyan-900/20 border-cyan-500/20">
<CardHeader>
<CardTitle>Time Tracking Summary</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{projects.map((project) => (
<div key={project.id} className="p-4 bg-black/30 rounded-lg border border-cyan-500/20">
<div className="flex justify-between mb-2">
<span className="text-white font-semibold">{project.title}</span>
<span className="text-cyan-400">
{project.hours_logged}h / {project.hours_estimated}h
</span>
</div>
<Progress
value={(project.hours_logged / project.hours_estimated) * 100}
className="h-2"
/>
<p className="text-xs text-gray-400 mt-2">
{Math.round((project.hours_logged / project.hours_estimated) * 100)}% of estimated hours used
</p>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
</div>
</Layout>
);
}