feat: dashboard v2, esm, upgrades (#211)

* esm

* wip

* wip

* wip

* wip

* wip

* wip

* subscription notice

* wip

* wip

* wip

* fix envs

* fix: update docker build

* fix

* esm/types

* delete dashboard :D

* add patches to dockerfiles

* update packages + catalogs + ts

* wip

* remove native libs

* ts

* improvements

* fix redirects and fetching session

* try fix favicon

* fixes

* fix

* order and resize reportds within a dashboard

* improvements

* wip

* added userjot to dashboard

* fix

* add op

* wip

* different cache key

* improve date picker

* fix table

* event details loading

* redo onboarding completely

* fix login

* fix

* fix

* extend session, billing and improve bars

* fix

* reduce price on 10M
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-10-16 12:27:44 +02:00
committed by GitHub
parent 436e81ecc9
commit 81a7e5d62e
741 changed files with 32695 additions and 16996 deletions

View File

@@ -0,0 +1,222 @@
import { EventIcon } from '@/components/events/event-icon';
import { ProjectLink } from '@/components/links';
import { SerieIcon } from '@/components/report-chart/common/serie-icon';
import { useNumber } from '@/hooks/use-numer-formatter';
import { pushModal } from '@/modals';
import { formatDateTime, formatTime } from '@/utils/date';
import { getProfileName } from '@/utils/getters';
import type { ColumnDef } from '@tanstack/react-table';
import { isToday } from 'date-fns';
import type { IServiceEvent } from '@openpanel/db';
export function useColumns() {
const number = useNumber();
const columns: ColumnDef<IServiceEvent>[] = [
{
size: 300,
accessorKey: 'name',
header: 'Name',
cell({ row }) {
const { name, path, duration } = row.original;
const renderName = () => {
if (name === 'screen_view') {
if (path.includes('/')) {
return <span className="max-w-md truncate">{path}</span>;
}
return (
<>
<span className="text-muted-foreground">Screen: </span>
<span className="max-w-md truncate">{path}</span>
</>
);
}
return name.replace(/_/g, ' ');
};
const renderDuration = () => {
if (name === 'screen_view') {
return (
<span className="text-muted-foreground">
{number.shortWithUnit(duration / 1000, 'min')}
</span>
);
}
return null;
};
return (
<div className="flex items-center gap-2">
<button
type="button"
className="transition-transform hover:scale-105"
onClick={() => {
pushModal('EditEvent', {
id: row.original.id,
});
}}
>
<EventIcon
size="sm"
name={row.original.name}
meta={row.original.meta}
/>
</button>
<span className="flex gap-2">
<button
type="button"
onClick={() => {
pushModal('EventDetails', {
id: row.original.id,
createdAt: row.original.createdAt,
projectId: row.original.projectId,
});
}}
className="font-medium"
>
{renderName()}
</button>
{renderDuration()}
</span>
</div>
);
},
},
{
accessorKey: 'createdAt',
header: 'Created at',
size: 170,
cell({ row }) {
const date = row.original.createdAt;
return (
<div>{isToday(date) ? formatTime(date) : formatDateTime(date)}</div>
);
},
},
{
accessorKey: 'profileId',
header: 'Profile',
cell({ row }) {
const { profile, profileId, deviceId } = row.original;
if (profile) {
return (
<ProjectLink
href={`/profiles/${profile.id}`}
className="whitespace-nowrap font-medium hover:underline"
>
{getProfileName(profile)}
</ProjectLink>
);
}
if (profileId && profileId !== deviceId) {
return (
<ProjectLink
href={`/profiles/${profileId}`}
className="whitespace-nowrap font-medium hover:underline"
>
Unknown
</ProjectLink>
);
}
if (deviceId) {
return (
<ProjectLink
href={`/profiles/${deviceId}`}
className="whitespace-nowrap font-medium hover:underline"
>
Anonymous
</ProjectLink>
);
}
return null;
},
},
{
accessorKey: 'sessionId',
header: 'Session ID',
size: 320,
},
{
accessorKey: 'deviceId',
header: 'Device ID',
size: 320,
},
{
accessorKey: 'country',
header: 'Country',
size: 150,
cell({ row }) {
const { country, city } = row.original;
return (
<div className="row items-center gap-2 min-w-0">
<SerieIcon name={country} />
<span className="truncate">{city}</span>
</div>
);
},
},
{
accessorKey: 'os',
header: 'OS',
size: 130,
cell({ row }) {
const { os } = row.original;
return (
<div className="row items-center gap-2 min-w-0">
<SerieIcon name={os} />
<span className="truncate">{os}</span>
</div>
);
},
},
{
accessorKey: 'browser',
header: 'Browser',
size: 110,
cell({ row }) {
const { browser } = row.original;
return (
<div className="row items-center gap-2 min-w-0">
<SerieIcon name={browser} />
<span className="truncate">{browser}</span>
</div>
);
},
},
{
accessorKey: 'properties',
header: 'Properties',
size: 400,
cell({ row }) {
const { properties } = row.original;
const filteredProperties = Object.fromEntries(
Object.entries(properties || {}).filter(
([key]) => !key.startsWith('__'),
),
);
const items = Object.entries(filteredProperties);
return (
<div className="row flex-wrap gap-x-4 gap-y-1 overflow-hidden text-sm">
{items.slice(0, 4).map(([key, value]) => (
<div key={key} className="row items-center gap-1 min-w-0">
<span className="text-muted-foreground">{key}</span>
<span className="truncate font-medium">{String(value)}</span>
</div>
))}
{items.length > 5 && (
<span className="truncate">{items.length - 5} more</span>
)}
</div>
);
},
},
];
return columns;
}