CUB-133: Build Dashboard page with summary cards
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m10s
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m10s
This commit is contained in:
139
frontend/src/pages/Dashboard.tsx
Normal file
139
frontend/src/pages/Dashboard.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getFilaments, getPrintJobs } from '../services/api'
|
||||
import SummaryCard from '../components/SummaryCard'
|
||||
import RecentPrints from '../components/RecentPrints'
|
||||
import LoadingSpinner from '../components/LoadingSpinner'
|
||||
import { Package, AlertTriangle, Printer, DollarSign, Plus, List } from 'lucide-react'
|
||||
|
||||
export default function Dashboard() {
|
||||
const {
|
||||
data: filamentData,
|
||||
isLoading: filamentLoading,
|
||||
error: filamentError,
|
||||
} = useQuery({
|
||||
queryKey: ['filaments', 'count'],
|
||||
queryFn: () => getFilaments({ limit: 1 }),
|
||||
})
|
||||
|
||||
const {
|
||||
data: lowStockData,
|
||||
isLoading: lowStockLoading,
|
||||
error: lowStockError,
|
||||
} = useQuery({
|
||||
queryKey: ['filaments', 'lowStock'],
|
||||
queryFn: () => getFilaments({ low_stock: true, limit: 1 }),
|
||||
})
|
||||
|
||||
const {
|
||||
data: recentPrints,
|
||||
isLoading: printsLoading,
|
||||
error: printsError,
|
||||
} = useQuery({
|
||||
queryKey: ['printJobs', 'recent'],
|
||||
queryFn: () => getPrintJobs({ limit: 5 }),
|
||||
})
|
||||
|
||||
const {
|
||||
data: allPrints,
|
||||
isLoading: costLoading,
|
||||
error: costError,
|
||||
} = useQuery({
|
||||
queryKey: ['printJobs', 'all'],
|
||||
queryFn: () => getPrintJobs({ limit: 1000 }),
|
||||
})
|
||||
|
||||
const totalSpools = filamentData?.total ?? 0
|
||||
const lowStockCount = lowStockData?.total ?? 0
|
||||
const recentPrintJobs = recentPrints?.data ?? []
|
||||
|
||||
// Calculate cost this month from all print jobs
|
||||
const now = new Date()
|
||||
const currentMonth = now.getMonth()
|
||||
const currentYear = now.getFullYear()
|
||||
const monthlyCost =
|
||||
allPrints?.data
|
||||
.filter((job) => {
|
||||
const d = new Date(job.started_at)
|
||||
return d.getMonth() === currentMonth && d.getFullYear() === currentYear
|
||||
})
|
||||
.reduce((sum, job) => sum + (job.cost_usd ?? 0), 0) ?? 0
|
||||
|
||||
const isLoading = filamentLoading || lowStockLoading || printsLoading || costLoading
|
||||
const error = filamentError || lowStockError || printsError || costError
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-slate-900">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-slate-900">
|
||||
<div className="p-6 rounded-lg bg-red-900/30 border border-red-500 text-red-200 max-w-md">
|
||||
<h2 className="text-xl font-bold mb-2">Error</h2>
|
||||
<p className="text-sm">{(error as Error).message || 'Failed to load dashboard data.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-900 p-4 md:p-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-emerald-400 mb-2">Dashboard</h1>
|
||||
<p className="text-slate-400">Overview of your Extrudex inventory and prints</p>
|
||||
</header>
|
||||
|
||||
{/* Summary Cards Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<SummaryCard
|
||||
title="Total Spools"
|
||||
value={totalSpools}
|
||||
icon={Package}
|
||||
color="emerald"
|
||||
/>
|
||||
<SummaryCard
|
||||
title="Low Stock"
|
||||
value={lowStockCount}
|
||||
icon={AlertTriangle}
|
||||
color={lowStockCount > 0 ? 'amber' : 'emerald'}
|
||||
/>
|
||||
<SummaryCard
|
||||
title="Recent Prints"
|
||||
value={recentPrintJobs.length}
|
||||
icon={Printer}
|
||||
color="sky"
|
||||
/>
|
||||
<SummaryCard
|
||||
title="Cost This Month"
|
||||
value={`$${monthlyCost.toFixed(2)}`}
|
||||
icon={DollarSign}
|
||||
color="violet"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="flex flex-wrap gap-3 mb-8">
|
||||
<button className="flex items-center gap-2 px-4 py-2 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white font-medium transition-colors active:scale-95 touch-manipulation">
|
||||
<Plus size={18} />
|
||||
Add Spool
|
||||
</button>
|
||||
<button className="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 text-slate-100 font-medium transition-colors active:scale-95 touch-manipulation">
|
||||
<List size={18} />
|
||||
View Inventory
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Recent Prints Section */}
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-slate-100 mb-4">Recent Prints</h2>
|
||||
<RecentPrints jobs={recentPrintJobs} />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user