diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index 115cc266e..09dc0aaa9 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -472,6 +472,56 @@ function SessionItem(props: { ) } +function ProjectGroupItem(props: { + group: SessionGroup + isCollapsed: boolean + onToggle: () => void + onSelect: (sessionId: string) => void + api: ApiClient | null + selectedSessionId?: string | null +}) { + const { group, isCollapsed, onToggle, onSelect, api, selectedSessionId } = props + return ( +
+
+ + +
+ +
+
+
+ {group.sessions.map((s) => ( + + ))} +
+
+
+
+ ) +} + export function SessionList(props: { sessions: SessionSummary[] onSelect: (sessionId: string) => void @@ -492,6 +542,20 @@ export function SessionList(props: { const [collapseOverrides, setCollapseOverrides] = useState>( () => new Map() ) + const [archivedCollapsed, setArchivedCollapsed] = useState>( + () => new Map() + ) + const isArchivedSectionCollapsed = (machineKey: string): boolean => { + return archivedCollapsed.get(machineKey) ?? true + } + const toggleArchivedSection = (machineKey: string) => { + setArchivedCollapsed(prev => { + const next = new Map(prev) + const currentlyCollapsed = prev.get(machineKey) ?? true + next.set(machineKey, !currentlyCollapsed) + return next + }) + } const isGroupCollapsed = (group: SessionGroup): boolean => { const override = collapseOverrides.get(group.key) if (override !== undefined) return override @@ -547,11 +611,11 @@ export function SessionList(props: { // Auto-expand group (and machine) containing selected session useEffect(() => { if (!selectedSessionId) return + const group = groups.find(g => + g.sessions.some(s => s.id === selectedSessionId) + ) + if (!group) return setCollapseOverrides(prev => { - const group = groups.find(g => - g.sessions.some(s => s.id === selectedSessionId) - ) - if (!group) return prev const next = new Map(prev) let changed = false // Expand project group if collapsed @@ -567,6 +631,16 @@ export function SessionList(props: { } return changed ? next : prev }) + // Auto-expand archived section if selected session is in an archived group + if (!group.hasActiveSession) { + const machineKey = group.machineId ?? UNKNOWN_MACHINE_ID + setArchivedCollapsed(prev => { + if (prev.get(machineKey) === false) return prev + const next = new Map(prev) + next.set(machineKey, false) + return next + }) + } }, [selectedSessionId, groups]) // Clean up stale collapse overrides @@ -588,6 +662,19 @@ export function SessionList(props: { } return changed ? next : prev }) + setArchivedCollapsed(prev => { + if (prev.size === 0) return prev + const knownMachines = new Set(groups.map(g => g.machineId ?? UNKNOWN_MACHINE_ID)) + const next = new Map(prev) + let changed = false + for (const key of next.keys()) { + if (!knownMachines.has(key)) { + next.delete(key) + changed = true + } + } + return changed ? next : prev + }) }, [groups]) return ( @@ -629,45 +716,64 @@ export function SessionList(props: {
- {mg.projectGroups.map((group) => { - const isCollapsed = isGroupCollapsed(group) + {(() => { + const activeProjectGroups = mg.projectGroups.filter(g => g.hasActiveSession) + const archivedProjectGroups = mg.projectGroups.filter(g => !g.hasActiveSession) + const showArchivedSection = activeProjectGroups.length > 0 && archivedProjectGroups.length > 0 + const machineKey = mg.machineId ?? UNKNOWN_MACHINE_ID + const archivedHidden = showArchivedSection && isArchivedSectionCollapsed(machineKey) + const visibleGroups = showArchivedSection ? activeProjectGroups : mg.projectGroups + return ( -
-
toggleGroup(group.key, isCollapsed)} - title={group.directory} - > - - - {group.displayName} - - - - ({group.sessions.length}) - -
- - {/* Level 3: Sessions */} -
-
-
- {group.sessions.map((s) => ( - - ))} -
-
-
-
+ <> + {visibleGroups.map((group) => { + const isCollapsed = isGroupCollapsed(group) + return ( + toggleGroup(group.key, isCollapsed)} + onSelect={props.onSelect} + api={api} + selectedSessionId={selectedSessionId} + /> + ) + })} + {showArchivedSection ? ( + <> + +
+
+ {archivedProjectGroups.map((group) => { + const isCollapsed = isGroupCollapsed(group) + return ( + toggleGroup(group.key, isCollapsed)} + onSelect={props.onSelect} + api={api} + selectedSessionId={selectedSessionId} + /> + ) + })} +
+
+ + ) : null} + ) - })} + })()}
diff --git a/web/src/lib/locales/en.ts b/web/src/lib/locales/en.ts index 77b5e1fe0..b0ea32b74 100644 --- a/web/src/lib/locales/en.ts +++ b/web/src/lib/locales/en.ts @@ -40,6 +40,7 @@ export default { // Sessions page 'sessions.count': '{n} sessions in {m} projects', + 'sessions.archived': 'Archived ({n})', 'sessions.new': 'New Session', // Session list diff --git a/web/src/lib/locales/zh-CN.ts b/web/src/lib/locales/zh-CN.ts index ca698dce3..d0ada89a4 100644 --- a/web/src/lib/locales/zh-CN.ts +++ b/web/src/lib/locales/zh-CN.ts @@ -40,6 +40,7 @@ export default { // Sessions page 'sessions.count': '{n} 个会话,{m} 个项目', + 'sessions.archived': '已归档 ({n})', 'sessions.new': '新建会话', // Session list