web: add previous/compare values for all charts

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-01-21 22:05:49 +01:00
parent 308ae98472
commit 46d5d203dc
27 changed files with 1290 additions and 231 deletions

View File

@@ -1,6 +1,16 @@
import * as React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { Pagination, usePagination } from '@/components/Pagination';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Tooltip,
TooltipContent,
@@ -8,10 +18,12 @@ import {
} from '@/components/ui/tooltip';
import { useFormatDateInterval } from '@/hooks/useFormatDateInterval';
import { useMappings } from '@/hooks/useMappings';
import { useNumber } from '@/hooks/useNumerFormatter';
import { useSelector } from '@/redux';
import { cn } from '@/utils/cn';
import { getChartColor } from '@/utils/theme';
import { PreviousDiffIndicator } from '../PreviousDiffIndicator';
interface ReportTableProps {
data: IChartData;
visibleSeries: IChartData['series'];
@@ -23,6 +35,8 @@ export function ReportTable({
visibleSeries,
setVisibleSeries,
}: ReportTableProps) {
const pagination = usePagination(50);
const number = useNumber();
const interval = useSelector((state) => state.report.interval);
const formatDate = useFormatDateInterval(interval);
const getLabel = useMappings();
@@ -37,117 +51,125 @@ export function ReportTable({
});
}
const row = 'flex border-b border-border last:border-b-0 flex-1';
const cell = 'p-2 last:pr-8 last:w-[8rem]';
const value = 'min-w-[6rem] text-right';
const header = 'text-sm font-medium';
const total =
'bg-gray-50 text-emerald-600 font-medium border-r border-border';
return (
<>
<div className="flex w-fit max-w-full rounded-md border border-border bg-white">
{/* Labels */}
<div className="border-r border-border">
<div className={cn(header, row, cell)}>Name</div>
{data.series.map((serie, index) => {
const checked = !!visibleSeries.find(
(item) => item.name === serie.name
);
<div className="grid grid-cols-[200px_1fr] border border-border rounded-md overflow-hidden">
<Table className="rounded-none border-t-0 border-l-0 border-b-0">
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.series
.slice(pagination.skip, pagination.skip + pagination.take)
.map((serie, index) => {
const checked = !!visibleSeries.find(
(item) => item.name === serie.name
);
return (
<div
key={serie.name}
className={cn(
'flex max-w-[200px] lg:max-w-[400px] xl:max-w-[600px] w-full min-w-full items-center gap-2',
row,
// avoid using cell since its better on the right side
'p-2'
)}
>
<Checkbox
onCheckedChange={(checked) =>
handleChange(serie.name, !!checked)
}
style={
checked
? {
background: getChartColor(index),
borderColor: getChartColor(index),
}
: undefined
}
checked={checked}
/>
<Tooltip delayDuration={200}>
<TooltipTrigger asChild>
<div className="min-w-0 overflow-hidden whitespace-nowrap text-ellipsis">
{getLabel(serie.name)}
</div>
</TooltipTrigger>
<TooltipContent>
<p>{getLabel(serie.name)}</p>
</TooltipContent>
</Tooltip>
</div>
);
})}
</div>
{/* ScrollView for all values */}
<div className="w-full overflow-auto">
{/* Header */}
<div className={cn('w-max', row)}>
<div className={cn(header, value, cell, total)}>Total</div>
<div className={cn(header, value, cell, total)}>Average</div>
{data.series[0]?.data.map((serie) => (
<div
key={serie.date.toString()}
className={cn(header, value, cell)}
>
{formatDate(serie.date)}
</div>
))}
</div>
{/* Values */}
{data.series.map((serie) => {
return (
<div className={cn('w-max', row)} key={serie.name}>
<div className={cn(header, value, cell, total)}>
{serie.metrics.sum}
</div>
<div className={cn(header, value, cell, total)}>
{serie.metrics.average}
</div>
{serie.data.map((item) => {
return (
<TableRow key={serie.name}>
<TableCell className="h-10">
<div className="flex items-center gap-2">
<Checkbox
onCheckedChange={(checked) =>
handleChange(serie.name, !!checked)
}
style={
checked
? {
background: getChartColor(index),
borderColor: getChartColor(index),
}
: undefined
}
checked={checked}
/>
<Tooltip delayDuration={200}>
<TooltipTrigger asChild>
<div className="min-w-0 overflow-hidden whitespace-nowrap text-ellipsis">
{getLabel(serie.name)}
</div>
</TooltipTrigger>
<TooltipContent>
<p>{getLabel(serie.name)}</p>
</TooltipContent>
</Tooltip>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
<div className="overflow-auto">
<Table className="rounded-none border-none">
<TableHeader>
<TableRow>
<TableHead>Total</TableHead>
<TableHead>Average</TableHead>
{data.series[0]?.data.map((serie) => (
<TableHead
key={serie.date.toString()}
className="whitespace-nowrap"
>
{formatDate(serie.date)}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{data.series
.slice(pagination.skip, pagination.skip + pagination.take)
.map((serie) => {
return (
<div key={item.date} className={cn(value, cell)}>
{item.count}
</div>
<TableRow key={serie.name}>
<TableCell className="h-10">
<div className="flex items-center gap-2 font-medium">
{number.format(serie.metrics.sum)}
<PreviousDiffIndicator
{...serie.metrics.previous.sum}
/>
</div>
</TableCell>
<TableCell className="h-10">
<div className="flex items-center gap-2 font-medium">
{number.format(serie.metrics.average)}
<PreviousDiffIndicator
{...serie.metrics.previous.average}
/>
</div>
</TableCell>
{serie.data.map((item) => {
return (
<TableCell
className="h-10"
key={item.date.toString()}
>
<div className="flex items-center gap-2">
{number.format(item.count)}
<PreviousDiffIndicator {...item.previous} />
</div>
</TableCell>
);
})}
</TableRow>
);
})}
</div>
);
})}
</TableBody>
</Table>
</div>
</div>
<div className="flex gap-4">
<div className="flex gap-1">
<div>Total</div>
<div>{data.metrics.sum}</div>
</div>
<div className="flex gap-1">
<div>Average</div>
<div>{data.metrics.averge}</div>
</div>
<div className="flex gap-1">
<div>Min</div>
<div>{data.metrics.min}</div>
</div>
<div className="flex gap-1">
<div>Max</div>
<div>{data.metrics.max}</div>
<div className="flex flex-col-reverse gap-4 md:flex-row md:justify-between md:items-center">
<div className="flex gap-1 flex-wrap">
<Badge>Total: {number.format(data.metrics.sum)}</Badge>
<Badge>Average: {number.format(data.metrics.average)}</Badge>
<Badge>Min: {number.format(data.metrics.min)}</Badge>
<Badge>Max: {number.format(data.metrics.max)}</Badge>
</div>
<Pagination {...pagination} />
</div>
</>
);