- sbo-field: fila etiqueta + control, con obligatorio (*), flecha dorada, etiqueta enlazable y mensaje de error. - sbo-text-field, sbo-textarea-field, sbo-number-field, sbo-date-field, sbo-select-field y sbo-lookup-field, con enlace bidireccional por señales ([(value)]) y entradas comunes (label, required, readonly, disabled, error). - sbo-number-field: formateado fuera de foco (1,234.50), acepta 12,5 y 1,234.50 al escribir, redondea a los decimales indicados y conserva el valor si el texto no es un número (parseDecimal). - sbo-select-field devuelve el valor con su tipo original (número o texto). - sbo-lookup-field: botón "…" y F4 abren sbo-value-help (VhLookup); elegir rellena el campo, emite la fila y devuelve el foco; escribir el código a mano sigue siendo válido. - Estilos globales de formulario (forms.css) tomados del mockup, con ancho de etiqueta configurable (--label-w). - Verificado en Chrome contra SAP real, en claro y oscuro: cliente elegido de la lista rellena código, nombre y RUC; edición de números; selects. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
106 lines
5.4 KiB
Markdown
106 lines
5.4 KiB
Markdown
# web
|
|
|
|
Frontend Angular (standalone, zoneless, señales) del ERP base. Interfaz densa y ligera estilo SAP Business One.
|
|
|
|
```bash
|
|
npm run dev:web # desde la raíz; http://localhost:4200 (proxy /api -> localhost:3000)
|
|
npm run build -w apps/web
|
|
npm test -w apps/web
|
|
```
|
|
|
|
## Criterios de rendimiento
|
|
|
|
Es un ERP, no un sitio publicitario: rápido y ligero.
|
|
|
|
- Sin librerías de UI (nada de Material/PrimeNG): componentes propios, densos (13 px).
|
|
- Fuentes del sistema, sin webfonts; sin animaciones.
|
|
- Zoneless + `OnPush`; rutas con `loadComponent` para cargar solo lo que se usa.
|
|
- Presupuesto del bundle inicial: aviso a 260 kB, error a 400 kB (`angular.json`). Hoy: ~200 kB (56 kB comprimido).
|
|
- Listas largas con paginación del servidor (`$top/$skip`) o virtualización.
|
|
|
|
## Temas
|
|
|
|
El color sale solo de variables CSS (`src/styles/themes/*.css`). Tema por defecto: **claro**, con la paleta del mockup de Oferta de ventas.
|
|
|
|
Agregar un tema:
|
|
1. `src/styles/themes/<id>.css` con un bloque `:root[data-theme='<id>']` (copiar los tokens de `light.css`).
|
|
2. `@import` en `src/styles.css`.
|
|
3. Una entrada en `src/app/core/theme/themes.ts`.
|
|
|
|
Tokens: `--app --shell --shell-ink --shell-quiet --win --panel --line --line-soft --ink --quiet --field --field-ro --field-b --focus --gold --grid-head --row-sel --row-alt --ok --ok-bg --warn --warn-bg --err --err-bg --btn --btn-b --btn-h --overlay --shadow-pop`.
|
|
|
|
## Tipos compartidos
|
|
|
|
`@basic-erp/sap-types` (`libs/sap-types`): usuario, respuestas de la API y el evaluador de permisos de menú, los mismos que usa el backend.
|
|
|
|
## Autenticación y rutas
|
|
|
|
- `core/auth`: `SessionStore` (sesión en localStorage, compartida entre pestañas, con timeout deslizante según el servidor), `AuthService` (login/logout/expire), interceptor (Bearer en `/api`, renueva actividad y atiende los 401) y guards (`authGuard`, `guestGuard`, `permissionGuard`).
|
|
- `core/menu`: `MenuService` carga `GET /api/user/menu` una vez por sesión; el menú visible sale de `MENU` (`@basic-erp/sap-types`) filtrado por permisos.
|
|
- `app.routes.ts` genera una ruta por pantalla del menú; las que aún no existen muestran "no disponible" (`ComingSoonPage`). Para construir una pantalla, se reemplaza su `loadComponent`.
|
|
- Un 401 en cualquier llamada cierra la sesión local y lleva al login recordando la ruta (`?returnUrl=`, solo rutas internas).
|
|
|
|
## Componentes compartidos (`src/app/shared`)
|
|
|
|
### `sbo-value-help` — lista de valores ("elegir de la lista")
|
|
|
|
Búsqueda por columna sobre un recurso del proxy `/api/sl`, selección única o múltiple y paginación del servidor.
|
|
|
|
```html
|
|
<sbo-value-help
|
|
[open]="open()"
|
|
title="Socios de negocio"
|
|
resource="BusinessPartners"
|
|
[columns]="columns"
|
|
baseFilter="CardType eq 'cCustomer'"
|
|
orderby="CardCode"
|
|
selection="single"
|
|
(selected)="onPick($event)"
|
|
(closed)="open.set(false)" />
|
|
```
|
|
|
|
```ts
|
|
columns: VhColumn[] = [
|
|
{ key: 'CardCode', title: 'Código', width: '130px', mono: true },
|
|
{ key: 'CardName', title: 'Nombre' },
|
|
{ key: 'CurrentAccountBalance', title: 'Saldo', type: 'currency' },
|
|
];
|
|
```
|
|
|
|
- Agregar una columna = una entrada en `columns`; el `$select` se deriva de ahí.
|
|
- Columna activa de búsqueda: clic en el encabezado (solo columnas de texto, o `searchable: true`). Se aplica al pulsar **Buscar**/Enter con `contains()`, combinada con `baseFilter` entre paréntesis y con las comillas escapadas.
|
|
- `selection="single"`: un clic en la fila confirma y cierra. `selection="multiple"`: casillas, "seleccionar página" y **Confirmar (n)**; lo marcado se conserva al cambiar de página (`[initialSelection]` precarga filas).
|
|
- `type`: `text` (defecto), `number`, `currency` (2 decimales), `date` (dd/MM/yyyy); `format: (row) => string` para algo a medida.
|
|
- Teclado: Enter en la búsqueda, Enter sobre una fila, Esc para cerrar.
|
|
- Cada apertura reinicia búsqueda, página y selección. Las peticiones obsoletas se cancelan solas (`httpResource`).
|
|
|
|
### Campos de formulario (`shared/form`)
|
|
|
|
Cada campo trae su etiqueta (fila SBO: etiqueta a la izquierda, control a la derecha) y se enlaza con señales: `[(value)]`.
|
|
|
|
| Componente | Valor | Notas |
|
|
|---|---|---|
|
|
| `sbo-text-field` | `string` | `maxlength`, `placeholder` |
|
|
| `sbo-textarea-field` | `string` | `rows` |
|
|
| `sbo-number-field` | `number \| null` | `decimals`; muestra `1,234.50` fuera de foco y acepta `12,5` o `1,234.50` al escribir |
|
|
| `sbo-date-field` | `string` ISO `yyyy-MM-dd` | selector nativo |
|
|
| `sbo-select-field` | `string \| number \| null` | `[options]`, `placeholder`; devuelve el tipo original |
|
|
| `sbo-lookup-field` | `string` | flecha dorada + botón "…" / F4 que abre `sbo-value-help` según `[lookup]` (`VhLookup`); emite `(picked)` con la fila; `linkable` + `(linkClick)` |
|
|
|
|
Comunes: `label`, `required`, `readonly`, `disabled`, `error` (borde rojo + mensaje). `sbo-field` es la fila vacía para proyectar cualquier otro control.
|
|
|
|
```html
|
|
<div class="sbo-hdr">
|
|
<div class="sbo-col">
|
|
<sbo-lookup-field label="Cliente" required [(value)]="cardCode" [lookup]="bp" (picked)="onBp($event)" />
|
|
<sbo-text-field label="Nombre" readonly [value]="cardName()" />
|
|
</div>
|
|
<div class="sbo-col">
|
|
<sbo-date-field label="Fecha" [(value)]="docDate" />
|
|
<sbo-number-field label="Importe" [decimals]="2" [(value)]="total" />
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
Estilos globales en `styles/forms.css` (`.sbo-in`, `.sbo-sel`, `.sbo-ta`, `.sbo-pick`, `.sbo-hdr`, `.sbo-col`, `.sbo-pair`). El ancho de etiqueta se ajusta con `--label-w` (165 px por defecto).
|