All checks were successful
Dev Build / build-test (pull_request) Successful in 2m10s
140 lines
4.4 KiB
TypeScript
140 lines
4.4 KiB
TypeScript
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>
|
|
)
|
|
}
|