feat(web): grilla de líneas editable y página de componentes de desarrollo
- sbo-lines-grid: columnas declarativas (texto / número con decimales / solo lectura / calculadas), celdas con lista de valores (botón "..." y F4) cuyo apply rellena la línea, añadir y quitar línea, y "Elegir artículos..." para agregar varias líneas a la vez. - Teclado: Enter confirma y baja a la misma columna (crea línea en la última fila), flechas arriba/abajo cambian de fila. Las celdas se confirman al salir de ellas; un número inválido conserva el anterior. - Documento cerrado: grilla de solo lectura, sin botones ni listas. - Columnas estándar de línea de SAP (artículo, descripción, cantidad, almacén, precio, % dto., impuesto, total) con TaxCode / SalesTaxCodes, verificado en el Service Layer real; recalcLine (cantidad x precio x (1 - dto)). - Corrige el foco al crear una línea con Enter: se pedía antes de que la fila existiera en el DOM (ahora espera al render). - /dev/componentes: página de componentes solo para desarrollo (canMatch isDevMode), sin entrada en el menú y sin escribir en SAP. - Verificado en Chrome contra SAP real: artículo elegido de la lista, 5 x 10,000 con 10 % = 45,000.00, Enter/flechas, varios artículos a la vez, quitar línea y subtotal del pie. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
26f0649da9
commit
0a7bfb850a
11 files changed
+1150
No files matched your search
@@ -0,0 +1,113 @@
|
||||
<div class="bar">
|
||||
<span class="count">{{ lines().length }} {{ lines().length === 1 ? 'línea' : 'líneas' }}</span>
|
||||
@if (!readonly()) {
|
||||
<span class="sp"></span>
|
||||
@if (multiAdd(); as m) {
|
||||
<button type="button" class="btn" (click)="multiOpen.set(true)">{{ m.label }}</button>
|
||||
}
|
||||
<button type="button" class="btn main" (click)="add()">+ {{ addLabel() }}</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="gridwrap">
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="rn">#</th>
|
||||
@for (c of columns(); track c.key) {
|
||||
<th [style.width]="c.width" [class.num]="c.type === 'number'" [class.req]="c.required">{{ c.title }}</th>
|
||||
}
|
||||
@if (!readonly()) {
|
||||
<th class="del"><span class="sr">Quitar</span></th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (line of lines(); track $index; let i = $index) {
|
||||
<tr>
|
||||
<td class="rn">{{ i + 1 }}</td>
|
||||
@for (c of columns(); track c.key; let j = $index) {
|
||||
<td [class.ro]="isReadonly(line, c)">
|
||||
@if (c.lookup && !isReadonly(line, c)) {
|
||||
<div class="pick">
|
||||
<input
|
||||
class="cell"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
[class.mono]="c.mono"
|
||||
[attr.data-r]="i"
|
||||
[attr.data-c]="j"
|
||||
[attr.aria-label]="c.title + ' de la línea ' + (i + 1)"
|
||||
[value]="cellText(line, c)"
|
||||
(change)="commit(i, c, $any($event.target))"
|
||||
(keydown)="onKey($event, i, j)"
|
||||
/>
|
||||
<button type="button" tabindex="-1" title="Lista de valores (F4)" [attr.aria-label]="'Abrir la lista de ' + c.title" (click)="openLookup(i, c)">…</button>
|
||||
</div>
|
||||
} @else {
|
||||
<input
|
||||
class="cell"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
[class.num]="c.type === 'number'"
|
||||
[class.mono]="c.mono"
|
||||
[readOnly]="isReadonly(line, c)"
|
||||
[attr.data-r]="i"
|
||||
[attr.data-c]="j"
|
||||
[attr.aria-label]="c.title + ' de la línea ' + (i + 1)"
|
||||
[attr.inputmode]="c.type === 'number' ? 'decimal' : null"
|
||||
[attr.title]="cellText(line, c) || null"
|
||||
[value]="cellText(line, c)"
|
||||
(focus)="c.type === 'number' ? numberFocus($event, line, c) : null"
|
||||
(change)="commit(i, c, $any($event.target))"
|
||||
(blur)="c.type === 'number' ? commit(i, c, $any($event.target)) : null"
|
||||
(keydown)="onKey($event, i, j)"
|
||||
/>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
@if (!readonly()) {
|
||||
<td class="del">
|
||||
<button type="button" [attr.aria-label]="'Quitar la línea ' + (i + 1)" title="Quitar línea" (click)="remove(i)">×</button>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr class="none">
|
||||
<td [attr.colspan]="columns().length + (readonly() ? 1 : 2)">
|
||||
{{ readonly() ? 'El documento no tiene líneas.' : 'No hay líneas. Pulse «Añadir línea» para comenzar.' }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if (lookup(); as cfg) {
|
||||
<sbo-value-help
|
||||
[open]="true"
|
||||
[title]="cfg.title"
|
||||
[resource]="cfg.resource"
|
||||
[columns]="cfg.columns"
|
||||
[baseFilter]="cfg.baseFilter"
|
||||
[orderby]="cfg.orderby"
|
||||
[rowKey]="cfg.valueKey"
|
||||
(selected)="lookupPicked($event)"
|
||||
(closed)="lookupClosed()"
|
||||
/>
|
||||
}
|
||||
@if (multiOpen(); as _) {
|
||||
@if (multiAdd(); as m) {
|
||||
<sbo-value-help
|
||||
[open]="true"
|
||||
[title]="m.lookup.title"
|
||||
[resource]="m.lookup.resource"
|
||||
[columns]="m.lookup.columns"
|
||||
[baseFilter]="m.lookup.baseFilter"
|
||||
[orderby]="m.lookup.orderby"
|
||||
selection="multiple"
|
||||
(selected)="multiPicked($event)"
|
||||
(closed)="multiOpen.set(false)"
|
||||
/>
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user