update dashboard metrics and move away from round corners

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-05-20 21:22:20 +02:00
parent 4350670bbc
commit 3ecdf54d5c
13 changed files with 125 additions and 137 deletions

View File

@@ -12,7 +12,7 @@ export function StickyBelowHeader({
return (
<div
className={cn(
'top-0 z-20 rounded-lg border-b border-border bg-background md:sticky [[id=dashboard]_&]:top-16 [[id=dashboard]_&]:rounded-none',
'top-0 z-20 border-b border-border bg-background md:sticky [[id=dashboard]_&]:top-16 [[id=dashboard]_&]:rounded-none',
className
)}
>

View File

@@ -41,9 +41,6 @@ export default function Page({
<OverviewFiltersButtons />
</StickyBelowHeader>
<div className="grid grid-cols-6 gap-4 p-4">
<div className="col-span-6">
<OverviewLiveHistogram projectId={projectId} />
</div>
<OverviewMetrics projectId={projectId} />
<OverviewTopSources projectId={projectId} />
<OverviewTopPages projectId={projectId} />

View File

@@ -14,6 +14,7 @@ import {
eachDayOfInterval,
endOfMonth,
format,
formatISO,
startOfMonth,
subMonths,
} from 'date-fns';
@@ -60,7 +61,9 @@ const ProfileActivity = ({ data }: Props) => {
end: endOfMonth(subMonths(startDate, 1)),
}).map((date) => {
const hit = data.find((item) =>
item.date.includes(date.toISOString().split('T')[0])
item.date.includes(
formatISO(date, { representation: 'date' })
)
);
return (
<div
@@ -82,7 +85,9 @@ const ProfileActivity = ({ data }: Props) => {
end: endDate,
}).map((date) => {
const hit = data.find((item) =>
item.date.includes(date.toISOString().split('T')[0])
item.date.includes(
formatISO(date, { representation: 'date' })
)
);
return (
<div

View File

@@ -18,9 +18,15 @@ interface PageProps {
params: {
id: string;
};
searchParams: {
header: string;
};
}
export default async function Page({ params: { id } }: PageProps) {
export default async function Page({
params: { id },
searchParams,
}: PageProps) {
const share = await getShareOverviewById(id);
if (!share) {
return notFound();
@@ -32,43 +38,42 @@ export default async function Page({ params: { id } }: PageProps) {
const organization = await getOrganizationBySlug(share.organizationSlug);
return (
<div className="bg-gradient-to-tl from-blue-950 to-blue-600 p-4 md:p-16">
<div className="mx-auto max-w-6xl">
<div className="mb-4 flex items-end justify-between">
<div>
{searchParams.header !== '0' && (
<div className="flex items-center justify-between border-b border-border bg-white p-4">
<div className="leading-none">
<span className="mb-4 text-white">{organization?.name}</span>
<h1 className="text-xl font-medium text-white">
{share.project?.name}
</h1>
<span className="mb-4">{organization?.name}</span>
<h1 className="text-xl font-medium">{share.project?.name}</h1>
</div>
<a href="https://openpanel.dev?utm_source=openpanel.dev&utm_medium=share">
<Logo className="text-white max-sm:[&_span]:hidden" />
<a
href="https://openpanel.dev?utm_source=openpanel.dev&utm_medium=share"
className="flex flex-col items-end text-lg font-medium"
>
<span className="text-xs">POWERED BY</span>
<span>openpanel.dev</span>
</a>
</div>
<div className="rounded-lg bg-slate-100 shadow ring-8 ring-blue-600/50 max-sm:-mx-3">
<StickyBelowHeader>
<div className="flex justify-between gap-2 p-4">
<div className="flex gap-2">
<OverviewReportRange />
{/* <OverviewFiltersDrawer projectId={projectId} mode="events" /> */}
</div>
<div className="flex gap-2">
<ServerLiveCounter projectId={projectId} />
</div>
)}
<div className="">
<StickyBelowHeader>
<div className="flex justify-between gap-2 p-4">
<div className="flex gap-2">
<OverviewReportRange />
{/* <OverviewFiltersDrawer projectId={projectId} mode="events" /> */}
</div>
<OverviewFiltersButtons />
</StickyBelowHeader>
<div className="grid grid-cols-6 gap-4 p-4">
<div className="col-span-6">
<OverviewLiveHistogram projectId={projectId} />
<div className="flex gap-2">
<ServerLiveCounter projectId={projectId} />
</div>
<OverviewMetrics projectId={projectId} />
<OverviewTopSources projectId={projectId} />
<OverviewTopPages projectId={projectId} />
<OverviewTopDevices projectId={projectId} />
<OverviewTopEvents projectId={projectId} />
<OverviewTopGeo projectId={projectId} />
</div>
<OverviewFiltersButtons />
</StickyBelowHeader>
<div className="mx-auto grid max-w-7xl grid-cols-6 gap-4 p-4">
<OverviewMetrics projectId={projectId} />
<OverviewTopSources projectId={projectId} />
<OverviewTopPages projectId={projectId} />
<OverviewTopDevices projectId={projectId} />
<OverviewTopEvents projectId={projectId} />
<OverviewTopGeo projectId={projectId} />
</div>
</div>
</div>

View File

@@ -12,8 +12,6 @@ import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { toast } from 'sonner';
import { useOverviewOptions } from '../useOverviewOptions';
export interface LiveCounterProps {
data: number;
projectId: string;
@@ -27,7 +25,6 @@ const AnimatedNumbers = dynamic(() => import('react-animated-numbers'), {
const FIFTEEN_SECONDS = 1000 * 15;
export default function LiveCounter({ data = 0, projectId }: LiveCounterProps) {
const { setLiveHistogram } = useOverviewOptions();
const client = useQueryClient();
const [counter, setCounter] = useState(data);
const lastRefresh = useRef(Date.now());
@@ -47,38 +44,33 @@ export default function LiveCounter({ data = 0, projectId }: LiveCounterProps) {
return (
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setLiveHistogram((p) => !p)}
className="flex h-8 items-center gap-2 rounded border border-border px-3 font-medium leading-none"
>
<div className="relative">
<div
className={cn(
'h-3 w-3 animate-ping rounded-full bg-emerald-500 opacity-100 transition-all',
counter === 0 && 'bg-destructive opacity-0'
)}
></div>
<div
className={cn(
'absolute left-0 top-0 h-3 w-3 rounded-full bg-emerald-500 transition-all',
counter === 0 && 'bg-destructive'
)}
></div>
</div>
<AnimatedNumbers
includeComma
transitions={(index) => ({
type: 'spring',
duration: index + 0.3,
damping: 10,
stiffness: 200,
})}
animateToNumber={counter}
locale="en"
<TooltipTrigger className="flex h-8 items-center gap-2 rounded border border-border px-3 font-medium leading-none">
<div className="relative">
<div
className={cn(
'h-3 w-3 animate-ping rounded-full bg-emerald-500 opacity-100 transition-all',
counter === 0 && 'bg-destructive opacity-0'
)}
/>
</button>
<div
className={cn(
'absolute left-0 top-0 h-3 w-3 rounded-full bg-emerald-500 transition-all',
counter === 0 && 'bg-destructive'
)}
/>
</div>
<AnimatedNumbers
includeComma
transitions={(index) => ({
type: 'spring',
duration: index + 0.3,
damping: 10,
stiffness: 200,
})}
animateToNumber={counter}
locale="en"
/>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>{counter} unique visitors last 5 minutes</p>

View File

@@ -5,9 +5,7 @@ import { cn } from '@/utils/cn';
import type { IChartInput } from '@openpanel/validation';
import AnimateHeight from '../animate-height';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import { useOverviewOptions } from './useOverviewOptions';
interface OverviewLiveHistogramProps {
projectId: string;
@@ -16,7 +14,6 @@ interface OverviewLiveHistogramProps {
export function OverviewLiveHistogram({
projectId,
}: OverviewLiveHistogramProps) {
const { liveHistogram } = useOverviewOptions();
const report: IChartInput = {
projectId,
events: [
@@ -80,11 +77,11 @@ export function OverviewLiveHistogram({
];
return (
<Wrapper count={0} open={liveHistogram}>
<Wrapper count={0}>
{staticArray.map((percent, i) => (
<div
key={i}
className="flex-1 animate-pulse rounded-md bg-slate-200 dark:bg-slate-800"
className="flex-1 animate-pulse rounded bg-slate-200 dark:bg-slate-800"
style={{ height: `${percent}%` }}
/>
))}
@@ -97,14 +94,14 @@ export function OverviewLiveHistogram({
}
return (
<Wrapper open={liveHistogram} count={liveCount}>
<Wrapper count={liveCount}>
{minutes.map((minute) => {
return (
<Tooltip key={minute.date}>
<TooltipTrigger asChild>
<div
className={cn(
'flex-1 rounded-md transition-all ease-in-out hover:scale-110',
'flex-1 rounded transition-all ease-in-out hover:scale-110',
minute.count === 0 ? 'bg-slate-200' : 'bg-blue-600'
)}
style={{
@@ -127,22 +124,19 @@ export function OverviewLiveHistogram({
}
interface WrapperProps {
open: boolean;
children: React.ReactNode;
count: number;
}
function Wrapper({ open, children, count }: WrapperProps) {
function Wrapper({ children, count }: WrapperProps) {
return (
<AnimateHeight open={open}>
<div className="flex flex-col">
<div className="relative -top-2 text-center text-xs font-medium text-muted-foreground">
{count} unique vistors last 30 minutes
</div>
<div className="relative flex aspect-[6/1] max-h-[150px] w-full flex-1 items-end gap-0.5 md:aspect-[10/1] md:gap-2">
{children}
</div>
<div className="flex h-full flex-col">
<div className="relative mb-1 text-xs font-medium text-muted-foreground">
{count} unique vistors last 30 minutes
</div>
</AnimateHeight>
<div className="relative flex h-full w-full flex-1 items-end gap-1">
{children}
</div>
</div>
);
}

View File

@@ -1,14 +1,14 @@
'use client';
import { WidgetHead } from '@/components/overview/overview-widget';
import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
import { ChartSwitch } from '@/components/report/chart';
import { Widget, WidgetBody } from '@/components/widget';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import { cn } from '@/utils/cn';
import type { IChartInput } from '@openpanel/validation';
import { OverviewLiveHistogram } from './overview-live-histogram';
interface OverviewMetricsProps {
projectId: string;
}
@@ -191,30 +191,40 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
const selectedMetric = reports[metric]!;
return (
<div className="card col-span-6 p-4">
<div className="-mx-2 -mt-2 mb-2 grid grid-cols-6 gap-2">
{reports.map((report, index) => (
<button
key={index}
<>
<div className="relative -top-0.5 col-span-6 -m-4 mb-0 md:m-0">
<div className="card mb-2 grid grid-cols-4">
{reports.map((report, index) => (
<button
key={index}
className={cn(
'col-span-2 flex-1 p-4 shadow-[0_0_0_0.5px] shadow-border md:col-span-1',
index === metric && 'bg-slate-50'
)}
onClick={() => {
setMetric(index);
}}
>
<ChartSwitch hideID {...report} />
</button>
))}
<div
className={cn(
'col-span-3 rounded-lg border border-background p-2 transition-all max-md:flex-1 md:col-span-2 lg:col-span-1 [&_[role="heading"]]:text-xs',
index === metric && 'border-border'
'col-span-4 min-h-28 flex-1 p-4 shadow-[0_0_0_0.5px] shadow-border max-md:row-start-1 md:col-span-2'
)}
onClick={() => {
setMetric(index);
}}
>
<ChartSwitch hideID {...report} />
{/* add active border */}
</button>
))}
<OverviewLiveHistogram projectId={projectId} />
</div>
</div>
<div className="card col-span-6 p-4">
<ChartSwitch
key={selectedMetric.id}
hideID
{...selectedMetric}
chartType="linear"
/>
</div>
</div>
<ChartSwitch
key={selectedMetric.id}
hideID
{...selectedMetric}
chartType="linear"
/>
</div>
</>
);
}

View File

@@ -46,12 +46,6 @@ export function useOverviewOptions() {
parseAsInteger.withDefault(0).withOptions(nuqsOptions)
);
// Toggles
const [liveHistogram, setLiveHistogram] = useQueryState(
'live',
parseAsBoolean.withDefault(true).withOptions(nuqsOptions)
);
return {
previous,
setPrevious,
@@ -72,9 +66,5 @@ export function useOverviewOptions() {
// Computed
interval,
// Toggles
liveHistogram,
setLiveHistogram,
};
}

View File

@@ -71,9 +71,9 @@ export function MetricCard({
{({ width, height }) => (
<AreaChart
width={width}
height={height / 2.5}
height={height / 3}
data={serie.data}
style={{ marginTop: (height / 2.5) * 1.5 }}
style={{ marginTop: (height / 3) * 2 }}
>
<Area
dataKey="count"
@@ -89,12 +89,9 @@ export function MetricCard({
</div>
<div className="relative">
<div className="flex items-center justify-between gap-2">
<div className="flex min-w-0 items-center gap-2 text-left font-medium">
<div className="flex min-w-0 items-center gap-2 text-left font-semibold">
<ColorSquare>{serie.event.id}</ColorSquare>
<span
role="heading"
className="overflow-hidden text-ellipsis whitespace-nowrap"
>
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-muted-foreground">
{serie.name || serie.event.displayName || serie.event.name}
</span>
</div>

View File

@@ -25,7 +25,7 @@ function BarHover({ x, y, width, height, top, left, right, bottom }: any) {
return (
<rect
{...{ x, y, width, height, top, left, right, bottom }}
rx="8"
rx="3"
fill={bg}
fillOpacity={0.5}
/>
@@ -79,7 +79,7 @@ export function ReportHistogramChart({
dataKey={`${serie.id}:prev:count`}
fill={getChartColor(serie.index)}
fillOpacity={0.2}
radius={8}
radius={3}
/>
)}
<Bar
@@ -87,7 +87,7 @@ export function ReportHistogramChart({
name={serie.name}
dataKey={`${serie.id}:count`}
fill={getChartColor(serie.index)}
radius={8}
radius={3}
/>
</React.Fragment>
);

View File

@@ -144,9 +144,7 @@
}
.card {
box-shadow: 0 1px 2px 0.5px rgba(0, 0, 0, 0.08);
border: 0 !important;
@apply rounded-xl bg-background;
@apply border border-border bg-background;
}
}