web improvements

This commit is contained in:
Carl-Gerhard Lindesvärd
2023-12-11 10:40:15 +01:00
parent b81d6823dd
commit e727109235
8 changed files with 143 additions and 99 deletions

View File

@@ -1,5 +1,7 @@
{ {
"editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }], "eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }],

View File

@@ -25,6 +25,7 @@ export function ReportLineChart({ interval, data }: ReportLineChartProps) {
const { editMode } = useChartContext(); const { editMode } = useChartContext();
const [visibleSeries, setVisibleSeries] = useState<string[]>([]); const [visibleSeries, setVisibleSeries] = useState<string[]>([]);
const formatDate = useFormatDateInterval(interval); const formatDate = useFormatDateInterval(interval);
console.log(JSON.stringify(data.series[0]?.data, null, 2));
const ref = useRef(false); const ref = useRef(false);
useEffect(() => { useEffect(() => {

View File

@@ -39,73 +39,99 @@ export function ReportTable({
const total = const total =
'bg-gray-50 text-emerald-600 font-medium border-r border-border'; 'bg-gray-50 text-emerald-600 font-medium border-r border-border';
return ( return (
<div className="flex w-fit max-w-full rounded-md border border-border"> <>
{/* Labels */} <div className="flex w-fit max-w-full rounded-md border border-border">
<div className="border-r border-border"> {/* Labels */}
<div className={cn(header, row, cell)}>Name</div> <div className="border-r border-border">
{data.series.map((serie, index) => { <div className={cn(header, row, cell)}>Name</div>
const checked = visibleSeries.includes(serie.name); {/* <div
className={cn(
'flex max-w-[200px] w-full min-w-full items-center gap-2',
row,
cell
)}
>
<div className="font-medium min-w-0 overflow-scroll whitespace-nowrap scrollbar-hide">
Summary
</div>
</div> */}
{data.series.map((serie, index) => {
const checked = visibleSeries.includes(serie.name);
return ( return (
<div <div
key={serie.name} key={serie.name}
className={cn('flex max-w-[200px] items-center gap-2', row, cell)} className={cn(
> 'flex max-w-[200px] w-full min-w-full items-center gap-2',
<Checkbox row,
onCheckedChange={(checked) => cell
handleChange(serie.name, !!checked) )}
} >
style={ <Checkbox
checked onCheckedChange={(checked) =>
? { handleChange(serie.name, !!checked)
background: getChartColor(index), }
borderColor: getChartColor(index), style={
} checked
: undefined ? {
} background: getChartColor(index),
checked={checked} borderColor: getChartColor(index),
/> }
<div className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap"> : undefined
{getLabel(serie.name)} }
checked={checked}
/>
<div
title={getLabel(serie.name)}
className="min-w-0 overflow-scroll whitespace-nowrap scrollbar-hide"
>
{getLabel(serie.name)}
</div>
</div> </div>
</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>
{data.series[0]?.data.map((serie) => (
<div
key={serie.date.toString()}
className={cn(header, value, cell)}
>
{formatDate(serie.date)}
</div>
))}
</div> </div>
{/* Values */} {/* ScrollView for all values */}
{data.series.map((serie) => { <div className="w-full overflow-auto">
return ( {/* Header */}
<div className={cn('w-max', row)} key={serie.name}> <div className={cn('w-max', row)}>
<div className={cn(header, value, cell, total)}> <div className={cn(header, value, cell, total)}>Total</div>
{serie.totalCount} {data.series[0]?.data.map((serie) => (
<div
key={serie.date.toString()}
className={cn(header, value, cell)}
>
{formatDate(serie.date)}
</div> </div>
{serie.data.map((item) => { ))}
return ( </div>
<div key={item.date} className={cn(value, cell)}>
{item.count} {/* Values */}
</div> {data.series.map((serie) => {
); return (
})} <div className={cn('w-max', row)} key={serie.name}>
</div> <div className={cn(header, value, cell, total)}>
); {serie.totalCount}
})} </div>
{serie.data.map((item) => {
return (
<div key={item.date} className={cn(value, cell)}>
{item.count}
</div>
);
})}
</div>
);
})}
</div>
</div> </div>
</div> <div className="flex gap-2">
<div>Summary</div>
<div>
{data.series.reduce((acc, serie) => serie.totalCount + acc, 0)}
</div>
</div>
</>
); );
} }

View File

@@ -40,6 +40,8 @@ export const Chart = memo(
} }
); );
console.log(chart.data);
const anyData = Boolean(chart.data?.series?.[0]?.data); const anyData = Boolean(chart.data?.series?.[0]?.data);
if (chart.isFetching && !anyData) { if (chart.isFetching && !anyData) {

View File

@@ -15,7 +15,7 @@ import {
import { RenderDots } from '@/components/ui/RenderDots'; import { RenderDots } from '@/components/ui/RenderDots';
import { useMappings } from '@/hooks/useMappings'; import { useMappings } from '@/hooks/useMappings';
import { useOrganizationParams } from '@/hooks/useOrganizationParams'; import { useOrganizationParams } from '@/hooks/useOrganizationParams';
import { useDispatch } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import type { import type {
IChartEvent, IChartEvent,
IChartEventFilter, IChartEventFilter,

View File

@@ -10,14 +10,7 @@ import { ReportSaveButton } from '@/components/report/ReportSaveButton';
import { reset, setReport } from '@/components/report/reportSlice'; import { reset, setReport } from '@/components/report/reportSlice';
import { ReportSidebar } from '@/components/report/sidebar/ReportSidebar'; import { ReportSidebar } from '@/components/report/sidebar/ReportSidebar';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet';
import { useRouterBeforeLeave } from '@/hooks/useRouterBeforeLeave'; import { useRouterBeforeLeave } from '@/hooks/useRouterBeforeLeave';
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import { createServerSideProps } from '@/server/getServerSideProps'; import { createServerSideProps } from '@/server/getServerSideProps';

View File

@@ -2,7 +2,12 @@ import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import * as cache from '@/server/cache'; import * as cache from '@/server/cache';
import { db } from '@/server/db'; import { db } from '@/server/db';
import { getProjectBySlug } from '@/server/services/project.service'; import { getProjectBySlug } from '@/server/services/project.service';
import type { IChartEvent, IChartInputWithDates, IChartRange } from '@/types'; import type {
IChartEvent,
IChartInputWithDates,
IChartRange,
IInterval,
} from '@/types';
import { getDaysOldDate } from '@/utils/date'; import { getDaysOldDate } from '@/utils/date';
import { toDots } from '@/utils/object'; import { toDots } from '@/utils/object';
import { zChartInputWithDates } from '@/utils/validation'; import { zChartInputWithDates } from '@/utils/validation';
@@ -81,6 +86,7 @@ export const chartRouter = createTRPCRouter({
}) })
) )
.query(async ({ input: { event, property, projectSlug } }) => { .query(async ({ input: { event, property, projectSlug } }) => {
const intervalInDays = 180;
const project = await getProjectBySlug(projectSlug); const project = await getProjectBySlug(projectSlug);
if (isJsonPath(property)) { if (isJsonPath(property)) {
const events = await db.$queryRawUnsafe<{ value: string }[]>( const events = await db.$queryRawUnsafe<{ value: string }[]>(
@@ -88,14 +94,7 @@ export const chartRouter = createTRPCRouter({
property property
)} AS value from events WHERE project_id = '${ )} AS value from events WHERE project_id = '${
project.id project.id
}' AND name = '${event}' AND "createdAt" >= NOW() - INTERVAL '30 days'` }' AND name = '${event}' AND "createdAt" >= NOW() - INTERVAL '${intervalInDays} days'`
);
console.log(
`SELECT ${selectJsonPath(
property
)} AS value from events WHERE project_id = '${
project.id
}' AND name = '${event}' AND "createdAt" >= NOW() - INTERVAL '30 days'`
); );
return { return {
@@ -110,7 +109,9 @@ export const chartRouter = createTRPCRouter({
not: null, not: null,
}, },
createdAt: { createdAt: {
gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 30), gte: new Date(
new Date().getTime() - 1000 * 60 * 60 * 24 * intervalInDays
),
}, },
}, },
distinct: property as any, distinct: property as any,
@@ -483,7 +484,7 @@ async function getChartData({
function fillEmptySpotsInTimeline( function fillEmptySpotsInTimeline(
items: ResultItem[], items: ResultItem[],
interval: string, interval: IInterval,
startDate: string, startDate: string,
endDate: string endDate: string
) { ) {
@@ -493,31 +494,36 @@ function fillEmptySpotsInTimeline(
const today = new Date(); const today = new Date();
if (interval === 'minute') { if (interval === 'minute') {
clonedStartDate.setSeconds(0, 0); clonedStartDate.setUTCSeconds(0, 0);
clonedEndDate.setMinutes(clonedEndDate.getMinutes() + 1, 0, 0); clonedEndDate.setUTCMinutes(clonedEndDate.getUTCMinutes() + 1, 0, 0);
} else if (interval === 'hour') { } else if (interval === 'hour') {
clonedStartDate.setMinutes(0, 0, 0); clonedStartDate.setUTCMinutes(0, 0, 0);
clonedEndDate.setMinutes(0, 0, 0); clonedEndDate.setUTCMinutes(0, 0, 0);
} else { } else {
clonedStartDate.setUTCHours(0, 0, 0, 0); clonedStartDate.setUTCHours(0, 0, 0, 0);
clonedEndDate.setUTCHours(0, 0, 0, 0); clonedEndDate.setUTCHours(0, 0, 0, 0);
} }
if (interval === 'month') {
clonedStartDate.setDate(1);
clonedEndDate.setDate(1);
}
// Force if interval is month and the start date is the same month as today // Force if interval is month and the start date is the same month as today
const shouldForce = () => const shouldForce = () =>
interval === 'month' && interval === 'month' &&
clonedStartDate.getFullYear() === today.getFullYear() && clonedStartDate.getUTCFullYear() === today.getUTCFullYear() &&
clonedStartDate.getMonth() === today.getMonth(); clonedStartDate.getUTCMonth() === today.getUTCMonth();
while ( while (
shouldForce() || shouldForce() ||
clonedStartDate.getTime() <= clonedEndDate.getTime() clonedStartDate.getTime() <= clonedEndDate.getTime()
) { ) {
const getYear = (date: Date) => date.getFullYear(); const getYear = (date: Date) => date.getUTCFullYear();
const getMonth = (date: Date) => date.getMonth(); const getMonth = (date: Date) => date.getUTCMonth();
const getDay = (date: Date) => date.getDate(); const getDay = (date: Date) => date.getUTCDate();
const getHour = (date: Date) => date.getHours(); const getHour = (date: Date) => date.getUTCHours();
const getMinute = (date: Date) => date.getMinutes(); const getMinute = (date: Date) => date.getUTCMinutes();
const item = items.find((item) => { const item = items.find((item) => {
const date = new Date(item.date); const date = new Date(item.date);
@@ -555,7 +561,10 @@ function fillEmptySpotsInTimeline(
}); });
if (item) { if (item) {
result.push(item); result.push({
...item,
date: clonedStartDate.toISOString(),
});
} else { } else {
result.push({ result.push({
date: clonedStartDate.toISOString(), date: clonedStartDate.toISOString(),
@@ -566,19 +575,19 @@ function fillEmptySpotsInTimeline(
switch (interval) { switch (interval) {
case 'day': { case 'day': {
clonedStartDate.setDate(clonedStartDate.getDate() + 1); clonedStartDate.setDate(clonedStartDate.getUTCDate() + 1);
break; break;
} }
case 'hour': { case 'hour': {
clonedStartDate.setHours(clonedStartDate.getHours() + 1); clonedStartDate.setHours(clonedStartDate.getUTCHours() + 1);
break; break;
} }
case 'minute': { case 'minute': {
clonedStartDate.setMinutes(clonedStartDate.getMinutes() + 1); clonedStartDate.setMinutes(clonedStartDate.getUTCMinutes() + 1);
break; break;
} }
case 'month': { case 'month': {
clonedStartDate.setMonth(clonedStartDate.getMonth() + 1); clonedStartDate.setMonth(clonedStartDate.getUTCMonth() + 1);
break; break;
} }
} }

View File

@@ -146,3 +146,14 @@
opacity: 1; opacity: 1;
} }
} }
/* For Webkit-based browsers (Chrome, Safari and Opera) */
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* For IE, Edge and Firefox */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}