CUB-45: AgentCard final integration with sub-components

- Assemble AgentCard from AgentStatusBadge, TaskProgressBar, QuickJumpButton
- All 6 inputs functional: status, task, progress, sessionKey, channel, lastActivity
- Left-border accent matches status color (Active: #38BDF8, Idle: #2DD4BF, Thinking: #A78BFA, Error: #F87171)
- Accessibility: role="article" on card, aria-labels on all sections
- Uses tactical dark mode CSS variables from CUB-47
- Added @Input() to QuickJumpButton sessionKey for parent binding
- JumpClick output forwarded from AgentCard
- Build passes, type checking passes
This commit is contained in:
cubecraft-agents[bot]
2026-04-26 13:36:45 +00:00
parent 8d0adeb2e9
commit 8341503a39
17 changed files with 1009 additions and 1 deletions

View File

@@ -0,0 +1,109 @@
// ============================================================================
// Task Progress Bar Component
// Per CUB-44: Determinate progress bar with optional elapsed time display.
// Uses Angular Material mat-progress-bar in determinate mode with tactical
// dark theme styling via CSS custom properties.
// ============================================================================
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatProgressBarModule } from '@angular/material/progress-bar';
/**
* Displays a determinate progress bar with an optional elapsed time indicator.
*
* Usage:
* ```html
* <app-task-progress-bar [progress]="65" />
* <app-task-progress-bar [progress]="42" [showElapsed]="true" />
* ```
*/
@Component({
selector: 'app-task-progress-bar',
standalone: true,
imports: [CommonModule, MatProgressBarModule],
templateUrl: './task-progress-bar.component.html',
styleUrl: './task-progress-bar.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskProgressBarComponent implements OnInit, OnDestroy {
// ---------------------------------------------------------------------------
// Inputs
// ---------------------------------------------------------------------------
/** Current progress percentage (0100). Required. */
@Input({ required: true })
progress!: number;
/** Whether to show elapsed time next to the percentage. Defaults to false. */
@Input()
showElapsed = false;
// ---------------------------------------------------------------------------
// Internal state
// ---------------------------------------------------------------------------
/** Timestamp when the component initialized — used for elapsed calculation. */
startTime = Date.now();
/** Formatted elapsed time string, e.g. "2m 15s ago". */
elapsedText = '';
/** Interval timer for updating the elapsed display. */
private timer: ReturnType<typeof setInterval> | null = null;
constructor(private cdr: ChangeDetectorRef) {}
// ---------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------
ngOnInit(): void {
this.updateElapsed();
if (this.showElapsed) {
// Update elapsed time every second
this.timer = setInterval(() => {
this.updateElapsed();
this.cdr.markForCheck();
}, 1000);
}
}
ngOnDestroy(): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Clamp progress to 0100 for safety. */
get clampedProgress(): number {
return Math.max(0, Math.min(100, this.progress ?? 0));
}
/** Recalculate the elapsed time string. */
private updateElapsed(): void {
const elapsedMs = Date.now() - this.startTime;
const totalSeconds = Math.floor(elapsedMs / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
if (minutes > 0) {
this.elapsedText = `${minutes}m ${seconds}s ago`;
} else {
this.elapsedText = `${seconds}s ago`;
}
}
}