Complete remaining todos: integration tests, E2E tests, REST API, data visualization, database abstraction, monitoring
- Added comprehensive integration tests for all packages - Set up Playwright for E2E testing - Created REST API with Express - Added data visualization components (Bar, Line, Pie charts) - Created database abstraction layer - Added health check and monitoring endpoints - Created API documentation
This commit is contained in:
133
apps/web/src/components/Charts.tsx
Normal file
133
apps/web/src/components/Charts.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import React from 'react';
|
||||
|
||||
// Simple chart components using CSS/SVG
|
||||
// In production, would use Recharts or Chart.js
|
||||
|
||||
interface BarChartProps {
|
||||
data: Array<{ label: string; value: number }>;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function BarChart({ data, title }: BarChartProps) {
|
||||
const maxValue = Math.max(...data.map((d) => d.value), 1);
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
{title && <h3 className="text-lg font-semibold mb-4">{title}</h3>}
|
||||
<div className="space-y-2">
|
||||
{data.map((item, index) => (
|
||||
<div key={index} className="flex items-center">
|
||||
<div className="w-24 text-sm text-gray-600 truncate">{item.label}</div>
|
||||
<div className="flex-1 mx-4">
|
||||
<div className="bg-gray-200 rounded-full h-6 relative">
|
||||
<div
|
||||
className="bg-blue-600 h-6 rounded-full flex items-center justify-end pr-2"
|
||||
style={{ width: `${(item.value / maxValue) * 100}%` }}
|
||||
>
|
||||
<span className="text-xs text-white font-medium">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface LineChartProps {
|
||||
data: Array<{ date: string; value: number }>;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function LineChart({ data, title }: LineChartProps) {
|
||||
const maxValue = Math.max(...data.map((d) => d.value), 1);
|
||||
const minValue = Math.min(...data.map((d) => d.value), 0);
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
{title && <h3 className="text-lg font-semibold mb-4">{title}</h3>}
|
||||
<div className="h-64 relative">
|
||||
<svg className="w-full h-full" viewBox="0 0 400 200" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#3b82f6"
|
||||
strokeWidth="2"
|
||||
points={data
|
||||
.map(
|
||||
(item, index) =>
|
||||
`${(index / (data.length - 1)) * 400},${
|
||||
200 - ((item.value - minValue) / (maxValue - minValue)) * 200
|
||||
}`
|
||||
)
|
||||
.join(' ')}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs text-gray-500 mt-2">
|
||||
{data.map((item, index) => (
|
||||
<span key={index}>{new Date(item.date).toLocaleDateString()}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface PieChartProps {
|
||||
data: Array<{ label: string; value: number; color?: string }>;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function PieChart({ data, title }: PieChartProps) {
|
||||
const total = data.reduce((sum, item) => sum + item.value, 0);
|
||||
let currentAngle = 0;
|
||||
|
||||
const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'];
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
{title && <h3 className="text-lg font-semibold mb-4">{title}</h3>}
|
||||
<div className="flex items-center">
|
||||
<svg width="200" height="200" viewBox="0 0 200 200">
|
||||
{data.map((item, index) => {
|
||||
const percentage = (item.value / total) * 100;
|
||||
const angle = (percentage / 100) * 360;
|
||||
const startAngle = currentAngle;
|
||||
currentAngle += angle;
|
||||
|
||||
const x1 = 100 + 80 * Math.cos((startAngle * Math.PI) / 180);
|
||||
const y1 = 100 + 80 * Math.sin((startAngle * Math.PI) / 180);
|
||||
const x2 = 100 + 80 * Math.cos((currentAngle * Math.PI) / 180);
|
||||
const y2 = 100 + 80 * Math.sin((currentAngle * Math.PI) / 180);
|
||||
const largeArc = angle > 180 ? 1 : 0;
|
||||
|
||||
return (
|
||||
<path
|
||||
key={index}
|
||||
d={`M 100 100 L ${x1} ${y1} A 80 80 0 ${largeArc} 1 ${x2} ${y2} Z`}
|
||||
fill={item.color || colors[index % colors.length]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
<div className="ml-6 space-y-2">
|
||||
{data.map((item, index) => (
|
||||
<div key={index} className="flex items-center">
|
||||
<div
|
||||
className="w-4 h-4 rounded mr-2"
|
||||
style={{
|
||||
backgroundColor: item.color || colors[index % colors.length],
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm">
|
||||
{item.label}: {((item.value / total) * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user