import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal, TemplateRef, viewChild, } from '@angular/core'; import { form, FormField, maxLength, pattern, required, } from '@angular/forms/signals'; import { PiActionButtonComponent, PiEmptyStateComponent, PiLabelDirective, PiSkeletonComponent, PiTextareaComponent, PiTextFieldComponent, } from '@pi/ui'; import { DrawerService } from '@task-stream/web/shared/ui'; import { ProjectsFacade } from '@task-stream/web/tracker/application'; import { AuthFacade } from '@task-stream/web/identity/application'; import { GoToComponent } from '../ui/go-to'; import { ProjectCardComponent } from '../ui/project-card'; import { MyWorkComponent } from './my-work'; @Component({ selector: 'ts-projects-page', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ FormField, GoToComponent, ProjectCardComponent, MyWorkComponent, PiActionButtonComponent, PiEmptyStateComponent, PiSkeletonComponent, PiTextFieldComponent, PiTextareaComponent, PiLabelDirective, ], template: `

Pireon · TaskStream

Hola, {{ firstName() }}

Lo tuyo en todos tus proyectos, al día en tiempo real.

@if (facade.projects().length) {

Proyectos {{ pad(facade.projects().length) }}

} @if (facade.loading() && !facade.projects().length) {
@for (i of [1, 2, 3]; track i) { }
} @else if (!facade.projects().length) { } @else {
@for (project of facade.sorted(); track project.id) { }
}
Clave (prefijo de los tickets) Nombre Descripción
`, styles: ` .ts-home-actions { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; @media (max-width: 640px) { width: 100%; ts-go-to { flex: 1; } } } .ts-section-header h2 { display: flex; align-items: baseline; gap: 0.625rem; .ts-mono { font-size: 0.875rem; font-weight: 400; } } .ts-public-check { display: flex; align-items: flex-start; gap: 0.625rem; font-size: 0.8125rem; color: var(--ts-muted); cursor: pointer; input { width: 1rem; height: 1rem; margin-top: 0.125rem; accent-color: var(--ts-brand); } strong { display: block; color: var(--ts-ink); font-size: 0.875rem; } } .ts-projects { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr)); gap: 1.25rem; } `, }) export class ProjectsPage implements OnInit { protected readonly facade = inject(ProjectsFacade); private readonly auth = inject(AuthFacade); protected readonly firstName = computed( () => this.auth.user()?.name.split(/\s+/)[0] ?? '', ); private readonly drawer = inject(DrawerService); private readonly createForm = viewChild.required>('createForm'); private readonly model = signal({ key: '', name: '', description: '', isPublic: false, }); protected readonly f = form(this.model, (p) => { required(p.key); pattern(p.key, /^[A-Za-z][A-Za-z0-9]{1,9}$/, { message: '2-10 letras/números, empezando por letra', }); required(p.name); maxLength(p.name, 120); }); protected readonly pad = (n: number) => String(n).padStart(2, '0'); ngOnInit(): void { this.facade.load(); } protected openCreate(): void { this.model.set({ key: '', name: '', description: '', isPublic: false }); this.drawer.open('Nuevo proyecto', this.createForm()); } protected create(): void { if (this.f().invalid()) return; const { key, name, description, isPublic } = this.model(); this.facade.create({ key: key.toUpperCase(), name, description: description || null, isPublic, }); this.drawer.closeAll(); } }