251 lines
7.2 KiB
TypeScript
251 lines
7.2 KiB
TypeScript
import { useRechartDataModel } from '@/hooks/use-rechart-data-model';
|
|
import { useVisibleSeries } from '@/hooks/use-visible-series';
|
|
import { useTRPC } from '@/integrations/trpc/react';
|
|
import { pushModal } from '@/modals';
|
|
import type { IChartData } from '@/trpc/client';
|
|
import { cn } from '@/utils/cn';
|
|
import { getChartColor } from '@/utils/theme';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { isSameDay, isSameHour, isSameMonth, isSameWeek } from 'date-fns';
|
|
import { last } from 'ramda';
|
|
import React, { useCallback } from 'react';
|
|
import {
|
|
Area,
|
|
CartesianGrid,
|
|
ComposedChart,
|
|
Customized,
|
|
Legend,
|
|
Line,
|
|
ReferenceLine,
|
|
ResponsiveContainer,
|
|
Tooltip,
|
|
XAxis,
|
|
YAxis,
|
|
} from 'recharts';
|
|
|
|
import { useDashedStroke } from '@/hooks/use-dashed-stroke';
|
|
import { useXAxisProps, useYAxisProps } from '../common/axis';
|
|
import { SolidToDashedGradient } from '../common/linear-gradient';
|
|
import { ReportChartTooltip } from '../common/report-chart-tooltip';
|
|
import { ReportTable } from '../common/report-table';
|
|
import { SerieIcon } from '../common/serie-icon';
|
|
import { SerieName } from '../common/serie-name';
|
|
import { useReportChartContext } from '../context';
|
|
|
|
interface Props {
|
|
data: IChartData;
|
|
}
|
|
|
|
export function Chart({ data }: Props) {
|
|
const {
|
|
report: {
|
|
previous,
|
|
interval,
|
|
projectId,
|
|
startDate,
|
|
endDate,
|
|
range,
|
|
lineType,
|
|
},
|
|
isEditMode,
|
|
options: { hideXAxis, hideYAxis },
|
|
} = useReportChartContext();
|
|
const trpc = useTRPC();
|
|
const references = useQuery(
|
|
trpc.reference.getChartReferences.queryOptions(
|
|
{
|
|
projectId,
|
|
startDate,
|
|
endDate,
|
|
range,
|
|
},
|
|
{
|
|
staleTime: 1000 * 60 * 10,
|
|
},
|
|
),
|
|
);
|
|
const { series, setVisibleSeries } = useVisibleSeries(data);
|
|
const rechartData = useRechartDataModel(series);
|
|
|
|
let dotIndex = undefined;
|
|
if (range === 'today') {
|
|
// Find closest index based on times
|
|
dotIndex = rechartData.findIndex((item) => {
|
|
return isSameHour(item.date, new Date());
|
|
});
|
|
}
|
|
|
|
const lastSerieDataItem = last(series[0]?.data || [])?.date || new Date();
|
|
const useDashedLastLine = (() => {
|
|
if (range === 'today') {
|
|
return true;
|
|
}
|
|
|
|
if (interval === 'hour') {
|
|
return isSameHour(lastSerieDataItem, new Date());
|
|
}
|
|
|
|
if (interval === 'day') {
|
|
return isSameDay(lastSerieDataItem, new Date());
|
|
}
|
|
|
|
if (interval === 'month') {
|
|
return isSameMonth(lastSerieDataItem, new Date());
|
|
}
|
|
|
|
if (interval === 'week') {
|
|
return isSameWeek(lastSerieDataItem, new Date());
|
|
}
|
|
|
|
return false;
|
|
})();
|
|
|
|
const CustomLegend = useCallback(() => {
|
|
return (
|
|
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-xs mt-4 -mb-2">
|
|
{series.map((serie) => (
|
|
<div
|
|
className="flex items-center gap-1"
|
|
key={serie.id}
|
|
style={{
|
|
color: getChartColor(serie.index),
|
|
}}
|
|
>
|
|
<SerieIcon name={serie.names} />
|
|
<SerieName name={serie.names} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}, [series]);
|
|
|
|
const yAxisProps = useYAxisProps({
|
|
hide: hideYAxis,
|
|
});
|
|
const xAxisProps = useXAxisProps({
|
|
hide: hideXAxis,
|
|
interval,
|
|
});
|
|
|
|
const handleChartClick = useCallback((e: any) => {
|
|
if (e?.activePayload?.[0]) {
|
|
const clickedData = e.activePayload[0].payload;
|
|
if (clickedData.date) {
|
|
pushModal('AddReference', {
|
|
datetime: new Date(clickedData.date).toISOString(),
|
|
});
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
const { getStrokeDasharray, calcStrokeDasharray, handleAnimationEnd } =
|
|
useDashedStroke({
|
|
dotIndex,
|
|
});
|
|
|
|
return (
|
|
<ReportChartTooltip.TooltipProvider references={references.data}>
|
|
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
|
<ResponsiveContainer>
|
|
<ComposedChart data={rechartData} onClick={handleChartClick}>
|
|
<Customized component={calcStrokeDasharray} />
|
|
<Line
|
|
dataKey="calcStrokeDasharray"
|
|
legendType="none"
|
|
animationDuration={0}
|
|
onAnimationEnd={handleAnimationEnd}
|
|
/>
|
|
<CartesianGrid
|
|
strokeDasharray="3 3"
|
|
horizontal={true}
|
|
vertical={false}
|
|
className="stroke-border"
|
|
/>
|
|
{references.data?.map((ref) => (
|
|
<ReferenceLine
|
|
key={ref.id}
|
|
x={ref.date.getTime()}
|
|
stroke={'oklch(from var(--foreground) l c h / 0.1)'}
|
|
strokeDasharray={'3 3'}
|
|
label={{
|
|
value: ref.title,
|
|
position: 'centerTop',
|
|
fill: '#334155',
|
|
fontSize: 12,
|
|
}}
|
|
fontSize={10}
|
|
/>
|
|
))}
|
|
<YAxis {...yAxisProps} />
|
|
<XAxis {...xAxisProps} />
|
|
<Legend content={<CustomLegend />} />
|
|
<Tooltip content={<ReportChartTooltip.Tooltip />} />
|
|
{series.map((serie) => {
|
|
const color = getChartColor(serie.index);
|
|
return (
|
|
<defs key={`defs-${serie.id}`}>
|
|
<linearGradient
|
|
id={`color${color}`}
|
|
x1="0"
|
|
y1="0"
|
|
x2="0"
|
|
y2="1"
|
|
>
|
|
<stop offset="0%" stopColor={color} stopOpacity={0.8} />
|
|
<stop offset={'100%'} stopColor={color} stopOpacity={0.1} />
|
|
</linearGradient>
|
|
</defs>
|
|
);
|
|
})}
|
|
{series.map((serie) => {
|
|
const color = getChartColor(serie.index);
|
|
return (
|
|
<Area
|
|
key={serie.id}
|
|
stackId="1"
|
|
type={lineType}
|
|
name={serie.id}
|
|
dataKey={`${serie.id}:count`}
|
|
strokeDasharray={
|
|
useDashedLastLine
|
|
? getStrokeDasharray(`${serie.id}:count`)
|
|
: undefined
|
|
}
|
|
fill={`url(#color${color})`}
|
|
isAnimationActive={false}
|
|
fillOpacity={0.7}
|
|
/>
|
|
);
|
|
})}
|
|
{previous &&
|
|
series.map((serie) => {
|
|
const color = getChartColor(serie.index);
|
|
return (
|
|
<Area
|
|
key={`${serie.id}:prev`}
|
|
stackId="2"
|
|
type={lineType}
|
|
name={`${serie.id}:prev`}
|
|
dataKey={`${serie.id}:prev:count`}
|
|
stroke={color}
|
|
fill={color}
|
|
fillOpacity={0.3}
|
|
strokeOpacity={0.3}
|
|
isAnimationActive={false}
|
|
/>
|
|
);
|
|
})}
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
{isEditMode && (
|
|
<ReportTable
|
|
data={data}
|
|
visibleSeries={series}
|
|
setVisibleSeries={setVisibleSeries}
|
|
/>
|
|
)}
|
|
</ReportChartTooltip.TooltipProvider>
|
|
);
|
|
}
|