Files
flxn-app/src/features/telemetry/components/sessions/sessions-list.tsx
T
kyle e4d1ba720a feat(telemetry): session journey view replaces raw events table
- sessions listed from client_events grouped by session id (7d window)
- timeline sheet merges page views, client events, errors, and server-fn
  activities joined by player within the session window
- vitals excluded from timelines; gap markers over 5 minutes
2026-08-25 22:56:09 -07:00

82 lines
2.6 KiB
TypeScript

import { Suspense, useState } from "react";
import { Plural, Trans, useLingui } from "@lingui/react/macro";
import { Text } from "@mantine/core";
import Sheet from "@/components/sheet/sheet";
import { ListSkeleton } from "@/components/skeletons";
import { useSessions } from "../../queries";
import type { SessionSummary } from "../../types";
import { PaginatedList, RelativeTime, TelemetryListItem } from "../shared";
import SessionTimelineView from "./session-timeline";
export const defaultSessionParams = { days: 7, page: 1 } as const;
const playerName = (player?: SessionSummary["player"]): string | null => {
if (!player) return null;
return [player.first_name, player.last_name].filter(Boolean).join(" ") || null;
};
const durationLabel = (session: SessionSummary): string => {
const ms =
Date.parse(session.lastSeen.replace(" ", "T")) -
Date.parse(session.started.replace(" ", "T"));
const minutes = Math.round(ms / 60000);
return minutes < 1 ? "<1m" : `${minutes}m`;
};
export const SessionsList = () => {
const { t } = useLingui();
const [page, setPage] = useState(1);
const [selected, setSelected] = useState<SessionSummary | null>(null);
const { data } = useSessions({ days: 7, page });
return (
<>
<PaginatedList
items={data.items}
keyOf={(session) => session.session_id}
empty={t`No sessions yet`}
page={data.page}
totalPages={data.totalPages}
onPageChange={setPage}
renderItem={(session) => (
<TelemetryListItem
title={playerName(session.player) ?? t`Anonymous`}
meta={[
<RelativeTime key="time" date={session.lastSeen} />,
<Plural key="pages" value={session.pageViews} one="# page" other="# pages" />,
durationLabel(session),
]}
error={
session.errors > 0 ? (
<Plural value={session.errors} one="# error" other="# errors" />
) : undefined
}
onClick={() => setSelected(session)}
/>
)}
/>
<Sheet
title={selected ? (playerName(selected.player) ?? t`Anonymous`) : ""}
opened={selected !== null}
onChange={() => setSelected(null)}
>
{selected && (
<Suspense fallback={<ListSkeleton rows={8} />}>
<SessionTimelineView sessionId={selected.session_id} />
</Suspense>
)}
</Sheet>
{data.totalItems > 0 && (
<Text size="xs" c="dimmed" px="md" py="sm">
<Trans>Last 7 days</Trans>
</Text>
)}
</>
);
};
export default SessionsList;