import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { RouterLink } from '@angular/router'; import { PiEmptyStateComponent } from '@pi/ui'; import { ProjectFacade } from '@task-stream/web/tracker/application'; import { clock, initials, timeAgo } from '../ui/copy'; /** Live feed of everything that happens in the project, as a log. */ @Component({ selector: 'ts-activity-page', changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, PiEmptyStateComponent], template: `

En directo

{{ facade.activity().length }} eventos
@if (facade.activity().length) {
    @for (entry of facade.activity(); track entry.id) {
  1. {{ initialsOf(entry.actor.name) }} {{ entry.summary }} @if (entry.issueKey) { {{ entry.issueKey }} } @else { } {{ ago(entry.occurredAt) }}
  2. }
} @else { } `, styles: ` .ts-log-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; .ts-eyebrow { display: inline-flex; align-items: center; gap: 0.5rem; } .ts-muted { font-size: 0.75rem; } } .ts-live { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--ts-s-done); animation: ts-pulse 2s ease-out infinite; } @keyframes ts-pulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ts-s-done) 60%, transparent); } 70%, 100% { box-shadow: 0 0 0 0.5rem transparent; } } .ts-log { margin: 0; padding: 0.25rem 0; list-style: none; li { display: grid; grid-template-columns: 3.5rem 1.75rem minmax(0, 1fr) auto 8.5rem; align-items: center; gap: 0.875rem; margin: 0; padding: 0.625rem 1.125rem; border-bottom: 1px dashed var(--ts-line); animation: ts-in 0.35s ease-out; &:last-child { border-bottom: 0; } &:first-child { animation-name: ts-in, ts-flash; animation-duration: 0.35s, 1.6s; } } } .ts-log__time, .ts-log__ago { font-family: var(--ts-font-mono); font-size: 0.75rem; color: var(--ts-muted); } .ts-log__ago { text-align: right; white-space: nowrap; } .ts-log__who { display: inline-grid; place-items: center; width: 1.75rem; height: 1.75rem; border-radius: 50%; background: color-mix(in srgb, var(--ts-brand) 16%, var(--ts-surface)); color: color-mix(in srgb, var(--ts-brand) 70%, var(--ts-ink)); font-family: var(--ts-font-mono); font-size: 0.625rem; font-weight: 600; } .ts-log__what { font-size: 0.875rem; color: var(--ts-ink-soft); } a.ts-key { text-decoration: none; &:hover { color: var(--ts-brand); border-color: var(--ts-brand); } } @keyframes ts-in { from { opacity: 0; transform: translateY(-4px); } } @keyframes ts-flash { from { background: color-mix(in srgb, var(--ts-brand) 10%, transparent); } } @media (max-width: 640px) { .ts-log li { grid-template-columns: 1.75rem minmax(0, 1fr) auto; gap: 0.625rem; padding-inline: 0.875rem; } .ts-log__time, .ts-log__ago { display: none; } } `, }) export class ActivityPage { protected readonly facade = inject(ProjectFacade); protected readonly ago = timeAgo; protected readonly initialsOf = initials; protected readonly clock = clock; }