import { ChangeDetectionStrategy, Component, computed, input, linkedSignal, output, } from '@angular/core'; import { PiDateFieldComponent, PiLabelDirective, PiNumberFieldComponent, PiSelectFieldComponent, PiTextFieldComponent, type PiSelectFieldOption, } from '@pi/ui'; import { CustomFieldDefinition, CustomFieldType, } from '@task-stream/web/tracker/domain'; /** `YYYY-MM-DD` of a local date. */ const dayOf = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; /** * One custom field of a ticket, with the control its type asks for. Lists * and dates are saved when picked; text and numbers when leaving the field. * Emits the new value (null clears it) only when it changed. */ @Component({ selector: 'ts-custom-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ PiDateFieldComponent, PiLabelDirective, PiNumberFieldComponent, PiSelectFieldComponent, PiTextFieldComponent, ], template: ` @switch (field().type) { @case (types.SELECT) { {{ field().name }} } @case (types.DATE) { {{ field().name }} } @case (types.NUMBER) { {{ field().name }} } @default { {{ field().name }} } } `, styles: ` :host { display: block; } `, }) export class CustomFieldComponent { readonly field = input.required(); readonly value = input(null); readonly disabled = input(false); readonly valueChange = output(); protected readonly types = CustomFieldType; protected readonly dayOf = dayOf; /** What is being typed; follows the saved value when it changes. */ protected readonly text = linkedSignal(() => this.value() ?? ''); protected readonly number = linkedSignal(() => { const value = this.value(); return value === null || value === '' ? null : Number(value); }); protected readonly date = computed(() => { const day = this.value(); return day ? new Date(`${day.slice(0, 10)}T00:00:00`) : null; }); /** The choices; one no longer offered still shows while the ticket has it. */ protected readonly options = computed[]>(() => { const current = this.value(); const choices = this.field().options; return [ { value: '', label: 'Sin valor' }, ...choices.map((o) => ({ value: o, label: o })), ...(current && !choices.includes(current) ? [{ value: current, label: current }] : []), ]; }); protected commit(next: string | null): void { if (next !== (this.value() || null)) this.valueChange.emit(next); } }