CUB-27: Responsive layout and adaptive navigation
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m46s

This commit is contained in:
2026-04-28 08:43:57 -04:00
committed by Rex
parent 048101e85c
commit 999f6614ce
13 changed files with 558 additions and 447 deletions

View File

@@ -1,51 +1,22 @@
import { ChangeDetectionStrategy, Component, signal, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, Component, signal, computed, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatChipsModule } from '@angular/material/chips';
import { AgentCardComponent } from '../../command-hub/components/agent-card/agent-card.component';
import { AgentSessionDrawerComponent } from '../../components/agent-session-drawer/index';
import { AgentCardData } from '../../models/agent.model';
import { AgentStatus } from '../../models/agent.model';
// ============================================================================
// Hub Page — Fleet status grid
// CUB-26: Integrates AgentCard click/long-press with session drawer.
// ============================================================================
/**
* Filter options for the hub page agent card grid.
* Per CUB-27: "Filter chip group (All, Active, Error, etc.) with horizontal scroll on mobile"
*/
export type AgentFilter = 'all' | AgentStatus;
@Component({
selector: 'app-hub-page',
standalone: true,
imports: [CommonModule, AgentCardComponent, AgentSessionDrawerComponent],
template: `
<div class="hub-page">
<h1 class="hub-page__title">Command Hub</h1>
<div class="hub-page__grid">
@for (agent of agents(); track agent.id) {
<app-agent-card
[status]="agent.status"
[task]="agent.currentTask ?? ''"
[progress]="agent.taskProgress ?? 0"
[sessionKey]="agent.sessionKey"
[channel]="agent.channel"
[lastActivity]="agent.lastActivity"
[agentId]="agent.id"
[displayName]="agent.displayName"
[role]="agent.role"
[errorMessage]="agent.errorMessage ?? ''"
(cardClick)="onCardClick($event)"
(cardLongPress)="onCardLongPress($event)"
/>
} @empty {
<p class="hub-page__empty">No agents online</p>
}
</div>
</div>
<!-- Agent Session Drawer -->
<app-agent-session-drawer
[isMobile]="isMobile()"
(openSession)="onOpenSession($event)"
(pinToDashboard)="onPinToDashboard($event)"
(drawerClose)="onDrawerClose()"
/>
`,
imports: [CommonModule, MatChipsModule, AgentCardComponent, AgentSessionDrawerComponent],
templateUrl: './hub-page.component.html',
styleUrl: './hub-page.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
@@ -54,6 +25,17 @@ export class HubPageComponent {
readonly isMobile = signal(false);
protected readonly filters: { label: string; value: AgentFilter }[] = [
{ label: 'All', value: 'all' },
{ label: 'Active', value: 'active' },
{ label: 'Idle', value: 'idle' },
{ label: 'Thinking', value: 'thinking' },
{ label: 'Error', value: 'error' },
{ label: 'Offline', value: 'offline' },
];
protected readonly activeFilter = signal<AgentFilter>('all');
/** Stub agent data (TODO: wire to AgentStatusService / SignalR). */
readonly agents = signal<AgentCardData[]>([
{
@@ -73,10 +55,10 @@ export class HubPageComponent {
displayName: 'Rex',
role: 'Frontend Agent',
status: 'thinking',
currentTask: 'Building agent session drawer',
currentTask: 'Building responsive layout',
taskProgress: 40,
taskElapsed: '02m 30s',
sessionKey: 'agent:rex:telegram:CUB-26:def456',
sessionKey: 'agent:rex:telegram:CUB-27:def456',
channel: 'telegram',
lastActivity: new Date(Date.now() - 30000),
},
@@ -119,6 +101,12 @@ export class HubPageComponent {
},
]);
protected readonly filteredAgents = computed(() => {
const filter = this.activeFilter();
if (filter === 'all') return this.agents();
return this.agents().filter(a => a.status === filter);
});
constructor() {
// Detect mobile viewport
if (typeof window !== 'undefined') {
@@ -128,6 +116,10 @@ export class HubPageComponent {
}
}
protected selectFilter(filter: AgentFilter): void {
this.activeFilter.set(filter);
}
/** Card click → open session drawer with agent details. */
onCardClick(sessionKey: string): void {
const agent = this.agents().find((a) => a.sessionKey === sessionKey);