feat: refactor account and proforma pages to use app-fullscreen layout and improve UI consistency
This commit is contained in:
parent
64b90888c3
commit
4411b8f07e
274
docs/frontend/editor-layout-audit.md
Normal file
274
docs/frontend/editor-layout-audit.md
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
# Auditoría De Layouts De Editores
|
||||||
|
|
||||||
|
Fecha de auditoría: 18 de agosto de 2026.
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Inventariar los editores reales del frontend del ERP y clasificarlos respecto a la norma visual vigente de edición focalizada.
|
||||||
|
|
||||||
|
Esta auditoría:
|
||||||
|
|
||||||
|
- inventaría rutas, páginas y dialogs de edición activos;
|
||||||
|
- clasifica cada caso por tipo;
|
||||||
|
- evalúa el layout actual frente a la norma;
|
||||||
|
- detecta desviaciones y excepciones justificadas;
|
||||||
|
- propone una secuencia pequeña de migración.
|
||||||
|
|
||||||
|
Esta auditoría no migra masivamente las pantallas.
|
||||||
|
|
||||||
|
## Norma visual de referencia
|
||||||
|
|
||||||
|
SSOT vigente:
|
||||||
|
|
||||||
|
- `packages/rdx-ui/docs/page-layouts.md`
|
||||||
|
- `packages/rdx-ui/docs/contributing-ui.md`
|
||||||
|
|
||||||
|
Resumen operativo:
|
||||||
|
|
||||||
|
- los editores de página completa deben usar por defecto shell `app-fullscreen`;
|
||||||
|
- la navegación mínima sale por `PageFormHeader` o un header de dominio equivalente;
|
||||||
|
- el contenido del editor se centra mediante `RdxEditorPageLayout`;
|
||||||
|
- `complexity="simple"` aplica a formularios focalizados centrados;
|
||||||
|
- `complexity="complex"` aplica a documentos o editores con `main + aside`, bloques especializados y footer sticky;
|
||||||
|
- los dialogs e inline editors no deben migrarse automáticamente a fullscreen.
|
||||||
|
|
||||||
|
## Método de investigación
|
||||||
|
|
||||||
|
Búsquedas y fuentes revisadas:
|
||||||
|
|
||||||
|
- rutas y layouts en `apps/web/src/routes/app-routes.tsx` y `modules/*/src/web/*routes.tsx`
|
||||||
|
- contratos de layout en `modules/core/src/web/lib/modules/module-client.interface.ts`
|
||||||
|
- primitivas Rdx en:
|
||||||
|
- `packages/rdx-ui/src/components/editor-page/rdx-editor-page-layout.tsx`
|
||||||
|
- `packages/rdx-ui/src/components/editor-page/rdx-editor-sticky-footer.tsx`
|
||||||
|
- `modules/core/src/web/components/form/form-actions/page-form-header.tsx`
|
||||||
|
- `apps/web/src/layout/app-fullscreen-layout.tsx`
|
||||||
|
- `apps/web/src/layout/app-sidebar-layout.tsx`
|
||||||
|
- módulos revisados:
|
||||||
|
- `identity`
|
||||||
|
- `customers`
|
||||||
|
- `customer-invoices`
|
||||||
|
- `companies`
|
||||||
|
- `catalogs`
|
||||||
|
- `document-series`
|
||||||
|
- `supplier`
|
||||||
|
- búsquedas textuales sobre:
|
||||||
|
- `layout: "app-fullscreen"`
|
||||||
|
- `layout: "app-sidebar"`
|
||||||
|
- `RdxEditorPageLayout`
|
||||||
|
- `PageFormHeader`
|
||||||
|
- `RdxEditorStickyFooter`
|
||||||
|
- `create`
|
||||||
|
- `update`
|
||||||
|
- `edit`
|
||||||
|
- `Dialog`
|
||||||
|
|
||||||
|
## Inventario
|
||||||
|
|
||||||
|
| Module | Feature | Route | Type | Current layout | Rdx editor layout | Compliance | Risk | Priority | Recommended action | Evidence |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| `identity` | Account self-service | `/account` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como referencia de editor simple focalizado. | `modules/identity/src/web/account-routes.tsx`, `modules/identity/src/web/account/ui/pages/account-page.tsx` |
|
||||||
|
| `identity` | Change password dialog | Sin ruta propia. Dialog dentro de `/account` | `DIALOG` | Host `app-fullscreen` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como dialog de edición contenida. | `modules/identity/src/web/account/ui/pages/account-page.tsx` |
|
||||||
|
| `customers` | Create customer | `/customers/create` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `LOW` | `-` | Ninguna. Referencia vigente de editor simple. | `modules/customers/src/web/customer-routes.tsx`, `modules/customers/src/web/create/ui/pages/customer-create-page.tsx` |
|
||||||
|
| `customers` | Update customer | `/customers/:id/edit` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `LOW` | `-` | Ninguna. Referencia vigente de editor simple. | `modules/customers/src/web/customer-routes.tsx`, `modules/customers/src/web/update/ui/pages/customer-update-page.tsx` |
|
||||||
|
| `customers` | Quick create customer | Sin ruta propia. Dialog reutilizado desde proformas | `DIALOG` | Host `app-fullscreen` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantenerlo como creación rápida embebida. | `modules/customers/src/common/features/quick-create-customer/ui/dialogs/quick-create-customer-dialog.tsx` |
|
||||||
|
| `companies` | Company details settings | `/settings/company/details` | `PAGE_SIMPLE` | `app-sidebar` | No usa `RdxEditorPageLayout` | `EXCEPTION_JUSTIFIED` | `MEDIUM` | `P3` | Mantener en settings por ahora. Revaluar solo si se define una variante focalizada específica para formularios de settings. | `modules/companies/src/web/company-routes.tsx`, `modules/companies/src/web/settings/company-details/ui/pages/company-details-settings-page.tsx` |
|
||||||
|
| `customer-invoices` | Verifactu settings | `/settings/billing/verifactu` | `PAGE_SIMPLE` | `app-sidebar` | No usa `RdxEditorPageLayout` | `EXCEPTION_JUSTIFIED` | `MEDIUM` | `P3` | Mantener en settings por ahora. Revaluar junto con una política específica de formularios largos dentro de settings. | `modules/customer-invoices/src/web/customer-invoice-routes.tsx`, `modules/customer-invoices/src/web/settings/verifactu/ui/pages/company-verifactu-settings-page.tsx` |
|
||||||
|
| `customer-invoices` | Create proforma | `/proformas/create` | `PAGE_SIMPLE` | `app-fullscreen` | `simple` | `COMPLIANT` | `MEDIUM` | `-` | Ninguna. Mantener como bootstrap inicial de proforma hacia el editor documental completo. | `modules/customer-invoices/src/web/customer-invoice-routes.tsx`, `modules/customer-invoices/src/web/proformas/create/ui/pages/proforma-create-page.tsx`, `modules/customer-invoices/src/web/proformas/create/ui/blocks/proforma-create-form.tsx` |
|
||||||
|
| `customer-invoices` | Update proforma | `/proformas/:id/edit` | `PAGE_COMPLEX` | `app-fullscreen` | `complex` | `COMPLIANT` | `HIGH` | `-` | Ninguna. Mantener como referencia vigente de editor complejo. | `modules/customer-invoices/src/web/customer-invoice-routes.tsx`, `modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx`, `modules/customer-invoices/src/web/proformas/update/ui/editors/proforma-update-editor-form.tsx` |
|
||||||
|
| `customer-invoices` | Change proforma status | Sin ruta propia. Dialog usado desde listado y detalle | `DIALOG` | Host mixto (`app-sidebar` o `app-fullscreen`) | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Es una edición contenida por dialog, no candidata a fullscreen. | `modules/customer-invoices/src/web/proformas/change-status/ui/change-status-dialog.tsx`, `modules/customer-invoices/src/web/proformas/list/ui/pages/list-proformas-page.tsx`, `modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx` |
|
||||||
|
| `catalogs` | Payment methods create/update | Host: `/settings/general/payment-methods` | `DIALOG` | Host `app-sidebar` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como edición contenida en dialog dentro de settings. | `modules/catalogs/src/web/catalogs-routes.tsx`, `modules/catalogs/src/web/settings/payment-methods/ui/pages/payment-methods-settings-page.tsx`, `modules/catalogs/src/web/settings/payment-methods/ui/components/payment-method-form-dialog.tsx` |
|
||||||
|
| `catalogs` | Payment terms create/update | Host: `/settings/general/payment-terms` | `DIALOG` | Host `app-sidebar` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como dialog aunque su contenido interno sea más rico. | `modules/catalogs/src/web/catalogs-routes.tsx`, `modules/catalogs/src/web/settings/payment-terms/ui/pages/payment-terms-settings-page.tsx`, `modules/catalogs/src/web/settings/payment-terms/ui/components/payment-term-form-dialog.tsx` |
|
||||||
|
| `catalogs` | Tax regimes create/update | Host: `/settings/general/tax-regimes` | `DIALOG` | Host `app-sidebar` | No aplica | `COMPLIANT` | `LOW` | `-` | Ninguna. Mantener como dialog de edición contenida. | `modules/catalogs/src/web/catalogs-routes.tsx`, `modules/catalogs/src/web/settings/tax-regimes/ui/pages/tax-regimes-settings-page.tsx`, `modules/catalogs/src/web/settings/tax-regimes/ui/components/tax-regime-form-dialog.tsx` |
|
||||||
|
| `customer-invoices` | Issued invoices create/edit references | Referencias a `/issued-invoices/create` y `/customer-invoices/:id/edit`, sin ruta activa | `UNCLEAR` | Sin ruta activa | No aplica | `UNKNOWN` | `MEDIUM` | `P2` | Aclarar si esas rutas deben existir o limpiar referencias muertas antes de cualquier trabajo visual. | `modules/customer-invoices/src/web/issued-invoices/list/ui/pages/list-issued-invoices-page.tsx`, `modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/issued-invoices-grid.tsx`, `modules/customer-invoices/src/web/customer-invoice-routes.tsx` |
|
||||||
|
| `document-series` | Frontend editor activo | No detectado | `LEGACY_OR_UNUSED` | Sin route frontend | No aplica | `UNKNOWN` | `LOW` | `P3` | Confirmar si el módulo tendrá UI activa antes de cualquier migración de layout. | Sin `src/web/*routes.tsx` ni páginas frontend activas en `modules/document-series` |
|
||||||
|
| `supplier` | Frontend editor activo | No detectado | `LEGACY_OR_UNUSED` | Sin route frontend | No aplica | `UNKNOWN` | `LOW` | `P3` | Resolver primero si el módulo se reactiva o se limpia; no priorizar migración visual. | Sin `src/web/*routes.tsx`; solo `modules/supplier/src/web/shared/*` |
|
||||||
|
|
||||||
|
## Editores conformes
|
||||||
|
|
||||||
|
Conformes respecto a la norma vigente:
|
||||||
|
|
||||||
|
- `customers/create`
|
||||||
|
- `customers/update`
|
||||||
|
- `/account`
|
||||||
|
- `proformas/:id/edit`
|
||||||
|
|
||||||
|
Motivos comunes observados:
|
||||||
|
|
||||||
|
- ruta bajo `app-fullscreen`;
|
||||||
|
- navegación mínima por `PageFormHeader` o header de dominio equivalente;
|
||||||
|
- uso explícito de `RdxEditorPageLayout`;
|
||||||
|
- geometría coherente con `simple` o `complex` según caso;
|
||||||
|
- acciones de salida/guardado visibles y no delegadas al sidebar.
|
||||||
|
|
||||||
|
## Desviaciones
|
||||||
|
|
||||||
|
### 1. `proformas/create`
|
||||||
|
|
||||||
|
HECHO OBSERVADO
|
||||||
|
|
||||||
|
- la ruta usa `app-fullscreen`;
|
||||||
|
- la página usa header de edición y `RdxEditorPageLayout complexity="simple"`;
|
||||||
|
- el flujo de alta actual solo captura cabecera inicial y deriva después al editor documental completo;
|
||||||
|
- no existe todavía editor de líneas, `aside`, totales sticky ni footer persistente equivalentes a `proformas/:id/edit`.
|
||||||
|
|
||||||
|
IMPACTO
|
||||||
|
|
||||||
|
- el create queda alineado con el patrón focalizado simple sin fingir que ya es el editor documental completo;
|
||||||
|
- la diferencia visual restante frente a `update` responde a una diferencia funcional real y no a drift accidental de layout.
|
||||||
|
|
||||||
|
DECISIÓN
|
||||||
|
|
||||||
|
- `PAGE_SIMPLE`;
|
||||||
|
- `COMPLIANT`.
|
||||||
|
|
||||||
|
### 2. Referencias a create/edit de issued invoices sin ruta activa
|
||||||
|
|
||||||
|
HECHO OBSERVADO
|
||||||
|
|
||||||
|
- hay referencias a `/issued-invoices/create` y a `/customer-invoices/:id/edit`;
|
||||||
|
- `customer-invoice-routes.tsx` no declara esas rutas activas.
|
||||||
|
|
||||||
|
IMPACTO
|
||||||
|
|
||||||
|
- el inventario de editores queda ambiguo en esa área;
|
||||||
|
- puede existir deuda de navegación muerta o una funcionalidad incompleta;
|
||||||
|
- no conviene planificar migración visual ahí hasta aclarar si hay editor real.
|
||||||
|
|
||||||
|
RECOMENDACIÓN
|
||||||
|
|
||||||
|
- confirmar si esas rutas deben implementarse;
|
||||||
|
- si no, limpiar referencias muertas antes de meterlas en cualquier backlog de layouts.
|
||||||
|
|
||||||
|
## Excepciones justificadas
|
||||||
|
|
||||||
|
### Company details settings
|
||||||
|
|
||||||
|
HECHO OBSERVADO
|
||||||
|
|
||||||
|
- es un formulario de página completa;
|
||||||
|
- vive bajo `/settings/company/details`;
|
||||||
|
- depende del shell de settings con `app-sidebar`.
|
||||||
|
|
||||||
|
INFERENCIA
|
||||||
|
|
||||||
|
- la navegación lateral de settings forma parte funcional de la tarea y hoy actúa como mecanismo primario de cambio de subsección.
|
||||||
|
|
||||||
|
DECISIÓN DE AUDITORÍA
|
||||||
|
|
||||||
|
- `EXCEPTION_JUSTIFIED` por ahora;
|
||||||
|
- no priorizar fullscreen salvo que el sistema de settings evolucione con una variante focalizada explícita.
|
||||||
|
|
||||||
|
### Verifactu settings
|
||||||
|
|
||||||
|
HECHO OBSERVADO
|
||||||
|
|
||||||
|
- es un formulario de configuración largo;
|
||||||
|
- vive bajo `/settings/billing/verifactu`;
|
||||||
|
- comparte patrón de settings con shell lateral.
|
||||||
|
|
||||||
|
INFERENCIA
|
||||||
|
|
||||||
|
- igual que en `company/details`, la tarea sigue integrada en navegación de settings y no en un editor aislado.
|
||||||
|
|
||||||
|
DECISIÓN DE AUDITORÍA
|
||||||
|
|
||||||
|
- `EXCEPTION_JUSTIFIED` por ahora;
|
||||||
|
- revisar sólo si se aborda una política transversal de formularios largos dentro de settings.
|
||||||
|
|
||||||
|
## Dialogs e inline editors
|
||||||
|
|
||||||
|
Dialogs correctamente clasificados y no candidatos automáticos a fullscreen:
|
||||||
|
|
||||||
|
- `QuickCreateCustomerDialog`
|
||||||
|
- `PaymentMethodFormDialog`
|
||||||
|
- `PaymentTermFormDialog`
|
||||||
|
- `TaxRegimeFormDialog`
|
||||||
|
- dialog de cambio de contraseña de `/account`
|
||||||
|
- `ChangeProformaStatusDialog`
|
||||||
|
|
||||||
|
No se detectaron rutas independientes que deban reclasificarse desde `DIALOG` o `INLINE` a `PAGE_*`.
|
||||||
|
|
||||||
|
No se detectaron inline editors de entidad de primer nivel con route propia. Sí existen bloques especializados internos, como el editor de líneas de proformas, pero forman parte de un `PAGE_COMPLEX` y no deben inventariarse como pantalla independiente.
|
||||||
|
|
||||||
|
## Incoherencias create/update
|
||||||
|
|
||||||
|
Agregados revisados:
|
||||||
|
|
||||||
|
- `customers`
|
||||||
|
- `create` y `update` comparten patrón y están alineados.
|
||||||
|
- `proformas`
|
||||||
|
- `create` usa `RdxEditorPageLayout complexity="simple"` porque hoy es un bootstrap inicial;
|
||||||
|
- `update` usa `RdxEditorPageLayout complexity="complex"` porque ya es el editor documental completo con `main`, bloques especializados y footer sticky.
|
||||||
|
|
||||||
|
Conclusión:
|
||||||
|
|
||||||
|
- la diferencia create/update restante es funcional legítima: `create` todavía no edita el documento completo y `update` sí.
|
||||||
|
|
||||||
|
## Riesgos de migración
|
||||||
|
|
||||||
|
Riesgos bajos:
|
||||||
|
|
||||||
|
- `customers/create`
|
||||||
|
- `customers/update`
|
||||||
|
- `/account`
|
||||||
|
|
||||||
|
Riesgos medios:
|
||||||
|
|
||||||
|
- `proformas/create` por convivencia con quick-create/select customer dialogs y porque el flujo actual parece de bootstrap inicial, no de edición documental completa;
|
||||||
|
- `company/details` y `verifactu` si se intentara moverlos fuera del shell settings.
|
||||||
|
|
||||||
|
Riesgos altos:
|
||||||
|
|
||||||
|
- `proformas/:id/edit`, aunque hoy ya cumple. Cualquier cambio allí impacta:
|
||||||
|
- scroll complejo
|
||||||
|
- footer sticky
|
||||||
|
- totales embebidos
|
||||||
|
- `aside`
|
||||||
|
- dialogs anidados
|
||||||
|
|
||||||
|
## Plan incremental
|
||||||
|
|
||||||
|
### Fase A
|
||||||
|
|
||||||
|
- completada: migración de `proformas/create`
|
||||||
|
|
||||||
|
Motivo:
|
||||||
|
|
||||||
|
- era el único `PAGE_*` activo no plenamente alineado con el patrón Rdx;
|
||||||
|
- ya usaba `app-fullscreen`, así que el salto era incremental;
|
||||||
|
- permite distinguir mejor entre bootstrap inicial y editor documental completo del agregado Proforma.
|
||||||
|
|
||||||
|
### Fase B
|
||||||
|
|
||||||
|
- revisar si `company/details` y `verifactu` deben seguir como excepción de settings o si merece una variante focalizada específica para formularios largos dentro de settings
|
||||||
|
|
||||||
|
Motivo:
|
||||||
|
|
||||||
|
- ahora mismo la excepción parece razonable;
|
||||||
|
- no conviene forzar fullscreen sin una decisión explícita sobre el sistema de settings.
|
||||||
|
|
||||||
|
### Fase C
|
||||||
|
|
||||||
|
- aclarar `issued-invoices` create/edit
|
||||||
|
|
||||||
|
Motivo:
|
||||||
|
|
||||||
|
- primero hay que determinar si existe editor real o solo referencias muertas.
|
||||||
|
|
||||||
|
### Fase D
|
||||||
|
|
||||||
|
- hacer una auditoría UX más fina de dialogs ricos, empezando por `payment-terms`
|
||||||
|
|
||||||
|
Motivo:
|
||||||
|
|
||||||
|
- no es un incumplimiento de fullscreen;
|
||||||
|
- sí es el dialog más complejo de los auditados y puede requerir reglas específicas de consistencia interna.
|
||||||
|
|
||||||
|
## No cambios
|
||||||
|
|
||||||
|
Durante esta auditoría:
|
||||||
|
|
||||||
|
- no se modificaron rutas ni layouts runtime de `customers`, `companies`, `customer-invoices`, `catalogs`, `identity`, `document-series` o `supplier`;
|
||||||
|
- no se tocaron hooks, controllers, formularios, React Query ni backend de los editores auditados;
|
||||||
|
- el único cambio de código del turno es este documento operativo de auditoría.
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import type { CustomerSelectionOption } from "@erp/customers";
|
import type { CustomerSelectionOption } from "@erp/customers";
|
||||||
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
||||||
|
|
||||||
import { ProformaCreateAutoAppliedInfo } from "./proforma-create-auto-applied-info";
|
import { ProformaCreateAutoAppliedInfo } from "./proforma-create-auto-applied-info";
|
||||||
import { ProformaCreateEmptyLinesCard } from "./proforma-create-empty-lines-card";
|
import { ProformaCreateEmptyLinesCard } from "./proforma-create-empty-lines-card";
|
||||||
@ -20,7 +19,6 @@ interface ProformaCreateFormProps {
|
|||||||
proformaSeriesOptions: { value: string; label: string }[];
|
proformaSeriesOptions: { value: string; label: string }[];
|
||||||
targetInvoiceSeriesOptions: { value: string; label: string }[];
|
targetInvoiceSeriesOptions: { value: string; label: string }[];
|
||||||
paymentTermOptions: { value: string; label: string }[];
|
paymentTermOptions: { value: string; label: string }[];
|
||||||
className?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProformaCreateForm = ({
|
export const ProformaCreateForm = ({
|
||||||
@ -37,10 +35,8 @@ export const ProformaCreateForm = ({
|
|||||||
proformaSeriesOptions,
|
proformaSeriesOptions,
|
||||||
targetInvoiceSeriesOptions,
|
targetInvoiceSeriesOptions,
|
||||||
paymentTermOptions,
|
paymentTermOptions,
|
||||||
className,
|
|
||||||
}: ProformaCreateFormProps) => {
|
}: ProformaCreateFormProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn("p-6", className)}>
|
|
||||||
<form id={formId} noValidate onKeyDown={preventEnterKeySubmitForm} onSubmit={onSubmit}>
|
<form id={formId} noValidate onKeyDown={preventEnterKeySubmitForm} onSubmit={onSubmit}>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<ProformaCreateInitialInfoCard
|
<ProformaCreateInitialInfoCard
|
||||||
@ -62,6 +58,5 @@ export const ProformaCreateForm = ({
|
|||||||
<ProformaCreateEmptyLinesCard />
|
<ProformaCreateEmptyLinesCard />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { ErrorAlert } from "@erp/core/components";
|
import { ErrorAlert } from "@erp/core/components";
|
||||||
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||||
import { QuickCreateCustomerDialog, SelectCustomerDialog } from "@erp/customers";
|
import { QuickCreateCustomerDialog, SelectCustomerDialog } from "@erp/customers";
|
||||||
|
import { RdxEditorPageLayout } from "@repo/rdx-ui/components";
|
||||||
import { FormProvider } from "react-hook-form";
|
import { FormProvider } from "react-hook-form";
|
||||||
|
|
||||||
import { ProformaInfoAlert } from "../../../update/ui/components";
|
import { ProformaInfoAlert } from "../../../update/ui/components";
|
||||||
@ -29,6 +30,10 @@ export const ProformaCreatePage = () => {
|
|||||||
</ProformaInfoAlert>
|
</ProformaInfoAlert>
|
||||||
</ProformaCreateHeader>
|
</ProformaCreateHeader>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||||
|
<RdxEditorPageLayout complexity="simple">
|
||||||
|
<div className="rounded-2xl border border-border/60 bg-background px-4 py-4 shadow-xs md:px-6 md:py-6">
|
||||||
|
<div className="flex min-w-0 flex-col gap-6">
|
||||||
{createCtrl.isCreateError && (
|
{createCtrl.isCreateError && (
|
||||||
<ErrorAlert
|
<ErrorAlert
|
||||||
message={
|
message={
|
||||||
@ -51,7 +56,6 @@ export const ProformaCreatePage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
|
||||||
<ProformaCreateForm
|
<ProformaCreateForm
|
||||||
formId={createCtrl.formId}
|
formId={createCtrl.formId}
|
||||||
isPaymentTermsLoading={createCtrl.isPaymentTermsLoading}
|
isPaymentTermsLoading={createCtrl.isPaymentTermsLoading}
|
||||||
@ -68,6 +72,9 @@ export const ProformaCreatePage = () => {
|
|||||||
targetInvoiceSeriesOptions={createCtrl.targetInvoiceSeriesOptions}
|
targetInvoiceSeriesOptions={createCtrl.targetInvoiceSeriesOptions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</RdxEditorPageLayout>
|
||||||
|
</div>
|
||||||
|
|
||||||
<SelectCustomerDialog
|
<SelectCustomerDialog
|
||||||
ctrl={selectCustomerCtrl.selectCtrl}
|
ctrl={selectCustomerCtrl.selectCtrl}
|
||||||
|
|||||||
@ -82,12 +82,13 @@ Reglas actuales:
|
|||||||
|
|
||||||
### UX actual de `/account`
|
### UX actual de `/account`
|
||||||
|
|
||||||
La pantalla web `/account` se presenta como una única vista de self-service para el usuario autenticado.
|
La pantalla web `/account` se presenta como un editor focalizado de self-service para el usuario autenticado.
|
||||||
|
|
||||||
Contenido visible actual:
|
Contenido visible actual:
|
||||||
|
|
||||||
- información personal: `name`, `email`, `language_code`
|
- información personal: `name`, `email`, `language_code`
|
||||||
- avatar en modo solo lectura, con fallback por iniciales
|
- modo `app-fullscreen`, sin sidebar principal
|
||||||
|
- contenido centrado con ancho controlado mediante `RdxEditorPageLayout`
|
||||||
- seguridad mediante dialog para cambio de contraseña
|
- seguridad mediante dialog para cambio de contraseña
|
||||||
- cierre de sesión reutilizando el flujo de logout existente
|
- cierre de sesión reutilizando el flujo de logout existente
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
|
"header": {
|
||||||
|
"back": "Back"
|
||||||
|
},
|
||||||
"title": "My account",
|
"title": "My account",
|
||||||
"description": "Manage your personal details, preferences, and security.",
|
"description": "Manage your personal details, preferences, and security.",
|
||||||
"loading": "Loading your account...",
|
"loading": "Loading your account...",
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
|
"header": {
|
||||||
|
"back": "Volver"
|
||||||
|
},
|
||||||
"title": "Mi cuenta",
|
"title": "Mi cuenta",
|
||||||
"description": "Gestiona tus datos personales, preferencias y seguridad.",
|
"description": "Gestiona tus datos personales, preferencias y seguridad.",
|
||||||
"loading": "Cargando tu cuenta...",
|
"loading": "Cargando tu cuenta...",
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const IdentityAccountRoutes = (_params: ModuleClientParams): ModuleRouteO
|
|||||||
{
|
{
|
||||||
path: "account",
|
path: "account",
|
||||||
handle: {
|
handle: {
|
||||||
layout: "app-sidebar",
|
layout: "app-fullscreen",
|
||||||
protected: true,
|
protected: true,
|
||||||
requireCompany: false,
|
requireCompany: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { ErrorAlert, PageErrorState } from "@erp/core/components";
|
import { ErrorAlert, PageFormHeader, PageErrorState } from "@erp/core/components";
|
||||||
import { UnsavedChangesProvider } from "@erp/core/hooks";
|
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||||
import {
|
import {
|
||||||
AppContent,
|
|
||||||
CancelActionButton,
|
CancelActionButton,
|
||||||
|
RdxEditorPageLayout,
|
||||||
RdxEditorSection,
|
RdxEditorSection,
|
||||||
RdxEditorStickyFooter,
|
RdxEditorStickyFooter,
|
||||||
RhfSubmitActionButton,
|
RhfSubmitActionButton,
|
||||||
@ -28,15 +28,15 @@ import type { AccountPasswordForm, AccountProfileForm } from "../../entities";
|
|||||||
|
|
||||||
const AccountLoadingState = ({ label }: { label: string }) => {
|
const AccountLoadingState = ({ label }: { label: string }) => {
|
||||||
return (
|
return (
|
||||||
<AppContent className="py-2">
|
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||||
<div
|
<RdxEditorPageLayout className="min-h-full" complexity="simple">
|
||||||
aria-busy="true"
|
<div className="flex min-h-[16rem] items-center justify-center text-sm text-muted-foreground">
|
||||||
className="flex min-h-64 items-center justify-center text-sm text-muted-foreground"
|
<div aria-busy="true" role="status">
|
||||||
role="status"
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
</AppContent>
|
</div>
|
||||||
|
</RdxEditorPageLayout>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,8 +62,9 @@ const AccountErrorState = ({
|
|||||||
variant: React.ComponentProps<typeof PageErrorState>["variant"];
|
variant: React.ComponentProps<typeof PageErrorState>["variant"];
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<AppContent className="py-2">
|
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||||
<div className="mx-auto w-full max-w-4xl">
|
<RdxEditorPageLayout complexity="simple">
|
||||||
|
<div className="mx-auto w-full max-w-3xl">
|
||||||
<PageErrorState
|
<PageErrorState
|
||||||
description={description}
|
description={description}
|
||||||
primaryAction={
|
primaryAction={
|
||||||
@ -80,7 +81,8 @@ const AccountErrorState = ({
|
|||||||
variant={variant}
|
variant={variant}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AppContent>
|
</RdxEditorPageLayout>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -200,6 +202,9 @@ export const AccountPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ctrl = useAccountPageController();
|
const ctrl = useAccountPageController();
|
||||||
const [isPasswordDialogOpen, setPasswordDialogOpen] = useState(false);
|
const [isPasswordDialogOpen, setPasswordDialogOpen] = useState(false);
|
||||||
|
const { navigateBack } = useReturnToNavigation({
|
||||||
|
fallbackPath: "/dashboard",
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ctrl.passwordSuccessVersion) {
|
if (!ctrl.passwordSuccessVersion) {
|
||||||
@ -246,17 +251,23 @@ export const AccountPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContent className="py-2">
|
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||||
<div className="max-w-4xl space-y-6">
|
<PageFormHeader
|
||||||
|
backLabel={t("account.header.back")}
|
||||||
|
onBack={navigateBack}
|
||||||
|
title={t("account.title")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||||
|
<RdxEditorPageLayout complexity="simple">
|
||||||
|
<div className="rounded-2xl border border-border/60 bg-background px-4 py-4 shadow-xs md:px-6 md:py-6">
|
||||||
|
<div className="flex min-w-0 flex-col gap-6">
|
||||||
<FormProvider {...ctrl.profileForm}>
|
<FormProvider {...ctrl.profileForm}>
|
||||||
<UnsavedChangesProvider isDirty={ctrl.profileForm.formState.isDirty}>
|
<UnsavedChangesProvider isDirty={ctrl.profileForm.formState.isDirty}>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="space-y-2">
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">{t("account.title")}</h1>
|
|
||||||
<p className="max-w-3xl text-sm text-muted-foreground">
|
<p className="max-w-3xl text-sm text-muted-foreground">
|
||||||
{t("account.description")}
|
{t("account.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
{ctrl.inlineProfileError ? (
|
{ctrl.inlineProfileError ? (
|
||||||
<ErrorAlert
|
<ErrorAlert
|
||||||
@ -275,7 +286,6 @@ export const AccountPage = () => {
|
|||||||
<RdxEditorSection
|
<RdxEditorSection
|
||||||
description={t("account.sections.personal.description")}
|
description={t("account.sections.personal.description")}
|
||||||
title={t("account.sections.personal.title")}
|
title={t("account.sections.personal.title")}
|
||||||
variant="panel"
|
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 gap-x-6 gap-y-5 md:grid-cols-12">
|
<div className="grid grid-cols-1 gap-x-6 gap-y-5 md:grid-cols-12">
|
||||||
<TextField<AccountProfileForm>
|
<TextField<AccountProfileForm>
|
||||||
@ -354,7 +364,6 @@ export const AccountPage = () => {
|
|||||||
<RdxEditorSection
|
<RdxEditorSection
|
||||||
description={t("account.sections.security.description")}
|
description={t("account.sections.security.description")}
|
||||||
title={t("account.sections.security.title")}
|
title={t("account.sections.security.title")}
|
||||||
variant="panel"
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@ -373,7 +382,6 @@ export const AccountPage = () => {
|
|||||||
<RdxEditorSection
|
<RdxEditorSection
|
||||||
description={t("account.sections.session.description")}
|
description={t("account.sections.session.description")}
|
||||||
title={t("account.sections.session.title")}
|
title={t("account.sections.session.title")}
|
||||||
variant="panel"
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
@ -385,6 +393,9 @@ export const AccountPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</RdxEditorSection>
|
</RdxEditorSection>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</RdxEditorPageLayout>
|
||||||
|
</div>
|
||||||
|
|
||||||
<AccountPasswordDialog
|
<AccountPasswordDialog
|
||||||
ctrl={ctrl}
|
ctrl={ctrl}
|
||||||
@ -393,6 +404,6 @@ export const AccountPage = () => {
|
|||||||
onOpenChange={handlePasswordDialogOpenChange}
|
onOpenChange={handlePasswordDialogOpenChange}
|
||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
</AppContent>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -64,6 +64,8 @@ Deben recibir props ya preparadas por la capa consumidora.
|
|||||||
|
|
||||||
## Editor simple
|
## Editor simple
|
||||||
|
|
||||||
|
- por defecto, las pantallas cuya finalidad principal sea crear, editar o configurar mediante formulario deben usar shell `app-fullscreen` y no el shell con sidebar;
|
||||||
|
- usar `PageFormHeader` o el header de dominio ya existente para la navegación mínima de salida, sin duplicar una segunda navegación lateral;
|
||||||
- `RdxEditorSection` con `variant="plain"` es el patrón por defecto del editor simple;
|
- `RdxEditorSection` con `variant="plain"` es el patrón por defecto del editor simple;
|
||||||
- usar una única superficie editable cuando sea útil separar claramente la página del área de formulario;
|
- usar una única superficie editable cuando sea útil separar claramente la página del área de formulario;
|
||||||
- no envolver cada sección en `Card` por defecto;
|
- no envolver cada sección en `Card` por defecto;
|
||||||
|
|||||||
@ -101,6 +101,8 @@ scroll container
|
|||||||
|
|
||||||
Reglas:
|
Reglas:
|
||||||
|
|
||||||
|
- las rutas cuya tarea principal sea crear, editar o configurar mediante un formulario focalizado deben entrar por el shell `app-fullscreen`;
|
||||||
|
- listados, dashboards, detalle y navegación de settings mantienen `app-sidebar` salvo necesidad funcional explícita;
|
||||||
- `RdxEditorPageLayout` resuelve padding interior, ancho centrado y `min-w-0`;
|
- `RdxEditorPageLayout` resuelve padding interior, ancho centrado y `min-w-0`;
|
||||||
- el editor simple puede apoyarse en una única superficie contenedora para diferenciar la página del área editable, sin volver a cards por sección;
|
- el editor simple puede apoyarse en una única superficie contenedora para diferenciar la página del área editable, sin volver a cards por sección;
|
||||||
- `RdxEditorSection` expresa jerarquía por defecto con spacing y divisor inferior;
|
- `RdxEditorSection` expresa jerarquía por defecto con spacing y divisor inferior;
|
||||||
@ -109,9 +111,11 @@ Reglas:
|
|||||||
- usar secciones planas con espacio o divisores;
|
- usar secciones planas con espacio o divisores;
|
||||||
- reservar sticky footer solo cuando el scroll lo justifique.
|
- reservar sticky footer solo cuando el scroll lo justifique.
|
||||||
|
|
||||||
Ejemplo validado en `customers/update` y `customers/create`:
|
Ejemplo validado en `customers/update`, `customers/create` y `/account`:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
|
<AppFullscreenLayout>
|
||||||
|
<PageFormHeader backLabel={backLabel} onBack={onBack} title={title} />
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<RdxEditorPageLayout complexity="simple">
|
<RdxEditorPageLayout complexity="simple">
|
||||||
<div className="flex min-w-0 flex-col gap-6">
|
<div className="flex min-w-0 flex-col gap-6">
|
||||||
@ -125,6 +129,7 @@ Ejemplo validado en `customers/update` y `customers/create`:
|
|||||||
</div>
|
</div>
|
||||||
</RdxEditorPageLayout>
|
</RdxEditorPageLayout>
|
||||||
</div>
|
</div>
|
||||||
|
</AppFullscreenLayout>
|
||||||
```
|
```
|
||||||
|
|
||||||
Deuda registrada:
|
Deuda registrada:
|
||||||
@ -142,8 +147,16 @@ ESPECÍFICO
|
|||||||
defaults iniciales
|
defaults iniciales
|
||||||
wiring de submit
|
wiring de submit
|
||||||
feedback de error
|
feedback de error
|
||||||
|
navegación de retorno concreta
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Candidatos explícitos para auditoría posterior:
|
||||||
|
|
||||||
|
- `customers/create`
|
||||||
|
- `customers/update`
|
||||||
|
- `/account`
|
||||||
|
- `proformas/update`
|
||||||
|
|
||||||
## Editor complejo
|
## Editor complejo
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user