- 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
82 lines
2.6 KiB
TypeScript
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;
|