This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-13 11:25:14 +01:00
parent 034be63ac0
commit 7f2c0f6cf0
64 changed files with 5820 additions and 1160 deletions

View File

@@ -22,7 +22,7 @@ export function ChartEmpty() {
return (
<div
className={
'aspect-video w-full max-h-[400px] flex justify-center items-center'
'aspect-video w-full max-h-[400px] min-h-[200px] flex justify-center items-center'
}
>
No data

View File

@@ -1,7 +1,18 @@
import { createContext, memo, useContext, useMemo } from 'react';
'use client';
import {
createContext,
memo,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import type { IChartSerie } from '@/server/api/routers/chart';
import type { IChartInput } from '@/types';
import { ChartLoading } from './ChartLoading';
export interface ChartContextType extends IChartInput {
editMode?: boolean;
hideID?: boolean;
@@ -53,6 +64,16 @@ export function withChartProivder<ComponentProps>(
WrappedComponent: React.FC<ComponentProps>
) {
const WithChartProvider = (props: ComponentProps & ChartContextType) => {
const [mounted, setMounted] = useState(props.chartType === 'metric');
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <ChartLoading />;
}
return (
<ChartProvider {...props}>
<WrappedComponent {...props} />

View File

@@ -1,3 +1,5 @@
'use client';
import type { IChartData } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare';
import { useNumber } from '@/hooks/useNumerFormatter';

View File

@@ -41,7 +41,7 @@ export function ReportAreaChart({
<>
<div
className={cn(
'max-sm:-mx-3',
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4'
)}
>

View File

@@ -1,35 +1,23 @@
import { useMemo, useState } from 'react';
import type { IChartData, RouterOutputs } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare';
'use client';
import { useMemo } from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { Progress } from '@/components/ui/progress';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { useNumber } from '@/hooks/useNumerFormatter';
import { cn } from '@/utils/cn';
import { NOT_SET_VALUE } from '@/utils/constants';
import { getChartColor } from '@/utils/theme';
import { createColumnHelper } from '@tanstack/react-table';
import { PreviousDiffIndicator } from '../PreviousDiffIndicator';
import { useChartContext } from './ChartProvider';
import { SerieIcon } from './SerieIcon';
interface ReportBarChartProps {
data: IChartData;
}
export function ReportBarChart({ data }: ReportBarChartProps) {
const { editMode, metric, unit, onClick } = useChartContext();
const { editMode, metric, onClick } = useChartContext();
const number = useNumber();
const series = useMemo(
() => (editMode ? data.series : data.series.slice(0, 20)),
@@ -62,7 +50,10 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
)}
{...(isClickable ? { onClick: () => onClick(serie) } : {})}
>
<div className="flex-1 break-all">{serie.name}</div>
<div className="flex-1 break-all flex items-center gap-2">
<SerieIcon name={serie.name} />
{serie.name}
</div>
<div className="flex-shrink-0 flex w-1/4 gap-4 items-center justify-end">
<PreviousDiffIndicator {...serie.metrics.previous[metric]} />
<div className="font-bold">

View File

@@ -19,9 +19,16 @@ interface ReportHistogramChartProps {
interval: IInterval;
}
function BarHover(props: any) {
function BarHover({ x, y, width, height, top, left, right, bottom }: any) {
const bg = theme?.colors?.slate?.['200'] as string;
return <rect {...props} rx="8" fill={bg} fill-opacity={0.5} />;
return (
<rect
{...{ x, y, width, height, top, left, right, bottom }}
rx="8"
fill={bg}
fillOpacity={0.5}
/>
);
}
export function ReportHistogramChart({
@@ -38,7 +45,7 @@ export function ReportHistogramChart({
<>
<div
className={cn(
'max-sm:-mx-3',
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4'
)}
>

View File

@@ -1,3 +1,5 @@
'use client';
import React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { AutoSizer } from '@/components/AutoSizer';
@@ -41,7 +43,7 @@ export function ReportLineChart({
<>
<div
className={cn(
'max-sm:-mx-3',
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4'
)}
>

View File

@@ -1,3 +1,5 @@
'use client';
import type { IChartData } from '@/app/_trpc/client';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import { cn } from '@/utils/cn';

View File

@@ -1,3 +1,5 @@
'use client';
import * as React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { Pagination, usePagination } from '@/components/Pagination';

View File

@@ -0,0 +1,65 @@
import { NOT_SET_VALUE } from '@/utils/constants';
import type { LucideIcon, LucideProps } from 'lucide-react';
import {
ActivityIcon,
ExternalLinkIcon,
HelpCircleIcon,
MonitorIcon,
MonitorPlayIcon,
PhoneIcon,
SmartphoneIcon,
SquareAsteriskIcon,
TabletIcon,
TabletSmartphoneIcon,
TwitterIcon,
} from 'lucide-react';
import {
getKeys,
getNetworks,
networkFor,
register,
SocialIcon,
} from 'react-social-icons';
interface SerieIconProps extends LucideProps {
name: string;
}
const mapper: Record<string, LucideIcon> = {
screen_view: MonitorPlayIcon,
session_start: ActivityIcon,
link_out: ExternalLinkIcon,
mobile: SmartphoneIcon,
desktop: MonitorIcon,
tablet: TabletIcon,
[NOT_SET_VALUE]: HelpCircleIcon,
};
const networks = getNetworks();
register('duckduckgo', {
color: 'red',
path: 'https://duckduckgo.com/favicon.ico',
});
export function SerieIcon({ name, ...props }: SerieIconProps) {
let Icon = mapper[name] ?? null;
if (name.includes('http')) {
Icon = ((_props) => (
<SocialIcon network={networkFor(name)} />
)) as LucideIcon;
}
if (Icon === null && networks.includes(name.toLowerCase())) {
Icon = ((_props) => (
<SocialIcon network={name.toLowerCase()} />
)) as LucideIcon;
}
return (
<div className="w-4 h-4 flex-shrink-0 relative [&_a]:!w-4 [&_a]:!h-4 [&_svg]:!rounded">
{Icon ? <Icon size={16} {...props} /> : null}
</div>
);
}

View File

@@ -1,12 +1,12 @@
'use client';
import { memo } from 'react';
import { memo, useEffect, useState } from 'react';
import type { RouterOutputs } from '@/app/_trpc/client';
import { api } from '@/app/_trpc/client';
import { useAppParams } from '@/hooks/useAppParams';
import type { IChartInput } from '@/types';
import { ChartEmpty } from './ChartEmpty';
import { ChartLoading } from './ChartLoading';
import { withChartProivder } from './ChartProvider';
import { ReportAreaChart } from './ReportAreaChart';
import { ReportBarChart } from './ReportBarChart';
@@ -33,9 +33,8 @@ export const Chart = memo(
formula,
unit,
metric,
initialData,
projectId,
}: ReportChartProps) {
const params = useAppParams();
const [data] = api.chart.chart.useSuspenseQuery(
{
// dont send lineType since it does not need to be sent
@@ -48,7 +47,7 @@ export const Chart = memo(
range,
startDate: null,
endDate: null,
projectId: params.projectId,
projectId,
previous,
formula,
unit,
@@ -56,7 +55,6 @@ export const Chart = memo(
},
{
keepPreviousData: true,
initialData,
}
);

View File

@@ -1,11 +1,11 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import type { IChartEvent } from '@/types';
import { cn } from '@/utils/cn';
import { DatabaseIcon, FilterIcon } from 'lucide-react';
import { DatabaseIcon } from 'lucide-react';
import { useChartContext } from '../chart/ChartProvider';
import { changeEvent } from '../reportSlice';
interface EventPropertiesComboboxProps {
@@ -16,7 +16,7 @@ export function EventPropertiesCombobox({
event,
}: EventPropertiesComboboxProps) {
const dispatch = useDispatch();
const { projectId } = useAppParams();
const { projectId } = useChartContext();
const query = api.chart.properties.useQuery(
{

View File

@@ -3,21 +3,21 @@
import { api } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux';
import type { IChartBreakdown } from '@/types';
import { SplitIcon } from 'lucide-react';
import { useChartContext } from '../chart/ChartProvider';
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
import { ReportBreakdownMore } from './ReportBreakdownMore';
import type { ReportEventMoreProps } from './ReportEventMore';
export function ReportBreakdowns() {
const params = useAppParams();
const { projectId } = useChartContext();
const selectedBreakdowns = useSelector((state) => state.report.breakdowns);
const dispatch = useDispatch();
const propertiesQuery = api.chart.properties.useQuery({
projectId: params.projectId,
projectId,
});
const propertiesCombobox = (propertiesQuery.data ?? []).map((item) => ({
value: item,

View File

@@ -6,12 +6,12 @@ import { Dropdown } from '@/components/Dropdown';
import { Checkbox } from '@/components/ui/checkbox';
import { Combobox } from '@/components/ui/combobox';
import { Input } from '@/components/ui/input';
import { useAppParams } from '@/hooks/useAppParams';
import { useDebounceFn } from '@/hooks/useDebounceFn';
import { useDispatch, useSelector } from '@/redux';
import type { IChartEvent } from '@/types';
import { GanttChart, GanttChartIcon, Users } from 'lucide-react';
import { useChartContext } from '../chart/ChartProvider';
import {
addEvent,
changeEvent,
@@ -28,9 +28,9 @@ export function ReportEvents() {
const previous = useSelector((state) => state.report.previous);
const selectedEvents = useSelector((state) => state.report.events);
const dispatch = useDispatch();
const params = useAppParams();
const { projectId } = useChartContext();
const eventsQuery = api.chart.events.useQuery({
projectId: params.projectId,
projectId,
});
const eventsCombobox = (eventsQuery.data ?? []).map((item) => ({
value: item.name,

View File

@@ -4,7 +4,6 @@ import { Dropdown } from '@/components/Dropdown';
import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { RenderDots } from '@/components/ui/RenderDots';
import { useAppParams } from '@/hooks/useAppParams';
import { useMappings } from '@/hooks/useMappings';
import { useDispatch } from '@/redux';
import type {
@@ -14,8 +13,8 @@ import type {
} from '@/types';
import { operators } from '@/utils/constants';
import { SlidersHorizontal, Trash } from 'lucide-react';
import { useParams } from 'next/navigation';
import { useChartContext } from '../../chart/ChartProvider';
import { changeEvent } from '../../reportSlice';
interface FilterProps {
@@ -24,7 +23,7 @@ interface FilterProps {
}
export function FilterItem({ filter, event }: FilterProps) {
const { projectId } = useAppParams();
const { projectId } = useChartContext();
const getLabel = useMappings();
const dispatch = useDispatch();
const potentialValues = api.chart.values.useQuery({

View File

@@ -1,10 +1,10 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import type { IChartEvent } from '@/types';
import { FilterIcon } from 'lucide-react';
import { useChartContext } from '../../chart/ChartProvider';
import { changeEvent } from '../../reportSlice';
interface FiltersComboboxProps {
@@ -13,7 +13,7 @@ interface FiltersComboboxProps {
export function FiltersCombobox({ event }: FiltersComboboxProps) {
const dispatch = useDispatch();
const { projectId } = useAppParams();
const { projectId } = useChartContext();
const query = api.chart.properties.useQuery(
{