feat(sessions): merge the activity feed into a tab on /sessions

Sessions and Activity Feed are two views of the same event pipeline, so
they now share one page: /sessions renders a tab bar (Sessions | Activity
Feed) with the active view in the ?view= query param, and /activity
redirects to /sessions?view=events. The sidebar entry is dropped.

The tab bar itself moves out of SettingsHub into components/PageTabs so
both hubs share it, and DocumentTitle loses the two branches for paths
that now only redirect.
This commit is contained in:
2026-08-26 09:17:17 +07:00
parent 341197aac0
commit bcfeeb5140
8 changed files with 136 additions and 48 deletions
+49
View File
@@ -0,0 +1,49 @@
/**
* @file History.tsx
* @description Tabbed shell for /sessions that hosts the two views over the same
* event pipeline: the per-session list and the raw activity feed. The active tab
* lives in the `?view=` query param, so `/activity` can redirect into it.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import type { ComponentType } from "react";
import { useSearchParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Activity, FolderOpen } from "lucide-react";
import { PageTabs } from "../components/PageTabs";
import { Sessions } from "./Sessions";
import { ActivityFeed } from "./ActivityFeed";
interface HistoryView {
id: string;
label: string;
Icon: ComponentType<{ className?: string }>;
Panel: ComponentType;
}
/**
* Sessions page shell: grouped sessions and the raw event stream as sibling tabs.
* @returns The tab bar plus the active view.
*/
export function History() {
const { t } = useTranslation("nav");
const [params, setParams] = useSearchParams();
const views: HistoryView[] = [
{ id: "sessions", label: t("sessions"), Icon: FolderOpen, Panel: Sessions },
{ id: "events", label: t("activityFeed"), Icon: Activity, Panel: ActivityFeed },
];
const defaultView = views[0]!;
const active = views.find((v) => v.id === params.get("view")) ?? defaultView;
return (
<div className="space-y-6">
<PageTabs
ariaLabel={t("sessions")}
activeId={active.id}
onSelect={(id) => setParams(id === defaultView.id ? {} : { view: id }, { replace: true })}
tabs={views.map(({ id, label, Icon }) => ({ id, label, Icon }))}
/>
<active.Panel />
</div>
);
}