- Updated kiosk stats API with expanded metrics - New components: company-tickets-card, gauge-chart, service-desk-card, ticket-leaders-card - Updated cycling-display, kpi-card, and ticker components - Added performance.css for kiosk optimizations - Added Wulf logo asset
122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
interface GaugeChartProps {
|
|
value: number;
|
|
max: number;
|
|
label: string;
|
|
breakpoints: {
|
|
green: number;
|
|
yellow: number;
|
|
red: number;
|
|
};
|
|
}
|
|
|
|
export function GaugeChart({ value, max, label, breakpoints }: GaugeChartProps) {
|
|
// Calculate percentages for color segments
|
|
const greenPercent = (breakpoints.green / max) * 100;
|
|
const yellowPercent = ((breakpoints.yellow - breakpoints.green) / max) * 100;
|
|
const redPercent = ((max - breakpoints.yellow) / max) * 100;
|
|
|
|
// Calculate needle angle (gauge goes from -90 to 90 degrees, 180 total)
|
|
const percentage = Math.min((value / max) * 100, 100);
|
|
const angle = -90 + (percentage / 100) * 180;
|
|
|
|
// SVG arc path helper
|
|
const createArc = (startAngle: number, endAngle: number, radius: number) => {
|
|
const start = polarToCartesian(100, 100, radius, endAngle);
|
|
const end = polarToCartesian(100, 100, radius, startAngle);
|
|
const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';
|
|
return `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 0 ${end.x} ${end.y}`;
|
|
};
|
|
|
|
const polarToCartesian = (centerX: number, centerY: number, radius: number, angleInDegrees: number) => {
|
|
const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;
|
|
return {
|
|
x: centerX + radius * Math.cos(angleInRadians),
|
|
y: centerY + radius * Math.sin(angleInRadians),
|
|
};
|
|
};
|
|
|
|
// Calculate segment angles
|
|
const greenEndAngle = -90 + (greenPercent / 100) * 180;
|
|
const yellowEndAngle = greenEndAngle + (yellowPercent / 100) * 180;
|
|
|
|
return (
|
|
<div className="flex flex-col items-center">
|
|
<svg viewBox="0 0 200 140" className="w-full max-w-[300px]">
|
|
{/* Background arc */}
|
|
<path
|
|
d={createArc(-90, 90, 70)}
|
|
fill="none"
|
|
stroke="rgba(59, 130, 246, 0.2)"
|
|
strokeWidth="20"
|
|
strokeLinecap="round"
|
|
/>
|
|
|
|
{/* Green segment */}
|
|
<path
|
|
d={createArc(-90, greenEndAngle, 70)}
|
|
fill="none"
|
|
stroke="#22c55e"
|
|
strokeWidth="20"
|
|
strokeLinecap="round"
|
|
/>
|
|
|
|
{/* Yellow segment */}
|
|
<path
|
|
d={createArc(greenEndAngle, yellowEndAngle, 70)}
|
|
fill="none"
|
|
stroke="#eab308"
|
|
strokeWidth="20"
|
|
strokeLinecap="round"
|
|
/>
|
|
|
|
{/* Red segment */}
|
|
<path
|
|
d={createArc(yellowEndAngle, 90, 70)}
|
|
fill="none"
|
|
stroke="#ef4444"
|
|
strokeWidth="20"
|
|
strokeLinecap="round"
|
|
/>
|
|
|
|
{/* Needle */}
|
|
<g transform={`rotate(${angle} 100 100)`}>
|
|
<line
|
|
x1="100"
|
|
y1="100"
|
|
x2="100"
|
|
y2="35"
|
|
stroke="#1e293b"
|
|
strokeWidth="4"
|
|
strokeLinecap="round"
|
|
/>
|
|
<circle cx="100" cy="100" r="8" fill="#1e293b" />
|
|
</g>
|
|
|
|
{/* Center value */}
|
|
<text
|
|
x="100"
|
|
y="105"
|
|
textAnchor="middle"
|
|
className="text-4xl font-bold fill-blue-500"
|
|
>
|
|
{value}
|
|
</text>
|
|
|
|
{/* Min/Max labels */}
|
|
<text x="20" y="130" textAnchor="start" className="text-sm fill-gray-400">
|
|
0
|
|
</text>
|
|
<text x="180" y="130" textAnchor="end" className="text-sm fill-gray-400">
|
|
{max}
|
|
</text>
|
|
</svg>
|
|
|
|
{/* Label */}
|
|
<div className="text-xl font-semibold text-gray-300 text-center mt-2">
|
|
{label}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|