feat: refactor account and proforma pages to use app-fullscreen layout and improve UI consistency

This commit is contained in:
David Arranz 2026-08-18 11:54:45 +02:00
parent 64b90888c3
commit 4411b8f07e
10 changed files with 546 additions and 237 deletions

View 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.

View File

@ -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>
); );
}; };

View File

@ -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}

View File

@ -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

View File

@ -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...",

View File

@ -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...",

View File

@ -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,
}, },

View File

@ -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>
); );
}; };

View File

@ -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;

View File

@ -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,10 +111,12 @@ 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
<div className="flex-1 overflow-y-auto"> <AppFullscreenLayout>
<PageFormHeader backLabel={backLabel} onBack={onBack} title={title} />
<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">
{notice} {notice}
@ -124,7 +128,8 @@ Ejemplo validado en `customers/update` y `customers/create`:
</form> </form>
</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