Files
basic-erp/apps/web/src/app/shared/form/fields.ts
T
GeorgeandClaude Sonnet 5.5 68caf401a7 refactor(web): filas de formulario con varios controles combinables
El mockup muestra que una fila es etiqueta + uno o varios controles en la
misma línea (Cliente = input + "...", Canal / Zona = dos mitades, Serie +
Número = 72 px + resto, select + número + botón) y que la cabecera tiene a
lo sumo 2 columnas. Los campos de un solo control no lo permitían.

- Controles sin etiqueta combinables dentro de sbo-field: sbo-input,
  sbo-textarea, sbo-number, sbo-date, sbo-select y el botón .sbo-btn;
  ocupan el espacio sobrante o un width fijo.
- El primer control de la fila toma el id de la etiqueta (claimId); los
  demás se nombran con ariaLabel. El error de la fila marca todos sus
  controles (aria-invalid / aria-describedby).
- Los campos con etiqueta (sbo-text-field, sbo-number-field, ...,
  sbo-lookup-field) pasan a ser atajos de un solo control con la misma API.
- CSS global: .sbo-group y .sbo-btn reemplazan a .sbo-pick/.sbo-pair.
- CLAUDE.md apunta al mockup renombrado (tools/oferta_venta.html).
- Verificado en Chrome contra SAP real, con una cabecera que reproduce las
  filas compuestas del mockup.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 19:49:16 -05:00

121 lines
3.6 KiB
TypeScript

import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
import { FieldBase } from './field-base';
import { DateInput, NumberInput, SelectInput, type SelectOption, TextInput, TextareaInput } from './controls';
import { SboField } from './sbo-field';
/*
* Campos con etiqueta de un solo control: atajo de `<sbo-field>` + el control correspondiente.
* Para filas con varios controles (input + botón, serie + número...) usar `sbo-field` directamente.
*/
@Component({
selector: 'sbo-text-field',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, TextInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-input
[(value)]="value"
[readonly]="readOnly()"
[disabled]="disabled()"
[required]="required()"
[maxlength]="maxlength()"
[placeholder]="placeholder()"
/>
</sbo-field>
`,
styles: ':host { display: block; }',
})
export class TextField extends FieldBase {
readonly value = model('');
readonly maxlength = input<number>();
readonly placeholder = input('');
}
@Component({
selector: 'sbo-textarea-field',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, TextareaInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-textarea
[(value)]="value"
[readonly]="readOnly()"
[disabled]="disabled()"
[required]="required()"
[maxlength]="maxlength()"
[placeholder]="placeholder()"
[rows]="rows()"
/>
</sbo-field>
`,
styles: ':host { display: block; }',
})
export class TextareaField extends FieldBase {
readonly value = model('');
readonly maxlength = input<number>();
readonly placeholder = input('');
readonly rows = input(3);
}
@Component({
selector: 'sbo-number-field',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, NumberInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-number
[(value)]="value"
[decimals]="decimals()"
[readonly]="readOnly()"
[disabled]="disabled()"
[required]="required()"
/>
</sbo-field>
`,
styles: ':host { display: block; }',
})
export class NumberField extends FieldBase {
readonly value = model<number | null>(null);
readonly decimals = input<number>();
}
@Component({
selector: 'sbo-date-field',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, DateInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-date [(value)]="value" [readonly]="readOnly()" [disabled]="disabled()" [required]="required()" />
</sbo-field>
`,
styles: ':host { display: block; }',
})
export class DateField extends FieldBase {
readonly value = model('');
}
@Component({
selector: 'sbo-select-field',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, SelectInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-select
[(value)]="value"
[options]="options()"
[placeholder]="placeholder()"
[readonly]="readOnly()"
[disabled]="disabled()"
[required]="required()"
/>
</sbo-field>
`,
styles: ':host { display: block; }',
})
export class SelectField extends FieldBase {
readonly options = input.required<readonly SelectOption[]>();
readonly value = model<string | number | null>(null);
readonly placeholder = input<string>();
}