Add complete multi-theme dashboard system including: - 8 selectable themes with persistent per-browser preferences - Theme bootstrap script to prevent FOUC on page load - 12+ reusable chart components (area, bar, donut, radial gauge, etc) - Custom checkbox UI component - New settings page for appearance customization Other changes: - Optimize supplier map API payload with getMappableSuppliers helper - Add aria-label attributes to back buttons on detail pages for better accessibility - Update next.config.ts to include new local development IP 192.168.1.128 - Update site manifest with all new favicon and PWA assets - Fix styling and layout spacing in rewrite quote modal
58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Sparkline — tiny inline-SVG line chart for KPI cards.
|
|
* Includes gradient fill below the line + endpoint dot.
|
|
*/
|
|
export function Sparkline({
|
|
values,
|
|
color,
|
|
width = 96,
|
|
height = 36,
|
|
}: {
|
|
values: number[];
|
|
color: string;
|
|
width?: number;
|
|
height?: number;
|
|
}) {
|
|
if (values.length === 0) {
|
|
return <div style={{ width, height }} aria-hidden />;
|
|
}
|
|
const max = Math.max(...values, 1);
|
|
const min = Math.min(...values, 0);
|
|
const range = max - min || 1;
|
|
const step = values.length > 1 ? width / (values.length - 1) : width;
|
|
const points = values
|
|
.map((v, i) => {
|
|
const x = i * step;
|
|
const y = height - ((v - min) / range) * height;
|
|
return `${x.toFixed(2)},${y.toFixed(2)}`;
|
|
})
|
|
.join(" ");
|
|
const areaPoints = `0,${height} ${points} ${width},${height}`;
|
|
const id = `spark-${color.replace("#", "")}-${Math.round(width)}`;
|
|
const last = values[values.length - 1];
|
|
const lastX = (values.length - 1) * step;
|
|
const lastY = height - ((last - min) / range) * height;
|
|
return (
|
|
<svg width={width} height={height} className="overflow-visible shrink-0">
|
|
<defs>
|
|
<linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor={color} stopOpacity={0.45} />
|
|
<stop offset="100%" stopColor={color} stopOpacity={0} />
|
|
</linearGradient>
|
|
</defs>
|
|
<polygon points={areaPoints} fill={`url(#${id})`} />
|
|
<polyline
|
|
points={points}
|
|
fill="none"
|
|
stroke={color}
|
|
strokeWidth={2}
|
|
strokeLinejoin="round"
|
|
strokeLinecap="round"
|
|
/>
|
|
<circle cx={lastX} cy={lastY} r={3.5} fill={color} />
|
|
<circle cx={lastX} cy={lastY} r={6} fill={color} fillOpacity={0.25} />
|
|
</svg>
|
|
);
|
|
}
|