Full enterprise web operating system including: - Next.js 14 frontend with App Router, i18n (DE/EN), shadcn/ui - NestJS 10 backend with Prisma, JWT auth, Swagger docs - Keycloak 24 SSO with role-based access control - HR module (employees, time tracking, absences, org chart) - LEAN module (3S planning, morning meeting SQCDM, skill matrix) - Integrations module (PlentyONE, Zulip, Todoist, FreeScout, Nextcloud, ecoDMS, GembaDocs) - Dashboard with customizable drag & drop widget grid - Docker Compose infrastructure (PostgreSQL 16, Redis 7, Keycloak 24) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
3.1 KiB
TypeScript
128 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
LineChart as RechartsLineChart,
|
|
Line,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer,
|
|
Legend,
|
|
} from 'recharts';
|
|
|
|
import { ChartContainer, ChartEmptyState } from './chart-container';
|
|
|
|
interface LineChartDataPoint {
|
|
name: string;
|
|
[key: string]: string | number;
|
|
}
|
|
|
|
interface LineChartSeries {
|
|
dataKey: string;
|
|
name: string;
|
|
color: string;
|
|
strokeWidth?: number;
|
|
dot?: boolean;
|
|
dashed?: boolean;
|
|
}
|
|
|
|
interface LineChartProps {
|
|
/** Chart title */
|
|
title: string;
|
|
/** Optional description */
|
|
description?: string;
|
|
/** Data points to display */
|
|
data: LineChartDataPoint[];
|
|
/** Series configuration */
|
|
series: LineChartSeries[];
|
|
/** Whether data is loading */
|
|
isLoading?: boolean;
|
|
/** Chart height */
|
|
height?: number;
|
|
/** Whether to show grid lines */
|
|
showGrid?: boolean;
|
|
/** Whether to show legend */
|
|
showLegend?: boolean;
|
|
/** Whether to use curved lines */
|
|
curved?: boolean;
|
|
/** Additional CSS classes */
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Line Chart component using Recharts
|
|
* Supports single or multiple series with various styling options
|
|
*/
|
|
export function LineChart({
|
|
title,
|
|
description,
|
|
data,
|
|
series,
|
|
isLoading = false,
|
|
height = 300,
|
|
showGrid = true,
|
|
showLegend = true,
|
|
curved = true,
|
|
className,
|
|
}: LineChartProps) {
|
|
if (!isLoading && data.length === 0) {
|
|
return (
|
|
<ChartContainer title={title} description={description} height={height} className={className}>
|
|
<ChartEmptyState />
|
|
</ChartContainer>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ChartContainer
|
|
title={title}
|
|
description={description}
|
|
isLoading={isLoading}
|
|
height={height}
|
|
className={className}
|
|
>
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<RechartsLineChart data={data} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
|
|
{showGrid && (
|
|
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" opacity={0.5} />
|
|
)}
|
|
|
|
<XAxis
|
|
dataKey="name"
|
|
className="text-xs"
|
|
tick={{ fill: 'hsl(var(--muted-foreground))' }}
|
|
/>
|
|
|
|
<YAxis className="text-xs" tick={{ fill: 'hsl(var(--muted-foreground))' }} />
|
|
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: 'hsl(var(--popover))',
|
|
border: '1px solid hsl(var(--border))',
|
|
borderRadius: '0.5rem',
|
|
color: 'hsl(var(--popover-foreground))',
|
|
}}
|
|
/>
|
|
|
|
{showLegend && <Legend wrapperStyle={{ paddingTop: '20px' }} />}
|
|
|
|
{series.map((s) => (
|
|
<Line
|
|
key={s.dataKey}
|
|
type={curved ? 'monotone' : 'linear'}
|
|
dataKey={s.dataKey}
|
|
name={s.name}
|
|
stroke={s.color}
|
|
strokeWidth={s.strokeWidth || 2}
|
|
strokeDasharray={s.dashed ? '5 5' : undefined}
|
|
dot={s.dot !== false}
|
|
activeDot={{ r: 6, strokeWidth: 2 }}
|
|
/>
|
|
))}
|
|
</RechartsLineChart>
|
|
</ResponsiveContainer>
|
|
</ChartContainer>
|
|
);
|
|
}
|