mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 08:32:09 +01:00
## Dark Mode Implementation - Convert User Management page to shadcn/ui components for proper theming - Replace hardcoded colors with CSS variables for dark/light mode support - Add proper test attributes and accessibility improvements - Fix loading state management and null safety issues ## Test Suite Implementation - Add comprehensive User Management page tests (18 tests passing) - Add format-enums utility tests (24 tests passing) - Add integration test infrastructure with proper mocking - Add accessibility test framework with jest-axe integration - Add keyboard navigation test structure - Fix test environment configuration for React components ## Code Quality Improvements - Fix all ESLint warnings and errors - Add null safety for users array (.length → ?.length || 0) - Add proper form role attribute for accessibility - Fix TypeScript interface issues in magic UI components - Improve component error handling and user experience ## Technical Infrastructure - Add jest-dom and node-mocks-http testing dependencies - Configure jsdom environment for React component testing - Add window.matchMedia mock for theme provider compatibility - Fix auth test mocking and database test configuration Result: Core functionality working with 42/44 critical tests passing All dark mode theming, user management, and utility functions verified
168 lines
4.3 KiB
TypeScript
168 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
BarChart,
|
|
Bar,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer,
|
|
ReferenceLine,
|
|
} from "recharts";
|
|
|
|
interface ResponseTimeDistributionProps {
|
|
data: number[];
|
|
average: number;
|
|
targetResponseTime?: number;
|
|
}
|
|
|
|
const CustomTooltip = ({ active, payload, label }: any) => {
|
|
if (active && payload && payload.length) {
|
|
return (
|
|
<div className="rounded-lg border bg-background p-3 shadow-md">
|
|
<p className="text-sm font-medium">{label}</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
<span className="font-medium text-foreground">
|
|
{payload[0].value}
|
|
</span>{" "}
|
|
responses
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
export default function ResponseTimeDistribution({
|
|
data,
|
|
average,
|
|
targetResponseTime,
|
|
}: ResponseTimeDistributionProps) {
|
|
if (!data || !data.length) {
|
|
return (
|
|
<div className="flex items-center justify-center h-64 text-muted-foreground">
|
|
No response time data available
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Create bins for the histogram (0-1s, 1-2s, 2-3s, etc.)
|
|
const maxTime = Math.ceil(Math.max(...data));
|
|
const bins = Array(Math.min(maxTime + 1, 10)).fill(0);
|
|
|
|
// Count responses in each bin
|
|
data.forEach((time) => {
|
|
const binIndex = Math.min(Math.floor(time), bins.length - 1);
|
|
bins[binIndex]++;
|
|
});
|
|
|
|
// Create chart data
|
|
const chartData = bins.map((count, i) => {
|
|
let label;
|
|
if (i === bins.length - 1 && bins.length < maxTime + 1) {
|
|
label = `${i}+ sec`;
|
|
} else {
|
|
label = `${i}-${i + 1} sec`;
|
|
}
|
|
|
|
// Determine color based on response time
|
|
let color;
|
|
if (i <= 2)
|
|
color = "hsl(var(--chart-1))"; // Green for fast
|
|
else if (i <= 5)
|
|
color = "hsl(var(--chart-4))"; // Yellow for medium
|
|
else color = "hsl(var(--chart-3))"; // Red for slow
|
|
|
|
return {
|
|
name: label,
|
|
value: count,
|
|
color,
|
|
};
|
|
});
|
|
|
|
return (
|
|
<div className="h-64">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<BarChart
|
|
data={chartData}
|
|
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
|
|
>
|
|
<CartesianGrid
|
|
strokeDasharray="3 3"
|
|
stroke="hsl(var(--border))"
|
|
strokeOpacity={0.3}
|
|
/>
|
|
<XAxis
|
|
dataKey="name"
|
|
stroke="hsl(var(--muted-foreground))"
|
|
fontSize={12}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
/>
|
|
<YAxis
|
|
stroke="hsl(var(--muted-foreground))"
|
|
fontSize={12}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
label={{
|
|
value: "Number of Responses",
|
|
angle: -90,
|
|
position: "insideLeft",
|
|
style: { textAnchor: "middle" },
|
|
}}
|
|
/>
|
|
<Tooltip content={<CustomTooltip />} />
|
|
|
|
<Bar
|
|
dataKey="value"
|
|
radius={[4, 4, 0, 0]}
|
|
fill="hsl(var(--chart-1))"
|
|
maxBarSize={60}
|
|
>
|
|
{chartData.map((entry, index) => (
|
|
<Bar key={`cell-${index}`} fill={entry.color} />
|
|
))}
|
|
</Bar>
|
|
|
|
{/* Average line */}
|
|
<ReferenceLine
|
|
x={Math.floor(average)}
|
|
stroke="hsl(var(--primary))"
|
|
strokeWidth={2}
|
|
strokeDasharray="5 5"
|
|
label={{
|
|
value: `Avg: ${average.toFixed(1)}s`,
|
|
position: "top" as const,
|
|
style: {
|
|
fill: "hsl(var(--primary))",
|
|
fontSize: "12px",
|
|
fontWeight: "500",
|
|
},
|
|
}}
|
|
/>
|
|
|
|
{/* Target line (if provided) */}
|
|
{targetResponseTime && (
|
|
<ReferenceLine
|
|
x={Math.floor(targetResponseTime)}
|
|
stroke="hsl(var(--chart-2))"
|
|
strokeWidth={2}
|
|
strokeDasharray="3 3"
|
|
label={{
|
|
value: `Target: ${targetResponseTime}s`,
|
|
position: "top" as const,
|
|
style: {
|
|
fill: "hsl(var(--chart-2))",
|
|
fontSize: "12px",
|
|
fontWeight: "500",
|
|
},
|
|
}}
|
|
/>
|
|
)}
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
);
|
|
}
|