Files
HartOMat/frontend/src/components/dashboard/widgets/RevenueChartWidget.tsx
T
Hartmut f15b035b88 feat(L1): modular widget dashboard — 15 configurable widgets
Replaces monolithic AdminDashboard/ClientDashboard with a per-user
configurable widget grid. 15 widget types: ProductionStats, QueueStatus,
RecentRenders, CostOverview, WorkerStatus, KPISummary, OrderThroughput,
Revenue, ItemStatus, ProcessingTimes, RenderTimeByOutputType,
OutputTypeUsage, TopProducts, OrdersByUser, RenderBackendStats.

DashboardTimeframeContext provides shared timeframe state. Dashboard
config persisted in DB via GET/PUT /api/dashboard/config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 23:11:13 +01:00

82 lines
3.5 KiB
TypeScript

import { useQuery } from '@tanstack/react-query'
import {
ResponsiveContainer, BarChart, Bar, Cell,
XAxis, YAxis, CartesianGrid, Tooltip,
} from 'recharts'
import { getDashboardKPIs } from '../../../api/analytics'
import { useDashboardTimeframe } from '../DashboardTimeframeContext'
const TOOLTIP_STYLE = {
backgroundColor: 'var(--color-bg-surface)',
border: '1px solid var(--color-border)',
borderRadius: '8px',
color: 'var(--color-text)',
}
const CATEGORY_COLORS = ['#00893d', '#6366f1', '#f59e0b', '#3b82f6', '#8b5cf6', '#14b8a6', '#f43f5e', '#06b6d4']
export default function RevenueChartWidget() {
const { dateFrom, dateTo } = useDashboardTimeframe()
const { data, isLoading, error } = useQuery({
queryKey: ['analytics-kpis', dateFrom, dateTo],
queryFn: () => getDashboardKPIs(dateFrom, dateTo),
staleTime: 60_000,
})
if (isLoading) return <div className="space-y-3"><div className="h-40 animate-pulse rounded-lg bg-surface-muted" /><div className="h-40 animate-pulse rounded-lg bg-surface-muted" /></div>
if (error) return <p className="text-xs text-red-500">Failed to load revenue</p>
if (!data) return null
return (
<div className="space-y-4">
<div>
<p className="text-xs font-medium text-content-secondary mb-2">Revenue per Month ()</p>
{data.revenue.length === 0 ? (
<p className="text-xs text-content-muted text-center py-4">No data yet</p>
) : (
<ResponsiveContainer width="100%" height={160}>
<BarChart data={data.revenue} margin={{ top: 4, right: 8, left: -16, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
<XAxis dataKey="month" tick={{ fill: 'var(--color-text-muted)', fontSize: 10 }} />
<YAxis tick={{ fill: 'var(--color-text-muted)', fontSize: 10 }} />
<Tooltip
contentStyle={TOOLTIP_STYLE}
formatter={(v: number | undefined) =>
v != null ? [`${v.toFixed(2)}`, 'Revenue'] : ['—', 'Revenue']
}
/>
<Bar dataKey="revenue" fill="#00893d" radius={[3, 3, 0, 0]} />
</BarChart>
</ResponsiveContainer>
)}
</div>
<div>
<p className="text-xs font-medium text-content-secondary mb-2">Revenue by Category ()</p>
{data.category_revenue.length === 0 ? (
<p className="text-xs text-content-muted text-center py-4">No data yet</p>
) : (
<ResponsiveContainer width="100%" height={160}>
<BarChart data={data.category_revenue} margin={{ top: 4, right: 8, left: -16, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
<XAxis dataKey="category" tick={{ fill: 'var(--color-text-muted)', fontSize: 10 }} />
<YAxis tick={{ fill: 'var(--color-text-muted)', fontSize: 10 }} />
<Tooltip
contentStyle={TOOLTIP_STYLE}
formatter={(v: number | undefined) =>
v != null ? [`${v.toFixed(2)}`, 'Revenue'] : ['—', 'Revenue']
}
/>
<Bar dataKey="revenue" radius={[3, 3, 0, 0]}>
{data.category_revenue.map((_, i) => (
<Cell key={i} fill={CATEGORY_COLORS[i % CATEGORY_COLORS.length]} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
)}
</div>
</div>
)
}