sbo-rows-table reutilizable (también en la ventana de lotes). Direcciones y contactos se envían completos con reemplazo; los precios solo por filas cambiadas. Probado contra SBODemoCL. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
82 lines
3.8 KiB
TypeScript
82 lines
3.8 KiB
TypeScript
import { ChangeDetectionStrategy, Component, computed, input, linkedSignal, output } from '@angular/core';
|
|
import type { DetailColumn } from './grid.types';
|
|
import { RowsTable } from './rows-table';
|
|
|
|
type Row = Record<string, unknown>;
|
|
|
|
/**
|
|
* Ventana con las filas de un detalle de línea (lotes, números de serie): tabla editable con «Añadir fila»
|
|
* y «Quitar». Solo emite `saved` al aceptar; cancelar descarta los cambios.
|
|
*/
|
|
@Component({
|
|
selector: 'sbo-line-detail-dialog',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
imports: [RowsTable],
|
|
template: `
|
|
<div class="overlay">
|
|
<div class="dlg" role="dialog" aria-modal="true" [attr.aria-label]="title()" (keydown.escape)="closed.emit()">
|
|
<header>
|
|
<h2>{{ title() }}</h2>
|
|
@if (subtitle()) {
|
|
<span class="sub">{{ subtitle() }}</span>
|
|
}
|
|
</header>
|
|
<div class="body">
|
|
<sbo-rows-table [columns]="columns()" [(rows)]="rows" [readonly]="readonly()" [emptyText]="readonly() ? 'Sin filas.' : 'Sin filas. Pulse «Añadir fila».'" />
|
|
</div>
|
|
@if (hint()) {
|
|
<p class="hint" [class.bad]="bad()">{{ hint() }}</p>
|
|
}
|
|
<footer>
|
|
@if (!readonly()) {
|
|
<button class="btn" type="button" (click)="add()">+ Añadir fila</button>
|
|
}
|
|
<span class="sp"></span>
|
|
<button class="btn" type="button" (click)="closed.emit()">{{ readonly() ? 'Cerrar' : 'Cancelar' }}</button>
|
|
@if (!readonly()) {
|
|
<button class="btn main" type="button" (click)="saved.emit(clean())">Aceptar</button>
|
|
}
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
`,
|
|
styles: `
|
|
.overlay { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; padding: 16px; background: var(--overlay); }
|
|
.dlg { display: flex; flex-direction: column; width: min(720px, 100%); max-height: 80vh; background: var(--win); border: 1px solid var(--line); border-radius: var(--radius-panel); overflow: hidden; box-shadow: var(--shadow-pop); }
|
|
header { display: flex; align-items: baseline; gap: 10px; padding: 8px 12px; background: var(--panel); border-bottom: 1px solid var(--line); }
|
|
h2 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
.sub { font-size: 12px; color: var(--quiet); }
|
|
.body { overflow: auto; padding: 8px 12px; }
|
|
.hint { margin: 0; padding: 6px 12px; font-size: 12px; color: var(--quiet); border-top: 1px solid var(--line-soft); }
|
|
.hint.bad { color: var(--err); }
|
|
footer { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-top: 1px solid var(--line); background: var(--panel); }
|
|
.sp { flex: 1; }
|
|
`,
|
|
})
|
|
export class LineDetailDialog {
|
|
readonly title = input.required<string>();
|
|
readonly subtitle = input('');
|
|
readonly columns = input.required<readonly DetailColumn[]>();
|
|
readonly initial = input<readonly Row[]>([]);
|
|
readonly readonly = input(false);
|
|
/** Texto bajo la tabla, calculado por quien abre la ventana con las filas en edición. */
|
|
readonly hintFor = input<((rows: readonly Row[]) => { text: string; bad: boolean }) | undefined>(undefined);
|
|
|
|
readonly saved = output<Row[]>();
|
|
readonly closed = output<void>();
|
|
|
|
protected readonly rows = linkedSignal<Row[]>(() => this.initial().map((r) => ({ ...r })));
|
|
protected readonly hintState = computed(() => this.hintFor()?.(this.rows()) ?? { text: '', bad: false });
|
|
protected readonly hint = computed(() => this.hintState().text);
|
|
protected readonly bad = computed(() => this.hintState().bad);
|
|
|
|
protected add(): void {
|
|
this.rows.update((rs) => [...rs, {}]);
|
|
}
|
|
|
|
/** Las filas sin ningún dato no se guardan. */
|
|
protected clean(): Row[] {
|
|
return this.rows().filter((r) => this.columns().some((c) => r[c.key] !== null && r[c.key] !== undefined && r[c.key] !== ''));
|
|
}
|
|
}
|