import { ChangeDetectionStrategy, Component, effect, inject, untracked, } from '@angular/core'; import { RouterLink } from '@angular/router'; import { PiEmptyStateComponent, PiNoteComponent, PiProgressBarComponent, PiSkeletonComponent, } from '@pi/ui'; import { EpicsFacade, ProjectFacade, provideEpicsFacade, } from '@task-stream/web/tracker/application'; import { Epic } from '@task-stream/web/tracker/domain'; import { statusLook, typeLook } from '../ui/copy'; /** /p/:key/epics — the big pieces of work and how far each has got. */ @Component({ selector: 'ts-epics-page', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ RouterLink, PiEmptyStateComponent, PiNoteComponent, PiProgressBarComponent, PiSkeletonComponent, ], providers: [provideEpicsFacade()], template: `

Una épica es un ticket que agrupa a otros. Cuelga tickets de ella desde su ficha («Subtareas») y aquí verás cuánto falta.

@if (facade.error(); as error) { } @else if (!facade.loaded()) { } @else if (facade.epics().length) { } @else { } `, styles: ` .ts-epics { display: grid; gap: 0.75rem; margin: 0; padding: 0; list-style: none; } .ts-epic { display: grid; gap: 0.75rem; padding: 1rem 1.125rem; border-left: 3px solid var(--tone, var(--ts-brand)); } .ts-epic__head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.625rem; align-items: center; } .ts-epic__title { overflow: hidden; color: inherit; font-weight: 600; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; &:hover { color: var(--ts-brand); } } `, }) export class EpicsPage { protected readonly facade = inject(EpicsFacade); private readonly project = inject(ProjectFacade); constructor() { effect(() => { const projectId = this.project.project()?.id; if (projectId) untracked(() => this.facade.open(projectId)); }); } protected summary(epic: Epic): string { const { finished, total, percent, storyPoints, finishedStoryPoints } = epic.progress; const points = storyPoints ? ` · ${finishedStoryPoints}/${storyPoints} puntos` : ''; return `${percent}% · ${finished} de ${total} tickets terminados${points}`; } protected typeTone = (epic: Epic) => typeLook(this.project.workflow(), epic.issue.type).tone; protected statusTone = (epic: Epic) => statusLook(this.project.workflow(), epic.issue.status).tone; protected statusLabel = (epic: Epic) => statusLook(this.project.workflow(), epic.issue.status).label; }