feat(web): botón «Campos» que muestra el nombre del campo de SAP junto a cada etiqueta y en las cabeceras de tablas (ayuda del cursor siempre)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-20 08:59:58 -05:00
1 parent 6887f9e474
commit 6ad824c6c1
19 files changed
+168 -37

No files matched your search

@@ -8,4 +8,6 @@ export abstract class FieldBase {
readonly readOnly = input(false, { alias: 'readonly', transform: booleanAttribute });
readonly disabled = input(false, { transform: booleanAttribute });
readonly error = input<string>();
/** Nombre del campo de SAP (ayuda y «Ver campos»). */
readonly dbName = input<string>();
}
+6 -6
View File
@@ -13,7 +13,7 @@ import { SboField } from './sbo-field';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, TextInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-field [label]="label()" [dbName]="dbName()" [required]="required()" [error]="error()">
<sbo-input
[(value)]="value"
[readonly]="readOnly()"
@@ -37,7 +37,7 @@ export class TextField extends FieldBase {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, CheckboxInput],
template: `
<sbo-field [label]="label()" [error]="error()">
<sbo-field [label]="label()" [dbName]="dbName()" [error]="error()">
<sbo-checkbox [(value)]="value" [readonly]="readOnly()" [disabled]="disabled()" />
</sbo-field>
`,
@@ -52,7 +52,7 @@ export class CheckboxField extends FieldBase {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, TextareaInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-field [label]="label()" [dbName]="dbName()" [required]="required()" [error]="error()">
<sbo-textarea
[(value)]="value"
[readonly]="readOnly()"
@@ -78,7 +78,7 @@ export class TextareaField extends FieldBase {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, NumberInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-field [label]="label()" [dbName]="dbName()" [required]="required()" [error]="error()">
<sbo-number
[(value)]="value"
[decimals]="decimals()"
@@ -100,7 +100,7 @@ export class NumberField extends FieldBase {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, DateInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-field [label]="label()" [dbName]="dbName()" [required]="required()" [error]="error()">
<sbo-date [(value)]="value" [readonly]="readOnly()" [disabled]="disabled()" [required]="required()" />
</sbo-field>
`,
@@ -115,7 +115,7 @@ export class DateField extends FieldBase {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SboField, SelectInput],
template: `
<sbo-field [label]="label()" [required]="required()" [error]="error()">
<sbo-field [label]="label()" [dbName]="dbName()" [required]="required()" [error]="error()">
<sbo-select
[(value)]="value"
[options]="options()"
@@ -33,6 +33,7 @@ const NAMES = new Map<string, string>();
template: `
<sbo-field
[label]="label()"
[dbName]="dbName()"
[required]="required()"
[error]="error() || missingText()"
action
+15 -2
View File
@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, booleanAttribute, input, output } from '@angular/core';
import { ChangeDetectionStrategy, Component, booleanAttribute, inject, input, output } from '@angular/core';
import { FieldNames } from '../../core/field-names';
let nextId = 0;
@@ -19,8 +20,11 @@ let nextId = 0;
[class.req]="required()"
[class.ga]="action()"
[class.link]="linkable()"
[attr.title]="dbName() ?? null"
(click)="labelClick.emit()"
>{{ label() }}</label
>{{ label() }}@if (dbName() && names.show()) {
<span class="db">{{ dbName() }}</span>
}</label
>
<div class="ctl">
<div class="sbo-group"><ng-content /></div>
@@ -65,6 +69,12 @@ let nextId = 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
}
.db {
display: block;
margin-top: -2px;
font: 10.5px/1.2 var(--mono, monospace);
color: var(--quiet);
}
label.link {
cursor: pointer;
}
@@ -83,7 +93,10 @@ let nextId = 0;
`,
})
export class SboField {
protected readonly names = inject(FieldNames);
readonly label = input.required<string>();
/** Nombre del campo de SAP; sale como ayuda del cursor y, con «Ver campos», debajo de la etiqueta. */
readonly dbName = input<string>();
readonly required = input(false, { transform: booleanAttribute });
readonly action = input(false, { transform: booleanAttribute });
/** La etiqueta es un enlace (ej. abrir el socio de negocio). */
@@ -151,3 +151,10 @@ tr.none td {
position: absolute;
left: -9999px;
}
th .db {
display: block;
font: 10.5px/1.2 var(--mono, monospace);
font-weight: 400;
color: var(--quiet);
}
+3 -1
View File
@@ -15,7 +15,9 @@
<tr>
<th class="rn">#</th>
@for (c of columns(); track c.key; let j = $index) {
<th sboColResize [style.width]="c.width" [style.text-align]="alignOf(c)" [class.req]="c.required">{{ c.title }}</th>
<th sboColResize [style.width]="c.width" [style.text-align]="alignOf(c)" [class.req]="c.required" [attr.title]="c.key">{{ c.title }}@if (names.show()) {
<span class="db">{{ c.key }}</span>
}</th>
@if (hasDetails() && j === detailAt()) {
<th class="det">Lotes / Series</th>
}
@@ -12,6 +12,7 @@ import {
output,
signal,
} from '@angular/core';
import { FieldNames } from '../../core/field-names';
import { formatNumber, parseDecimal } from '../../core/format';
import { SlLookupApi } from '../../core/sl/sl-lookup';
import type { ListRow } from '../list/list.types';
@@ -57,6 +58,7 @@ export class LinesGrid {
private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);
private readonly injector = inject(Injector);
protected readonly names = inject(FieldNames);
constructor() {
// las celdas de código con texto propio (la cuenta con su formato) piden los textos de las líneas que se cargan
+6 -1
View File
@@ -1,6 +1,7 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, computed, effect, inject, input, model, signal } from '@angular/core';
import { API_URL } from '../../core/api';
import { FieldNames } from '../../core/field-names';
import { formatNumber, parseDecimal } from '../../core/format';
import { SlLookupApi } from '../../core/sl/sl-lookup';
import type { ListRow } from '../list/list.types';
@@ -31,7 +32,9 @@ const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10)
<tr>
<th class="rn">#</th>
@for (c of columns(); track c.key) {
<th [style.width]="c.width" [class.req]="c.required">{{ c.title }}</th>
<th [style.width]="c.width" [class.req]="c.required" [attr.title]="c.key">{{ c.title }}@if (fieldNames.show()) {
<span class="db">{{ c.key }}</span>
}</th>
}
@if (canRemove()) {
<th class="del"></th>
@@ -145,6 +148,7 @@ const dateOnly = (v: unknown): string => (typeof v === 'string' ? v.slice(0, 10)
.menu button:hover { background: var(--btn-h); }
table { width: 100%; min-width: max-content; border-collapse: collapse; }
th { padding: 4px 6px; background: var(--grid-head); border: 1px solid var(--line); font-size: 12.5px; font-weight: 600; text-align: left; white-space: nowrap; }
th .db { display: block; font: 10.5px/1.2 var(--mono, monospace); font-weight: 400; color: var(--quiet); }
th.req::after { content: ' *'; color: var(--err); }
td { padding: 0 2px; height: 27px; border: 1px solid var(--line-soft); white-space: nowrap; }
td.ro { background: var(--field-ro); }
@@ -181,6 +185,7 @@ export class RowsTable {
return rows.filter((r) => keys.some((k) => Number(r[k]) !== 0 && Number.isFinite(Number(r[k]))));
});
protected readonly fieldNames = inject(FieldNames);
private readonly lookupApi = inject(SlLookupApi);
private readonly http = inject(HttpClient);
/** Nombres de los catálogos de las columnas `nameOf`, por recurso. */
@@ -75,3 +75,10 @@ tr.none td {
width: 30px;
text-align: center;
}
th .db {
display: block;
font: 10.5px/1.2 var(--mono, monospace);
font-weight: 400;
color: var(--quiet);
}
+4 -1
View File
@@ -19,10 +19,13 @@
[style.text-align]="align(c)"
[class.pick]="searchable(c)"
[class.active]="c.key === activeColumnKey()"
[attr.title]="searchable(c) ? 'Buscar en esta columna' : null"
[attr.title]="(searchable(c) ? 'Buscar en esta columna · ' : '') + c.key"
(click)="columnPicked.emit(c)"
>
{{ c.title }}
@if (names.show()) {
<span class="db">{{ c.key }}</span>
}
</th>
}
</tr>
+4 -1
View File
@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, inject, input, output } from '@angular/core';
import { FieldNames } from '../../core/field-names';
import { ColResize } from '../table/col-resize';
import { Badge } from '../window/badge';
import { type ListColumn, type ListRow, alignOf, cellText, isSearchable, type Tone } from './list.types';
@@ -15,6 +16,8 @@ import { type ListColumn, type ListRow, alignOf, cellText, isSearchable, type To
styleUrl: './data-table.css',
})
export class DataTable {
protected readonly names = inject(FieldNames);
readonly columns = input.required<readonly ListColumn[]>();
readonly rows = input.required<readonly ListRow[]>();
/** Campo único de cada fila. */