<script lang="ts">
import { placeLabel } from "../../utils/places.js";
import { analytics } from "../../stores/analytics.svelte.js";
import { sessions } from "../../stores/sessions.svelte.js";
import { context } from "../../stores/context.svelte.js";
import LoadingSpinner from "../layout/LoadingSpinner.svelte";
function truncate(text: string, max: number): string {
if (text.length <= max) return text;
return text.slice(0, max - 1) + "\u2026";
}
function formatDuration(mins: number): string {
const total = Math.round(mins);
if (total < 60) return `${total}m`;
const h = Math.floor(total / 60);
const m = total % 60;
return m > 0 ? `${h}h ${m}m` : `${h}h`;
}
function handleSessionClick(id: string) {
if (analytics.includeOneShot && !sessions.filters.includeOneShot) {
sessions.filters.includeOneShot = true;
sessions.invalidateFilterCaches();
}
void context.openSession(id, false);
}
</script>
<div class="top-sessions-container">
<div class="top-header">
<h3 class="chart-title">Top Sessions</h3>
<div class="metric-toggle">
<button
class="toggle-btn"
class:active={analytics.topMetric === "messages"}
onclick={() => analytics.setTopMetric("messages")}
>
By Messages
</button>
<button
class="toggle-btn"
class:active={analytics.topMetric === "duration"}
onclick={() => analytics.setTopMetric("duration")}
>
By Duration
</button>
</div>
</div>
{#if !analytics.loaded.topSessions || analytics.loading.topSessions}
<LoadingSpinner label="Loading top sessions" />
{:else if analytics.errors.topSessions}
<div class="error">
{analytics.errors.topSessions}
<button
class="retry-btn"
onclick={() => analytics.fetchTopSessions()}
>
Retry
</button>
</div>
{:else if analytics.topSessions && analytics.topSessions.sessions.length > 0}
<div class="session-list">
{#each analytics.topSessions.sessions as session, i}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="session-row"
onclick={() => handleSessionClick(session.id)}
>
<span class="rank">{i + 1}</span>
<div class="session-info">
<span class="session-label">
{session.first_message
? truncate(session.first_message, 50)
: session.id.slice(0, 12)}
</span>
<span class="session-project">{placeLabel(session)}</span>
</div>
<span class="session-metric">
{#if analytics.topMetric === "duration"}
{formatDuration(session.duration_min)}
{:else}
{session.message_count}
{/if}
</span>
</div>
{/each}
</div>
{:else}
<div class="empty">No sessions in range</div>
{/if}
</div>
<style>
.top-sessions-container {
flex: 1;
display: flex;
flex-direction: column;
}
.top-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--wb-spacing-8);
}
.chart-title {
font-size: var(--wb-font-size-12);
font-weight: 600;
color: var(--vscode-editor-foreground);
}
.metric-toggle {
display: flex;
gap: var(--wb-spacing-2);
background: var(--vscode-input-background);
border-radius: var(--wsb-radius-sm);
padding: var(--wb-spacing-1);
}
.toggle-btn {
padding: var(--wb-spacing-2) var(--wb-spacing-8);
font-size: var(--wb-font-size-10);
border-radius: var(--wsb-radius-sm);
color: var(--vscode-disabledForeground);
cursor: pointer;
transition: background 0.1s, color 0.1s;
}
.toggle-btn.active {
background: var(--vscode-sideBar-background);
color: var(--vscode-editor-foreground);
font-weight: 500;
}
.toggle-btn:hover:not(.active) {
color: var(--vscode-foreground);
}
.session-list {
display: flex;
flex-direction: column;
gap: var(--wb-spacing-2);
overflow-y: auto;
flex: 1;
}
.session-row {
display: flex;
align-items: center;
gap: var(--wb-spacing-8);
padding: var(--wb-spacing-4) var(--wb-spacing-6);
border-radius: var(--wsb-radius-sm);
cursor: pointer;
transition: background 0.1s;
}
.session-row:hover {
background: var(--vscode-list-hoverBackground);
}
.rank {
flex-shrink: 0;
width: var(--wb-control-size-18);
text-align: right;
font-size: var(--wb-font-size-10);
font-weight: 600;
color: var(--vscode-disabledForeground);
font-family: var(--wsb-font-mono);
}
.session-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: var(--wb-spacing-1);
}
.session-label {
font-size: var(--wb-font-size-11);
color: var(--vscode-foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.session-project {
font-size: var(--wb-font-size-9);
color: var(--vscode-disabledForeground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.session-metric {
flex-shrink: 0;
font-size: var(--wb-font-size-11);
font-weight: 500;
font-family: var(--wsb-font-mono);
color: var(--vscode-focusBorder);
min-width: var(--wb-control-size-36);
text-align: right;
}
.empty {
color: var(--vscode-disabledForeground);
font-size: var(--wb-font-size-12);
padding: var(--wb-spacing-24);
text-align: center;
}
.error {
color: var(--vscode-errorForeground);
font-size: var(--wb-font-size-12);
padding: var(--wb-spacing-12);
display: flex;
align-items: center;
gap: var(--wb-spacing-8);
}
.retry-btn {
padding: var(--wb-spacing-2) var(--wb-spacing-8);
border: 1px solid currentColor;
border-radius: var(--wsb-radius-sm);
font-size: var(--wb-font-size-11);
color: inherit;
cursor: pointer;
}
</style>