fix broken retention page

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-06-20 23:40:29 +02:00
parent cf8617e809
commit a43b3be588
4 changed files with 271 additions and 270 deletions

View File

@@ -4,6 +4,7 @@ import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client'; import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { getChartColor } from '@/utils/theme'; import { getChartColor } from '@/utils/theme';
import { import {
Area, Area,
@@ -33,74 +34,76 @@ export function ReportAreaChart({ data }: ReportAreaChartProps) {
return ( return (
<> <>
<ResponsiveContainer> <div className={cn(editMode && 'card p-4')}>
{({ width, height }) => ( <ResponsiveContainer>
<AreaChart width={width} height={height} data={rechartData}> {({ width, height }) => (
<Tooltip content={<ReportChartTooltip />} /> <AreaChart width={width} height={height} data={rechartData}>
<XAxis <Tooltip content={<ReportChartTooltip />} />
axisLine={false} <XAxis
fontSize={12} axisLine={false}
dataKey="date" fontSize={12}
tickFormatter={(m: string) => formatDate(m)} dataKey="date"
tickLine={false} tickFormatter={(m: string) => formatDate(m)}
allowDuplicatedCategory={false} tickLine={false}
/> allowDuplicatedCategory={false}
<YAxis />
width={getYAxisWidth(data.metrics.max)} <YAxis
fontSize={12} width={getYAxisWidth(data.metrics.max)}
axisLine={false} fontSize={12}
tickLine={false} axisLine={false}
allowDecimals={false} tickLine={false}
tickFormatter={number.short} allowDecimals={false}
/> tickFormatter={number.short}
/>
{series.map((serie) => { {series.map((serie) => {
const color = getChartColor(serie.index); const color = getChartColor(serie.index);
return ( return (
<React.Fragment key={serie.id}> <React.Fragment key={serie.id}>
<defs> <defs>
<linearGradient <linearGradient
id={`color${color}`} id={`color${color}`}
x1="0" x1="0"
y1="0" y1="0"
x2="0" x2="0"
y2="1" y2="1"
> >
<stop <stop
offset="0%" offset="0%"
stopColor={color} stopColor={color}
stopOpacity={0.8} stopOpacity={0.8}
></stop> ></stop>
<stop <stop
offset="100%" offset="100%"
stopColor={color} stopColor={color}
stopOpacity={0.1} stopOpacity={0.1}
></stop> ></stop>
</linearGradient> </linearGradient>
</defs> </defs>
<Area <Area
key={serie.id} key={serie.id}
type={lineType} type={lineType}
isAnimationActive={false} isAnimationActive={false}
strokeWidth={2} strokeWidth={2}
dataKey={`${serie.id}:count`} dataKey={`${serie.id}:count`}
stroke={color} stroke={color}
fill={`url(#color${color})`} fill={`url(#color${color})`}
stackId={'1'} stackId={'1'}
fillOpacity={1} fillOpacity={1}
/> />
</React.Fragment> </React.Fragment>
); );
})} })}
<CartesianGrid <CartesianGrid
strokeDasharray="3 3" strokeDasharray="3 3"
horizontal={true} horizontal={true}
vertical={false} vertical={false}
className="stroke-def-200" className="stroke-def-200"
/> />
</AreaChart> </AreaChart>
)} )}
</ResponsiveContainer> </ResponsiveContainer>
</div>
{editMode && ( {editMode && (
<ReportTable <ReportTable
data={data} data={data}

View File

@@ -4,6 +4,7 @@ import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import type { IChartData } from '@/trpc/client'; import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { getChartColor, theme } from '@/utils/theme'; import { getChartColor, theme } from '@/utils/theme';
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts'; import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
@@ -40,57 +41,59 @@ export function ReportHistogramChart({ data }: ReportHistogramChartProps) {
return ( return (
<> <>
<ResponsiveContainer> <div className={cn(editMode && 'card p-4')}>
{({ width, height }) => ( <ResponsiveContainer>
<BarChart width={width} height={height} data={rechartData}> {({ width, height }) => (
<CartesianGrid <BarChart width={width} height={height} data={rechartData}>
strokeDasharray="3 3" <CartesianGrid
vertical={false} strokeDasharray="3 3"
className="stroke-def-200" vertical={false}
/> className="stroke-def-200"
<Tooltip content={<ReportChartTooltip />} cursor={<BarHover />} /> />
<XAxis <Tooltip content={<ReportChartTooltip />} cursor={<BarHover />} />
fontSize={12} <XAxis
dataKey="date" fontSize={12}
tickFormatter={formatDate} dataKey="date"
tickLine={false} tickFormatter={formatDate}
axisLine={false} tickLine={false}
/> axisLine={false}
<YAxis />
fontSize={12} <YAxis
axisLine={false} fontSize={12}
tickLine={false} axisLine={false}
width={getYAxisWidth(data.metrics.max)} tickLine={false}
allowDecimals={false} width={getYAxisWidth(data.metrics.max)}
domain={[0, data.metrics.max]} allowDecimals={false}
tickFormatter={number.short} domain={[0, data.metrics.max]}
/> tickFormatter={number.short}
{series.map((serie) => { />
return ( {series.map((serie) => {
<React.Fragment key={serie.id}> return (
{previous && ( <React.Fragment key={serie.id}>
{previous && (
<Bar
key={`${serie.id}:prev`}
name={`${serie.id}:prev`}
dataKey={`${serie.id}:prev:count`}
fill={getChartColor(serie.index)}
fillOpacity={0.2}
radius={3}
/>
)}
<Bar <Bar
key={`${serie.id}:prev`} key={serie.id}
name={`${serie.id}:prev`} name={serie.id}
dataKey={`${serie.id}:prev:count`} dataKey={`${serie.id}:count`}
fill={getChartColor(serie.index)} fill={getChartColor(serie.index)}
fillOpacity={0.2}
radius={3} radius={3}
/> />
)} </React.Fragment>
<Bar );
key={serie.id} })}
name={serie.id} </BarChart>
dataKey={`${serie.id}:count`} )}
fill={getChartColor(serie.index)} </ResponsiveContainer>
radius={3} </div>
/>
</React.Fragment>
);
})}
</BarChart>
)}
</ResponsiveContainer>
{editMode && ( {editMode && (
<ReportTable <ReportTable
data={data} data={data}

View File

@@ -7,6 +7,7 @@ import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import { api } from '@/trpc/client'; import { api } from '@/trpc/client';
import type { IChartData } from '@/trpc/client'; import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn';
import { getChartColor } from '@/utils/theme'; import { getChartColor } from '@/utils/theme';
import { isSameDay, isSameHour, isSameMonth } from 'date-fns'; import { isSameDay, isSameHour, isSameMonth } from 'date-fns';
import { SplineIcon } from 'lucide-react'; import { SplineIcon } from 'lucide-react';
@@ -127,159 +128,161 @@ export function ReportLineChart({ data }: ReportLineChartProps) {
return ( return (
<> <>
<ResponsiveContainer> <div className={cn(editMode && 'card p-4')}>
{({ width, height }) => ( <ResponsiveContainer>
<ComposedChart width={width} height={height} data={rechartData}> {({ width, height }) => (
<CartesianGrid <ComposedChart width={width} height={height} data={rechartData}>
strokeDasharray="3 3" <CartesianGrid
horizontal={true} strokeDasharray="3 3"
vertical={false} horizontal={true}
className="stroke-def-200" vertical={false}
/> className="stroke-def-200"
{references.data?.map((ref) => (
<ReferenceLine
key={ref.id}
x={ref.date.getTime()}
stroke={'#94a3b8'}
strokeDasharray={'3 3'}
label={{
value: ref.title,
position: 'centerTop',
fill: '#334155',
fontSize: 12,
}}
fontSize={10}
/> />
))} {references.data?.map((ref) => (
<YAxis <ReferenceLine
width={getYAxisWidth(data.metrics.max)} key={ref.id}
fontSize={12} x={ref.date.getTime()}
axisLine={false} stroke={'#94a3b8'}
tickLine={false} strokeDasharray={'3 3'}
allowDecimals={false} label={{
tickFormatter={number.short} value: ref.title,
/> position: 'centerTop',
{series.length > 1 && ( fill: '#334155',
<Legend fontSize: 12,
wrapperStyle={{ fontSize: '10px' }} }}
content={<CustomLegend />} fontSize={10}
/>
))}
<YAxis
width={getYAxisWidth(data.metrics.max)}
fontSize={12}
axisLine={false}
tickLine={false}
allowDecimals={false}
tickFormatter={number.short}
/> />
)} {series.length > 1 && (
<Tooltip content={<ReportChartTooltip />} /> <Legend
<XAxis wrapperStyle={{ fontSize: '10px' }}
axisLine={false} content={<CustomLegend />}
fontSize={12} />
dataKey="timestamp" )}
scale="utc" <Tooltip content={<ReportChartTooltip />} />
domain={['dataMin', 'dataMax']} <XAxis
tickFormatter={(m: string) => formatDate(new Date(m))} axisLine={false}
type="number" fontSize={12}
tickLine={false} dataKey="timestamp"
/> scale="utc"
{series.map((serie) => { domain={['dataMin', 'dataMax']}
const color = getChartColor(serie.index); tickFormatter={(m: string) => formatDate(new Date(m))}
return ( type="number"
<React.Fragment key={serie.id}> tickLine={false}
<defs> />
{isAreaStyle && ( {series.map((serie) => {
<linearGradient const color = getChartColor(serie.index);
id={`color${color}`} return (
x1="0" <React.Fragment key={serie.id}>
y1="0" <defs>
x2="0" {isAreaStyle && (
y2="1" <linearGradient
> id={`color${color}`}
<stop x1="0"
offset="0%" y1="0"
stopColor={color} x2="0"
stopOpacity={0.8} y2="1"
></stop> >
<stop <stop
offset="100%" offset="0%"
stopColor={color} stopColor={color}
stopOpacity={0.1} stopOpacity={0.8}
></stop> ></stop>
</linearGradient> <stop
)} offset="100%"
{gradientTwoColors( stopColor={color}
`hideAllButLastInterval_${serie.id}`, stopOpacity={0.1}
'rgba(0,0,0,0)', ></stop>
color, </linearGradient>
lastIntervalPercent )}
)} {gradientTwoColors(
{gradientTwoColors( `hideAllButLastInterval_${serie.id}`,
`hideJustLastInterval_${serie.id}`, 'rgba(0,0,0,0)',
color, color,
'rgba(0,0,0,0)', lastIntervalPercent
lastIntervalPercent )}
)} {gradientTwoColors(
</defs> `hideJustLastInterval_${serie.id}`,
<Line color,
dot={false} 'rgba(0,0,0,0)',
type={lineType} lastIntervalPercent
name={serie.id} )}
isAnimationActive={false} </defs>
strokeWidth={2}
dataKey={`${serie.id}:count`}
stroke={useDashedLastLine ? 'transparent' : color}
// Use for legend
fill={color}
/>
{isAreaStyle && (
<Area
name={`${serie.id}:area:noTooltip`}
dataKey={`${serie.id}:count`}
fill={`url(#color${color})`}
type={lineType}
isAnimationActive={false}
fillOpacity={0.1}
/>
)}
{useDashedLastLine && (
<>
<Line
dot={false}
type={lineType}
name={`${serie.id}:dashed:noTooltip`}
isAnimationActive={false}
strokeWidth={2}
dataKey={`${serie.id}:count`}
stroke={`url('#hideAllButLastInterval_${serie.id}')`}
strokeDasharray="4 2"
strokeOpacity={0.7}
/>
<Line
dot={false}
type={lineType}
name={`${serie.id}:solid:noTooltip`}
isAnimationActive={false}
strokeWidth={2}
dataKey={`${serie.id}:count`}
stroke={`url('#hideJustLastInterval_${serie.id}')`}
/>
</>
)}
{previous && (
<Line <Line
type={lineType}
name={`${serie.id}:prev`}
isAnimationActive={false}
strokeWidth={1}
dot={false} dot={false}
strokeDasharray={'1 1'} type={lineType}
strokeOpacity={0.5} name={serie.id}
dataKey={`${serie.id}:prev:count`} isAnimationActive={false}
stroke={color} strokeWidth={2}
dataKey={`${serie.id}:count`}
stroke={useDashedLastLine ? 'transparent' : color}
// Use for legend // Use for legend
fill={color} fill={color}
/> />
)} {isAreaStyle && (
</React.Fragment> <Area
); name={`${serie.id}:area:noTooltip`}
})} dataKey={`${serie.id}:count`}
</ComposedChart> fill={`url(#color${color})`}
)} type={lineType}
</ResponsiveContainer> isAnimationActive={false}
fillOpacity={0.1}
/>
)}
{useDashedLastLine && (
<>
<Line
dot={false}
type={lineType}
name={`${serie.id}:dashed:noTooltip`}
isAnimationActive={false}
strokeWidth={2}
dataKey={`${serie.id}:count`}
stroke={`url('#hideAllButLastInterval_${serie.id}')`}
strokeDasharray="4 2"
strokeOpacity={0.7}
/>
<Line
dot={false}
type={lineType}
name={`${serie.id}:solid:noTooltip`}
isAnimationActive={false}
strokeWidth={2}
dataKey={`${serie.id}:count`}
stroke={`url('#hideJustLastInterval_${serie.id}')`}
/>
</>
)}
{previous && (
<Line
type={lineType}
name={`${serie.id}:prev`}
isAnimationActive={false}
strokeWidth={1}
dot={false}
strokeDasharray={'1 1'}
strokeOpacity={0.5}
dataKey={`${serie.id}:prev:count`}
stroke={color}
// Use for legend
fill={color}
/>
)}
</React.Fragment>
);
})}
</ComposedChart>
)}
</ResponsiveContainer>
</div>
{editMode && ( {editMode && (
<ReportTable <ReportTable
data={data} data={data}

View File

@@ -1,14 +1,10 @@
import { cn } from '@/utils/cn';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { useChartContext } from './ChartProvider';
interface ResponsiveContainerProps { interface ResponsiveContainerProps {
children: (props: { width: number; height: number }) => React.ReactNode; children: (props: { width: number; height: number }) => React.ReactNode;
} }
export function ResponsiveContainer({ children }: ResponsiveContainerProps) { export function ResponsiveContainer({ children }: ResponsiveContainerProps) {
const { editMode } = useChartContext();
const maxHeight = 300; const maxHeight = 300;
const minHeight = 200; const minHeight = 200;
return ( return (
@@ -17,17 +13,13 @@ export function ResponsiveContainer({ children }: ResponsiveContainerProps) {
maxHeight, maxHeight,
minHeight, minHeight,
}} }}
className={cn('aspect-video w-full max-sm:-mx-3', editMode && 'card p-4')} className={'aspect-video w-full max-sm:-mx-3'}
> >
<AutoSizer disableHeight> <AutoSizer disableHeight>
{({ width }) => {({ width }) =>
children({ children({
width, width,
height: Math.min( height: Math.min(maxHeight, width * 0.5625),
Math.max(width * 0.5625, minHeight),
// we add p-4 (16px) padding in edit mode
editMode ? maxHeight - 16 : maxHeight
),
}) })
} }
</AutoSizer> </AutoSizer>