Mobile PWA port of the desktop console: bottom-sheet modals, card-list record views, bottom tab nav, vite-plugin-pwa (manifest, service worker, icons). API layer, workflow UIDs, and design tokens reused verbatim.
88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
import { Card } from './Card';
|
|
import {
|
|
BarChart,
|
|
Bar,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer
|
|
} from 'recharts';
|
|
|
|
export interface ChartRow {
|
|
dimension: string;
|
|
value: number;
|
|
}
|
|
|
|
export interface ChartConfig {
|
|
chart_uid: string;
|
|
key: string;
|
|
rows: ChartRow[];
|
|
}
|
|
|
|
export interface AnalyticsChartProps {
|
|
data?: unknown[];
|
|
}
|
|
|
|
export function AnalyticsChart({ data }: AnalyticsChartProps) {
|
|
if (!data || !Array.isArray(data) || data.length === 0) return null;
|
|
|
|
const charts = data as ChartConfig[];
|
|
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899', '#14B8A6'];
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 w-full">
|
|
{charts.map((chart, idx) => {
|
|
const title = (chart.key || `Chart ${idx + 1}`)
|
|
.replace(/_/g, ' ')
|
|
.replace(/\b\w/g, c => c.toUpperCase());
|
|
|
|
// Safely format dimension string (fallbacks for empty strings)
|
|
const formattedRows = (chart.rows || []).map(r => ({
|
|
...r,
|
|
dimension: String(r.dimension || '').trim() || 'Unknown'
|
|
}));
|
|
|
|
if (formattedRows.length === 0) return null;
|
|
|
|
return (
|
|
<Card key={chart.chart_uid || idx} title={title} className="shadow-sm border-t-4 border-t-indigo-500">
|
|
<div className="h-[320px] w-full mt-4">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<BarChart data={formattedRows} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#E2E8F0" />
|
|
<XAxis
|
|
dataKey="dimension"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: '#64748B' }}
|
|
dy={10}
|
|
/>
|
|
<YAxis
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: '#64748B' }}
|
|
allowDecimals={false}
|
|
/>
|
|
<Tooltip
|
|
cursor={{ fill: '#F8FAFC' }}
|
|
contentStyle={{ borderRadius: '8px', border: '1px solid #E2E8F0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '14px', fontFamily: 'inherit' }}
|
|
labelStyle={{ fontWeight: 'bold', color: '#0F172A', marginBottom: '4px' }}
|
|
/>
|
|
<Bar
|
|
dataKey="value"
|
|
name="Value"
|
|
fill={colors[idx % colors.length]}
|
|
radius={[4, 4, 0, 0]}
|
|
maxBarSize={40}
|
|
/>
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|