refactor: replace RdxEditorLayout with RdxPageContent across multiple components

- Updated ScopedListProformasPageView, ProformaUpdateEditorForm, ProformaUpdatePage, RecurringProformaCreatePage, and others to use RdxPageContent instead of RdxEditorLayout.
- Removed RdxPageStack and replaced it with RdxPageContent for consistent layout management.
- Deleted RdxEditorLayout component and its associated tests.
- Introduced RdxPageContent component to handle main content and optional aside layout.
- Adjusted tests to reflect changes in component structure and layout.
This commit is contained in:
David Arranz 2026-09-14 11:00:56 +02:00
parent e7e82d95ad
commit a13ababdf6
37 changed files with 368 additions and 365 deletions

View File

@ -2,12 +2,11 @@ import {
RdxBadge, RdxBadge,
RdxContextPanel, RdxContextPanel,
RdxEditorActionBar, RdxEditorActionBar,
RdxEditorLayout,
RdxFormGrid, RdxFormGrid,
RdxPageActions, RdxPageActions,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
RdxPanel, RdxPanel,
RdxRowActions, RdxRowActions,
RightPanel, RightPanel,
@ -87,7 +86,7 @@ export default function UiKitPage() {
return ( return (
<RdxPageContainer width="wide"> <RdxPageContainer width="wide">
<RdxPageStack spacing="relaxed"> <RdxPageContent spacing="relaxed">
<RdxPageHeader <RdxPageHeader
actions={ actions={
<RdxPageActions <RdxPageActions
@ -244,7 +243,18 @@ export default function UiKitPage() {
id="page-containers" id="page-containers"
title="Page containers" title="Page containers"
> >
<div className="grid gap-4 xl:grid-cols-3"> <div className="grid gap-4 xl:grid-cols-4">
<ExampleCard notes="`spacing=&quot;none&quot;`" title="None">
<RdxPageContent
className="rounded-lg border border-border bg-background p-4"
spacing="none"
>
<SurfaceExample>Block 1</SurfaceExample>
<SurfaceExample>Block 2</SurfaceExample>
<SurfaceExample>Block 3</SurfaceExample>
</RdxPageContent>
</ExampleCard>
<ExampleCard notes="`width=&quot;full&quot;`" title="Full"> <ExampleCard notes="`width=&quot;full&quot;`" title="Full">
<RdxPageContainer <RdxPageContainer
className="rounded-lg border border-dashed border-border bg-muted/40" className="rounded-lg border border-dashed border-border bg-muted/40"
@ -277,42 +287,42 @@ export default function UiKitPage() {
<Separator /> <Separator />
<UiKitSection <UiKitSection
description="`RdxPageStack` usa `gap` para componer contenido condicional sin depender de `space-y-*`." description="`RdxPageContent` usa `gap` para componer contenido condicional sin depender de `space-y-*`."
id="vertical-spacing" id="vertical-spacing"
title="Vertical spacing" title="Vertical spacing"
> >
<div className="grid gap-4 xl:grid-cols-3"> <div className="grid gap-4 xl:grid-cols-3">
<ExampleCard notes="`spacing=&quot;compact&quot;`" title="Compact"> <ExampleCard notes="`spacing=&quot;compact&quot;`" title="Compact">
<RdxPageStack <RdxPageContent
className="rounded-lg border border-border bg-background p-4" className="rounded-lg border border-border bg-background p-4"
spacing="compact" spacing="compact"
> >
<SurfaceExample>Block 1</SurfaceExample> <SurfaceExample>Block 1</SurfaceExample>
<SurfaceExample>Block 2</SurfaceExample> <SurfaceExample>Block 2</SurfaceExample>
<SurfaceExample>Block 3</SurfaceExample> <SurfaceExample>Block 3</SurfaceExample>
</RdxPageStack> </RdxPageContent>
</ExampleCard> </ExampleCard>
<ExampleCard notes="`spacing=&quot;default&quot;`" title="Default"> <ExampleCard notes="`spacing=&quot;default&quot;`" title="Default">
<RdxPageStack <RdxPageContent
className="rounded-lg border border-border bg-background p-4" className="rounded-lg border border-border bg-background p-4"
spacing="default" spacing="default"
> >
<SurfaceExample>Block 1</SurfaceExample> <SurfaceExample>Block 1</SurfaceExample>
<SurfaceExample>Block 2</SurfaceExample> <SurfaceExample>Block 2</SurfaceExample>
<SurfaceExample>Block 3</SurfaceExample> <SurfaceExample>Block 3</SurfaceExample>
</RdxPageStack> </RdxPageContent>
</ExampleCard> </ExampleCard>
<ExampleCard notes="`spacing=&quot;relaxed&quot;`" title="Relaxed"> <ExampleCard notes="`spacing=&quot;relaxed&quot;`" title="Relaxed">
<RdxPageStack <RdxPageContent
className="rounded-lg border border-border bg-background p-4" className="rounded-lg border border-border bg-background p-4"
spacing="relaxed" spacing="relaxed"
> >
<SurfaceExample>Block 1</SurfaceExample> <SurfaceExample>Block 1</SurfaceExample>
<SurfaceExample>Block 2</SurfaceExample> <SurfaceExample>Block 2</SurfaceExample>
<SurfaceExample>Block 3</SurfaceExample> <SurfaceExample>Block 3</SurfaceExample>
</RdxPageStack> </RdxPageContent>
</ExampleCard> </ExampleCard>
</div> </div>
</UiKitSection> </UiKitSection>
@ -454,12 +464,12 @@ export default function UiKitPage() {
title="Real list-page composition" title="Real list-page composition"
> >
<ExampleCard <ExampleCard
notes="Representa `RdxPageContainer width=&quot;full&quot;`, `RdxPageStack spacing=&quot;default&quot;`, `RdxPageHeader`, acción primaria, aviso opcional y superficie de listado." notes="Representa `RdxPageContainer width=&quot;full&quot;`, `RdxPageContent spacing=&quot;default&quot;`, `RdxPageHeader`, acción primaria, aviso opcional y superficie de listado."
title="Pilot integration" title="Pilot integration"
> >
<div className="rounded-lg border border-border bg-muted/30"> <div className="rounded-lg border border-border bg-muted/30">
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader <RdxPageHeader
actions={ actions={
<RdxPageActions <RdxPageActions
@ -487,7 +497,7 @@ export default function UiKitPage() {
<div className="h-72 rounded-md border border-dashed border-border bg-muted/30" /> <div className="h-72 rounded-md border border-dashed border-border bg-muted/30" />
</div> </div>
</div> </div>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
</ExampleCard> </ExampleCard>
@ -506,7 +516,7 @@ export default function UiKitPage() {
> >
<div className="rounded-lg border border-border bg-muted/30"> <div className="rounded-lg border border-border bg-muted/30">
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader <RdxPageHeader
actions={ actions={
<RdxPageActions <RdxPageActions
@ -557,7 +567,7 @@ export default function UiKitPage() {
</div> </div>
</div> </div>
</div> </div>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
</ExampleCard> </ExampleCard>
@ -746,19 +756,19 @@ export default function UiKitPage() {
<Separator /> <Separator />
<UiKitSection <UiKitSection
description="Foundations iniciales del editor simple validadas con `customers/update` y `customers/create`, manteniendo el header legacy fuera de `rdx-ui`." description="Composición interior de página con spacing y aside opcional, separada del ancho y padding exterior."
id="editor-foundations" id="editor-foundations"
title="Editor foundations" title="Editor foundations"
> >
<div className="grid gap-4"> <div className="grid gap-4">
<ExampleCard title="RdxPageContainer + RdxEditorLayout"> <ExampleCard title="RdxPageContainer + RdxPageContent">
<div className="rounded-lg border border-border bg-muted/30"> <div className="rounded-lg border border-border bg-muted/30">
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="rounded-lg border border-dashed border-border bg-background p-4 text-sm text-foreground"> <div className="rounded-lg border border-dashed border-border bg-background p-4 text-sm text-foreground">
Centered editor content Centered editor content
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
</ExampleCard> </ExampleCard>
@ -809,7 +819,7 @@ export default function UiKitPage() {
> >
<div className="rounded-lg border border-border bg-muted/30"> <div className="rounded-lg border border-border bg-muted/30">
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="rounded-2xl border border-border/60 bg-background px-4 py-4 shadow-xs md:px-6 md:py-6"> <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"> <div className="flex min-w-0 flex-col gap-6">
<div className="rounded-lg border border-border bg-background px-4 py-3 text-sm text-muted-foreground"> <div className="rounded-lg border border-border bg-background px-4 py-3 text-sm text-muted-foreground">
@ -849,7 +859,7 @@ export default function UiKitPage() {
</div> </div>
</div> </div>
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
</ExampleCard> </ExampleCard>
@ -875,7 +885,7 @@ export default function UiKitPage() {
<div className="flex min-h-0 flex-1 flex-col bg-muted/20"> <div className="flex min-h-0 flex-1 flex-col bg-muted/20">
<div className="min-h-0 flex-1 overflow-y-auto"> <div className="min-h-0 flex-1 overflow-y-auto">
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxEditorLayout <RdxPageContent
aside={ aside={
<div className="flex min-w-0 flex-col gap-6"> <div className="flex min-w-0 flex-col gap-6">
<RdxPanel <RdxPanel
@ -931,7 +941,7 @@ export default function UiKitPage() {
</section> </section>
</div> </div>
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
@ -972,14 +982,14 @@ export default function UiKitPage() {
<ExampleCard title="List composition"> <ExampleCard title="List composition">
<div className="rounded-lg border border-border bg-muted/30"> <div className="rounded-lg border border-border bg-muted/30">
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack> <RdxPageContent>
<RdxPageHeader <RdxPageHeader
actions={<RdxPageActions primary={<Button type="button">New item</Button>} />} actions={<RdxPageActions primary={<Button type="button">New item</Button>} />}
description="Static list example" description="Static list example"
title="List page" title="List page"
/> />
<SurfaceExample>List content</SurfaceExample> <SurfaceExample>List content</SurfaceExample>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
</ExampleCard> </ExampleCard>
@ -988,26 +998,26 @@ export default function UiKitPage() {
<div className="space-y-4"> <div className="space-y-4">
<div className="rounded-lg border border-border bg-muted/30"> <div className="rounded-lg border border-border bg-muted/30">
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxPageStack> <RdxPageContent>
<RdxPageHeader description="Simple centered editor" title="Editor page" /> <RdxPageHeader description="Simple centered editor" title="Editor page" />
<SurfaceExample>Form content</SurfaceExample> <SurfaceExample>Form content</SurfaceExample>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
<div className="rounded-lg border border-border bg-muted/30"> <div className="rounded-lg border border-border bg-muted/30">
<RdxPageContainer width="wide"> <RdxPageContainer width="wide">
<RdxPageStack> <RdxPageContent>
<RdxPageHeader description="Wide detail composition" title="Detail page" /> <RdxPageHeader description="Wide detail composition" title="Detail page" />
<SurfaceExample>Detail content</SurfaceExample> <SurfaceExample>Detail content</SurfaceExample>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</div> </div>
</div> </div>
</ExampleCard> </ExampleCard>
</div> </div>
</UiKitSection> </UiKitSection>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
); );
} }

View File

@ -1,9 +1,9 @@
import { PageErrorState } from "@erp/core/components"; import { PageErrorState } from "@erp/core/components";
import { import {
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import { Skeleton } from "@repo/shadcn-ui/components"; import { Skeleton } from "@repo/shadcn-ui/components";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
@ -64,7 +64,7 @@ export const HomePage = () => {
return ( return (
<RdxPageFrame> <RdxPageFrame>
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader <RdxPageHeader
description={t("web.home.header.description")} description={t("web.home.header.description")}
title={t("web.home.header.title")} title={t("web.home.header.title")}
@ -113,7 +113,7 @@ export const HomePage = () => {
{t("web.home.footer.link")} {t("web.home.footer.link")}
</Link> </Link>
</p> </p>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -1,8 +1,8 @@
import { import {
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
@ -30,7 +30,7 @@ export const SettingsLayout = ({ sections }: SettingsLayoutProps) => {
title={t("web.settings.title")} title={t("web.settings.title")}
/> />
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<div className="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]"> <div className="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
<aside className="min-w-0 lg:sticky lg:top-6 lg:self-start"> <aside className="min-w-0 lg:sticky lg:top-6 lg:self-start">
<SettingsNavigation sections={sections} /> <SettingsNavigation sections={sections} />
@ -40,7 +40,7 @@ export const SettingsLayout = ({ sections }: SettingsLayoutProps) => {
<Outlet /> <Outlet />
</section> </section>
</div> </div>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -61,6 +61,16 @@ Application shell
Dentro de `AppMain`, una pantalla usa `RdxPageFrame`; el sistema visual y sus reglas se documentan en [UI system](./ui-system.md). Dentro de `AppMain`, una pantalla usa `RdxPageFrame`; el sistema visual y sus reglas se documentan en [UI system](./ui-system.md).
```text
AppMain
└── RdxPageFrame
├── header
├── content
│ └── RdxPageContainer
│ └── RdxPageContent
└── footer opcional
```
## 6. Identity y sesión ## 6. Identity y sesión
El runtime actual se compone en `apps/web/src/app.tsx`: El runtime actual se compone en `apps/web/src/app.tsx`:
@ -130,4 +140,3 @@ El estado remoto permanece en TanStack Query. No se duplica en estado local salv
- No introducir acceso a red en UI ni reglas de negocio en Pages. - No introducir acceso a red en UI ni reglas de negocio en Pages.
- Mantener routing, sesión, tenant y sistema visual como responsabilidades separadas. - Mantener routing, sesión, tenant y sistema visual como responsabilidades separadas.
- Cambios de API visual pública deben sincronizar implementación, [UI system](./ui-system.md), UI Kit y tests. - Cambios de API visual pública deben sincronizar implementación, [UI system](./ui-system.md), UI Kit y tests.

View File

@ -9,7 +9,7 @@ Los componentes aquí citados se definen en `ui-system.md`; este documento expli
```tsx ```tsx
<RdxPageFrame> <RdxPageFrame>
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack> <RdxPageContent>
<RdxPageHeader <RdxPageHeader
title={title} title={title}
description={description} description={description}
@ -17,7 +17,7 @@ Los componentes aquí citados se definen en `ui-system.md`; este documento expli
/> />
{refreshError} {refreshError}
<DataTable topContent={toolbarAndFilters} {...tableProps} /> <DataTable topContent={toolbarAndFilters} {...tableProps} />
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
``` ```
@ -40,12 +40,10 @@ El controller de página concentra URL, filtros, consulta, estados y navegación
footer={<RdxEditorActionBar secondary={cancel} primary={submit} />} footer={<RdxEditorActionBar secondary={cancel} primary={submit} />}
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout aside={aside}> <RdxPageContent aside={aside}>
<RdxPageStack> {notice}
{notice} <RdxPanel title={sectionTitle}>{fields}</RdxPanel>
<RdxPanel title={sectionTitle}>{fields}</RdxPanel> </RdxPageContent>
</RdxPageStack>
</RdxEditorLayout>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
``` ```
@ -185,4 +183,3 @@ Las traducciones productivas deben probarse sin sustituirlas por un diccionario
- [ ] Acciones, permisos y navegación permanecen en el consumidor. - [ ] Acciones, permisos y navegación permanecen en el consumidor.
- [ ] Responsive, teclado, foco, i18n y light/dark están validados. - [ ] Responsive, teclado, foco, i18n y light/dark están validados.
- [ ] Implementación, documentación, UI Kit y tests siguen sincronizados. - [ ] Implementación, documentación, UI Kit y tests siguen sincronizados.

View File

@ -28,8 +28,7 @@ AppMain
│ └── RdxEditorHeader │ └── RdxEditorHeader
├── content (scroll principal) ├── content (scroll principal)
│ └── RdxPageContainer │ └── RdxPageContainer
│ ├── RdxPageStack │ └── RdxPageContent
│ └── RdxEditorLayout
└── footer opcional └── footer opcional
└── RdxEditorActionBar └── RdxEditorActionBar
``` ```
@ -52,7 +51,7 @@ Cada nivel tiene una sola responsabilidad. No se reconstruye viewport, anchura y
`padding="none"` elimina únicamente su padding. El container no controla altura ni scroll. `padding="none"` elimina únicamente su padding. El container no controla altura ni scroll.
`RdxPageStack` establece el ritmo vertical: `RdxPageContent` establece el ritmo vertical del main:
| Spacing | Gap | | Spacing | Gap |
| --- | --- | | --- | --- |
@ -93,9 +92,9 @@ Ninguno usa `sticky` para seleccionar una variante. El frame coloca el header fu
Ordena regiones `secondary`, `primary` y `overflow`. No decide qué acciones existen ni sus permisos. Ordena regiones `secondary`, `primary` y `overflow`. No decide qué acciones existen ni sus permisos.
### `RdxEditorLayout` ### `RdxPageContent`
Distribuye main y un `aside` opcional. No controla width, padding ni scroll. Sin aside no reserva una columna. En desktop, el aside usa `22rem`; en anchos menores se apila. Apila verticalmente el main y distribuye un `aside` opcional. No controla width, padding ni scroll. Sin aside no reserva una columna. En desktop, el aside usa `22rem`; en anchos menores se apila. `spacing` afecta únicamente al main.
El aside aloja contexto secundario, metadata o configuración auxiliar. No se mueven campos allí solo por estética. El aside aloja contexto secundario, metadata o configuración auxiliar. No se mueven campos allí solo por estética.
@ -182,4 +181,3 @@ Cada cambio de API pública debe actualizar conjuntamente:
- Colores físicos o color como única señal. - Colores físicos o color como única señal.
Las recetas para construir páginas concretas están en [UI patterns](./ui-patterns.md). Las recetas para construir páginas concretas están en [UI patterns](./ui-patterns.md).

View File

@ -776,7 +776,7 @@ Ejemplos:
```txt ```txt
RdxPageContainer RdxPageContainer
RdxPageHeader RdxPageHeader
RdxPageStack RdxPageContent
``` ```
No mover a `packages/rdx-ui`: No mover a `packages/rdx-ui`:

View File

@ -118,7 +118,7 @@ Todos los textos visibles pertenecen al namespace `Suppliers`. Se mantienen recu
## 17. Componentes reutilizados ## 17. Componentes reutilizados
- `RdxPageContainer`, `RdxPageStack`, `RdxPageHeader`, `RdxPageActions`; - `RdxPageContainer`, `RdxPageContent`, `RdxPageHeader`, `RdxPageActions`;
- `RdxEditorPageLayout`, `RdxPanel`, `RdxFormGrid`; - `RdxEditorPageLayout`, `RdxPanel`, `RdxFormGrid`;
- `DataTable`, `SkeletonDataTable`, `DataTableColumnHeader`, `RdxRowActions`; - `DataTable`, `SkeletonDataTable`, `DataTableColumnHeader`, `RdxRowActions`;
- `TextField`, `SelectField`, `PageFormHeader`, `FormActionsBar`; - `TextField`, `SelectField`, `PageFormHeader`, `FormActionsBar`;

View File

@ -1,9 +1,9 @@
import { ErrorAlert, PageErrorState, SimpleSearchInput } from "@erp/core/components"; import { ErrorAlert, PageErrorState, SimpleSearchInput } from "@erp/core/components";
import { import {
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import { import {
Button, Button,
@ -54,7 +54,7 @@ export const AuditPage = () => {
return ( return (
<RdxPageFrame> <RdxPageFrame>
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader description={t("audit.description")} title={t("audit.title")} /> <RdxPageHeader description={t("audit.description")} title={t("audit.title")} />
{controller.isError && controller.hasUsableData ? ( {controller.isError && controller.hasUsableData ? (
@ -97,7 +97,7 @@ export const AuditPage = () => {
</div> </div>
</div> </div>
<AuditEntrySheet entryId={controller.entry} onClose={controller.closeEntry} /> <AuditEntrySheet entryId={controller.entry} onClose={controller.closeEntry} />
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -1,4 +1,4 @@
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components"; import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
import { Skeleton } from "@repo/shadcn-ui/components"; import { Skeleton } from "@repo/shadcn-ui/components";
export const ProformaCreateSkeleton = () => { export const ProformaCreateSkeleton = () => {
@ -12,13 +12,13 @@ export const ProformaCreateSkeleton = () => {
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="space-y-6"> <div className="space-y-6">
<Skeleton className="h-10 w-full" /> <Skeleton className="h-10 w-full" />
<Skeleton className="h-64 w-full" /> <Skeleton className="h-64 w-full" />
<Skeleton className="h-48 w-full" /> <Skeleton className="h-48 w-full" />
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -1,12 +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 { import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
RdxEditorLayout,
RdxPageContainer,
RdxPageFrame,
RdxPageStack,
} 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";
@ -39,47 +34,45 @@ export const ProformaCreatePage = () => {
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent spacing="default">
<RdxPageStack spacing="default"> {createCtrl.isCreateError && (
{createCtrl.isCreateError && ( <ErrorAlert
<ErrorAlert message={
message={ createCtrl.createError instanceof Error
createCtrl.createError instanceof Error ? createCtrl.createError.message
? createCtrl.createError.message : "Revisa los datos e inténtalo de nuevo."
: "Revisa los datos e inténtalo de nuevo." }
} title="No se pudo crear la proforma"
title="No se pudo crear la proforma"
/>
)}
{createCtrl.isSeriesLoadError && (
<ErrorAlert
message={
createCtrl.seriesLoadError instanceof Error
? createCtrl.seriesLoadError.message
: "Inténtalo de nuevo más tarde."
}
title="No se pudieron cargar las series de facturación"
/>
)}
<ProformaCreateForm
formId={createCtrl.formId}
isPaymentTermsLoading={createCtrl.isPaymentTermsLoading}
isProformaSeriesLoading={createCtrl.isProformaSeriesLoading}
isSubmitting={createCtrl.isCreating}
isTargetInvoiceSeriesLoading={createCtrl.isTargetInvoiceSeriesLoading}
onClearCustomer={createCtrl.clearCustomer}
onPaymentTermChange={createCtrl.handlePaymentTermChange}
onSelectCustomer={selectCustomerCtrl.selectCtrl.openDialog}
onSubmit={createCtrl.onSubmit}
paymentTermOptions={createCtrl.paymentTermOptions}
proformaSeriesOptions={createCtrl.proformaSeriesOptions}
selectedCustomer={createCtrl.selectedCustomer}
targetInvoiceSeriesOptions={createCtrl.targetInvoiceSeriesOptions}
/> />
</RdxPageStack> )}
</RdxEditorLayout>
{createCtrl.isSeriesLoadError && (
<ErrorAlert
message={
createCtrl.seriesLoadError instanceof Error
? createCtrl.seriesLoadError.message
: "Inténtalo de nuevo más tarde."
}
title="No se pudieron cargar las series de facturación"
/>
)}
<ProformaCreateForm
formId={createCtrl.formId}
isPaymentTermsLoading={createCtrl.isPaymentTermsLoading}
isProformaSeriesLoading={createCtrl.isProformaSeriesLoading}
isSubmitting={createCtrl.isCreating}
isTargetInvoiceSeriesLoading={createCtrl.isTargetInvoiceSeriesLoading}
onClearCustomer={createCtrl.clearCustomer}
onPaymentTermChange={createCtrl.handlePaymentTermChange}
onSelectCustomer={selectCustomerCtrl.selectCtrl.openDialog}
onSubmit={createCtrl.onSubmit}
paymentTermOptions={createCtrl.paymentTermOptions}
proformaSeriesOptions={createCtrl.proformaSeriesOptions}
selectedCustomer={createCtrl.selectedCustomer}
targetInvoiceSeriesOptions={createCtrl.targetInvoiceSeriesOptions}
/>
</RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>

View File

@ -2,9 +2,9 @@ import { ErrorAlert } from "@erp/core/components";
import { import {
RdxPageActions, RdxPageActions,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
@ -33,7 +33,7 @@ export const ScopedListProformasPageView = ({
return ( return (
<RdxPageFrame> <RdxPageFrame>
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader <RdxPageHeader
actions={primaryAction ? <RdxPageActions primary={primaryAction} /> : undefined} actions={primaryAction ? <RdxPageActions primary={primaryAction} /> : undefined}
description={description} description={description}
@ -45,7 +45,7 @@ export const ScopedListProformasPageView = ({
) : null} ) : null}
{content} {content}
</RdxPageStack> </RdxPageContent>
{dialogs} {dialogs}
</RdxPageContainer> </RdxPageContainer>

View File

@ -1,5 +1,5 @@
import type { CustomerSelectionOption } from "@erp/customers"; import type { CustomerSelectionOption } from "@erp/customers";
import { RdxEditorLayout, type SelectFieldItem } from "@repo/rdx-ui/components"; import { RdxPageContent, type SelectFieldItem } from "@repo/rdx-ui/components";
import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers"; import { preventEnterKeySubmitForm } from "@repo/rdx-ui/helpers";
import type { SubmitEventHandler } from "react"; import type { SubmitEventHandler } from "react";
@ -53,43 +53,42 @@ export const ProformaUpdateEditorForm = ({
className, className,
}: ProformaUpdateEditorProps) => { }: ProformaUpdateEditorProps) => {
return ( return (
<RdxEditorLayout <form id={formId} noValidate onKeyDown={preventEnterKeySubmitForm} onSubmit={onSubmit}>
aside={ <RdxPageContent
<div className="flex min-w-0 flex-col gap-6"> aside={
<ProformaUpdateRecipientEditor
disabled={isSubmitting}
onChangeCustomerClick={onChangeCustomerClick}
onCreateCustomerClick={onCreateCustomerClick}
selectedCustomer={selectedCustomer}
/>
<ProformaUpdateTaxEditor taxCtrl={taxCtrl} taxRegimeOptions={taxCtrl.taxRegimeOptions} />
<ProformaUpdatePaymentEditor
className="w-full"
disabled={isSubmitting || paymentCtrl.isLoading}
onPaymentTermChange={onPaymentTermChange}
paymentMethodOptions={paymentCtrl.paymentMethodOptions}
paymentTermOptions={paymentCtrl.paymentTermOptions}
/>
</div>
}
className={className}
>
<div className="min-w-0">
<form id={formId} noValidate onKeyDown={preventEnterKeySubmitForm} onSubmit={onSubmit}>
<div className="flex min-w-0 flex-col gap-6"> <div className="flex min-w-0 flex-col gap-6">
<ProformaUpdateHeaderEditor disabled={isSubmitting} seriesOptions={seriesOptions} /> <ProformaUpdateRecipientEditor
<ProformaUpdateItemsEditor
disabled={isSubmitting} disabled={isSubmitting}
itemsCtrl={itemsCtrl} onChangeCustomerClick={onChangeCustomerClick}
onCreateCustomerClick={onCreateCustomerClick}
selectedCustomer={selectedCustomer}
/>
<ProformaUpdateTaxEditor
taxCtrl={taxCtrl} taxCtrl={taxCtrl}
totalsCtrl={totalsCtrl} taxRegimeOptions={taxCtrl.taxRegimeOptions}
/>
<ProformaUpdatePaymentEditor
className="w-full"
disabled={isSubmitting || paymentCtrl.isLoading}
onPaymentTermChange={onPaymentTermChange}
paymentMethodOptions={paymentCtrl.paymentMethodOptions}
paymentTermOptions={paymentCtrl.paymentTermOptions}
/> />
</div> </div>
</form> }
</div> className={className}
</RdxEditorLayout> >
<ProformaUpdateHeaderEditor disabled={isSubmitting} seriesOptions={seriesOptions} />
<ProformaUpdateItemsEditor
disabled={isSubmitting}
itemsCtrl={itemsCtrl}
taxCtrl={taxCtrl}
totalsCtrl={totalsCtrl}
/>
</RdxPageContent>
</form>
); );
}; };

View File

@ -67,7 +67,7 @@ test("conecta la alta rápida directa y la alta rápida desde selector con callb
{footer} {footer}
</> </>
), ),
RdxPageStack: ({ children }: { children: ReactNode }) => children, RdxPageContent: ({ children }: { children: ReactNode }) => children,
RhfSubmitActionButton: () => null, RhfSubmitActionButton: () => null,
}, },
}); });

View File

@ -7,8 +7,8 @@ import {
RdxBadge, RdxBadge,
RdxEditorActionBar, RdxEditorActionBar,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageStack,
RhfSubmitActionButton, RhfSubmitActionButton,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import { showErrorToast } from "@repo/rdx-ui/helpers"; import { showErrorToast } from "@repo/rdx-ui/helpers";
@ -54,43 +54,45 @@ export const ProformaUpdatePage = () => {
if (ctrl.pageError) { if (ctrl.pageError) {
return ( return (
<RdxPageContainer width="full"> <RdxPageFrame>
<div className="mx-auto w-full max-w-3xl"> <RdxPageContainer width="full">
<PageErrorState <div className="mx-auto w-full max-w-3xl">
description={ctrl.pageError.description} <PageErrorState
primaryAction={ description={ctrl.pageError.description}
ctrl.pageError.canRetry primaryAction={
? { ctrl.pageError.canRetry
label: ctrl.isRetrying
? t("pages.proformas.update.page_error.actions.retrying")
: t("pages.proformas.update.page_error.actions.retry"),
onClick: ctrl.retryUpdate,
isLoading: ctrl.isRetrying,
}
: ctrl.pageError.variant === "forbidden"
? { ? {
label: t("pages.proformas.update.page_error.actions.go_home"), label: ctrl.isRetrying
onClick: ctrl.goHome, ? t("pages.proformas.update.page_error.actions.retrying")
: t("pages.proformas.update.page_error.actions.retry"),
onClick: ctrl.retryUpdate,
isLoading: ctrl.isRetrying,
} }
: { : ctrl.pageError.variant === "forbidden"
? {
label: t("pages.proformas.update.page_error.actions.go_home"),
onClick: ctrl.goHome,
}
: {
label: t("pages.proformas.update.page_error.actions.back_to_list"),
onClick: ctrl.goBackToList,
}
}
reference={ctrl.pageError.reference}
secondaryAction={
ctrl.pageError.canRetry
? {
label: t("pages.proformas.update.page_error.actions.back_to_list"), label: t("pages.proformas.update.page_error.actions.back_to_list"),
onClick: ctrl.goBackToList, onClick: ctrl.goBackToList,
} }
} : undefined
reference={ctrl.pageError.reference} }
secondaryAction={ title={ctrl.pageError.title}
ctrl.pageError.canRetry variant={ctrl.pageError.variant}
? { />
label: t("pages.proformas.update.page_error.actions.back_to_list"), </div>
onClick: ctrl.goBackToList, </RdxPageContainer>
} </RdxPageFrame>
: undefined
}
title={ctrl.pageError.title}
variant={ctrl.pageError.variant}
/>
</div>
</RdxPageContainer>
); );
} }
@ -188,7 +190,7 @@ export const ProformaUpdatePage = () => {
<RdxPageContainer width="full"> <RdxPageContainer width="full">
{hasPageAlerts ? ( {hasPageAlerts ? (
<RdxPageContainer className="pb-0" width="full"> <RdxPageContainer className="pb-0" width="full">
<RdxPageStack spacing="compact"> <RdxPageContent spacing="compact">
{updateCtrl.isUpdateError && ( {updateCtrl.isUpdateError && (
<ErrorAlert <ErrorAlert
message={ message={
@ -221,7 +223,7 @@ export const ProformaUpdatePage = () => {
)} )}
/> />
)} )}
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
) : null} ) : null}

View File

@ -1,8 +1,8 @@
import { useHookForm } from "@erp/core/hooks"; import { useHookForm } from "@erp/core/hooks";
import { import {
RdxEditorHeader, RdxEditorHeader,
RdxEditorLayout,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import { import {
@ -88,7 +88,7 @@ export const RecurringProformaCreatePage = () => {
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<form <form
className="space-y-6" className="space-y-6"
id={RECURRING_PROFORMA_CREATE_FORM_ID} id={RECURRING_PROFORMA_CREATE_FORM_ID}
@ -139,7 +139,7 @@ export const RecurringProformaCreatePage = () => {
</CardContent> </CardContent>
</Card> </Card>
</form> </form>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -1,5 +1,5 @@
import { PageErrorState } from "@erp/core/components"; import { PageErrorState } from "@erp/core/components";
import { RdxBadge, RdxPageContainer, RdxPageFrame, RdxPageStack } from "@repo/rdx-ui/components"; import { RdxBadge, RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
import { Button, Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components"; import { Button, Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components";
import { ArrowLeftIcon } from "lucide-react"; import { ArrowLeftIcon } from "lucide-react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
@ -130,7 +130,7 @@ export const RecurringProformaDetailPage = () => {
} }
> >
<RdxPageContainer width="wide"> <RdxPageContainer width="wide">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RecurringProformaOperationalSummary <RecurringProformaOperationalSummary
locale={dateLocale} locale={dateLocale}
recurringProforma={recurringProforma} recurringProforma={recurringProforma}
@ -264,7 +264,7 @@ export const RecurringProformaDetailPage = () => {
<ArrowLeftIcon aria-hidden className="size-4" /> <ArrowLeftIcon aria-hidden className="size-4" />
{t("recurring_proformas.detail.back")} {t("recurring_proformas.detail.back")}
</Button> </Button>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -125,7 +125,7 @@ mock.module("@repo/rdx-ui/components", {
namedExports: { namedExports: {
RdxPageContainer: ({ children }: { children: ReactNode }) => <div>{children}</div>, RdxPageContainer: ({ children }: { children: ReactNode }) => <div>{children}</div>,
RdxPageFrame: ({ children }: { children: ReactNode }) => <div>{children}</div>, RdxPageFrame: ({ children }: { children: ReactNode }) => <div>{children}</div>,
RdxPageStack: ({ children }: { children: ReactNode }) => <div>{children}</div>, RdxPageContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
RdxPageHeader: ({ RdxPageHeader: ({
title, title,
description, description,

View File

@ -2,9 +2,9 @@ import { ErrorAlert } from "@erp/core/components";
import { import {
RdxPageActions, RdxPageActions,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
@ -31,7 +31,7 @@ export const ScopedListRecurringProformasPageView = ({
return ( return (
<RdxPageFrame> <RdxPageFrame>
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader <RdxPageHeader
actions={primaryAction ? <RdxPageActions primary={primaryAction} /> : undefined} actions={primaryAction ? <RdxPageActions primary={primaryAction} /> : undefined}
description={description} description={description}
@ -43,7 +43,7 @@ export const ScopedListRecurringProformasPageView = ({
) : null} ) : null}
{content} {content}
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -1,6 +1,6 @@
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 { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components"; import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
import { FormProvider } from "react-hook-form"; import { FormProvider } from "react-hook-form";
import { useTranslation } from "../../../i18n"; import { useTranslation } from "../../../i18n";
@ -47,28 +47,22 @@ export const CustomerCreatePage = () => {
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent spacing="default">
<div className="rounded-2xl border border-border/60 bg-background px-4 py-4 shadow-xs md:px-6 md:py-6"> {createCtrl.isCreateError ? (
<div className="flex min-w-0 flex-col gap-6"> <ErrorAlert
{createCtrl.isCreateError ? ( message={(createCtrl.createError as Error)?.message ?? t("create.errors.message")}
<ErrorAlert title={t("create.errors.title")}
message={ />
(createCtrl.createError as Error)?.message ?? t("create.errors.message") ) : null}
}
title={t("create.errors.title")}
/>
) : null}
<CustomerCreateEditorForm <CustomerCreateEditorForm
fiscalCtrl={createCtrl.fiscalCtrl} fiscalCtrl={createCtrl.fiscalCtrl}
formId={createCtrl.formId} formId={createCtrl.formId}
isSubmitting={createCtrl.isCreating} isSubmitting={createCtrl.isCreating}
onReset={createCtrl.resetForm} onReset={createCtrl.resetForm}
onSubmit={createCtrl.onSubmit} onSubmit={createCtrl.onSubmit}
/> />
</div> </RdxPageContent>
</div>
</RdxEditorLayout>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
</UnsavedChangesProvider> </UnsavedChangesProvider>

View File

@ -2,9 +2,9 @@ import { ErrorAlert } from "@erp/core/components";
import { import {
RdxPageActions, RdxPageActions,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
@ -33,7 +33,7 @@ export const ListCustomersPageView = ({
return ( return (
<RdxPageFrame> <RdxPageFrame>
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader <RdxPageHeader
actions={primaryAction ? <RdxPageActions primary={primaryAction} /> : undefined} actions={primaryAction ? <RdxPageActions primary={primaryAction} /> : undefined}
description={description} description={description}
@ -45,7 +45,7 @@ export const ListCustomersPageView = ({
) : null} ) : null}
{content} {content}
</RdxPageStack> </RdxPageContent>
{dialogs} {dialogs}
</RdxPageContainer> </RdxPageContainer>

View File

@ -1,11 +1,6 @@
import { ErrorAlert, PageErrorState } from "@erp/core/components"; import { ErrorAlert, PageErrorState } from "@erp/core/components";
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks"; import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
import { import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
RdxEditorLayout,
RdxPageContainer,
RdxPageFrame,
RdxPageStack,
} from "@repo/rdx-ui/components";
import { FormProvider } from "react-hook-form"; import { FormProvider } from "react-hook-form";
import { useTranslation } from "../../../i18n"; import { useTranslation } from "../../../i18n";
@ -16,6 +11,7 @@ import { CustomerUpdateEditorForm } from "../editor";
export const CustomerUpdatePage = () => { export const CustomerUpdatePage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctrl = useCustomerUpdatePageController(); const ctrl = useCustomerUpdatePageController();
const { updateCtrl, returnTo } = ctrl; const { updateCtrl, returnTo } = ctrl;
@ -78,6 +74,7 @@ export const CustomerUpdatePage = () => {
<UnsavedChangesProvider isDirty={updateCtrl.form.formState.isDirty}> <UnsavedChangesProvider isDirty={updateCtrl.form.formState.isDirty}>
<RdxPageFrame <RdxPageFrame
contentClassName="bg-muted/20" contentClassName="bg-muted/20"
footer={<h1>hola</h1>}
header={ header={
<CustomerUpdateHeader <CustomerUpdateHeader
formId={updateCtrl.formId} formId={updateCtrl.formId}
@ -102,30 +99,38 @@ export const CustomerUpdatePage = () => {
/> />
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer className="bg-white" width="content">
{hasPageAlerts ? ( <RdxPageContent>
<RdxPageContainer className="pb-0" width="full"> {hasPageAlerts ? (
<RdxPageStack spacing="compact"> <RdxPageContainer className="pb-0" width="full">
{updateCtrl.isUpdateError && ( <div className="flex min-w-0 flex-col gap-4">
<ErrorAlert {updateCtrl.isUpdateError && (
message={ <ErrorAlert
(updateCtrl.updateError as Error)?.message ?? message={
t( (updateCtrl.updateError as Error)?.message ??
"pages.customers.update.error_msg", t(
"Revisa los datos e inténtalo de nuevo." "pages.customers.update.error_msg",
) "Revisa los datos e inténtalo de nuevo."
} )
title={t( }
"pages.customers.update.error_title", title={t(
"No se pudo guardar los cambios" "pages.customers.update.error_title",
)} "No se pudo guardar los cambios"
/> )}
)} />
</RdxPageStack> )}
</RdxPageContainer> </div>
) : null} </RdxPageContainer>
) : null}
<CustomerUpdateEditorForm
fiscalCtrl={updateCtrl.fiscalCtrl}
formId={updateCtrl.formId}
isSubmitting={updateCtrl.isUpdating}
onReset={updateCtrl.resetForm}
onSubmit={updateCtrl.onSubmit}
/>
<RdxEditorLayout>
<div className="rounded-2xl border border-border/60 bg-background px-4 py-4 shadow-xs md:px-6 md:py-6"> <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"> <div className="flex min-w-0 flex-col gap-6">
{updateCtrl.isUpdateError ? ( {updateCtrl.isUpdateError ? (
@ -143,17 +148,9 @@ export const CustomerUpdatePage = () => {
)} )}
/> />
) : null} ) : null}
<CustomerUpdateEditorForm
fiscalCtrl={updateCtrl.fiscalCtrl}
formId={updateCtrl.formId}
isSubmitting={updateCtrl.isUpdating}
onReset={updateCtrl.resetForm}
onSubmit={updateCtrl.onSubmit}
/>
</div> </div>
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
</UnsavedChangesProvider> </UnsavedChangesProvider>

View File

@ -88,7 +88,7 @@ Contenido visible actual:
- información personal: `name`, `email`, `language_code` - información personal: `name`, `email`, `language_code`
- modo `app-fullscreen`, sin sidebar principal - modo `app-fullscreen`, sin sidebar principal
- contenido centrado mediante `RdxPageContainer` y compuesto con `RdxEditorLayout` - contenido centrado mediante `RdxPageContainer` y compuesto con `RdxPageContent`
- 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,17 +1,17 @@
import { PageErrorState } from "@erp/core/components"; import { PageErrorState } from "@erp/core/components";
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components"; import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
import type { ComponentProps } from "react"; import type { ComponentProps } from "react";
export const AccountLoadingState = ({ label }: { label: string }) => ( export const AccountLoadingState = ({ label }: { label: string }) => (
<RdxPageFrame contentClassName="bg-muted/20"> <RdxPageFrame contentClassName="bg-muted/20">
<RdxPageContainer className="min-h-full" width="content"> <RdxPageContainer className="min-h-full" width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="flex min-h-[16rem] items-center justify-center text-sm text-muted-foreground"> <div className="flex min-h-[16rem] items-center justify-center text-sm text-muted-foreground">
<div aria-busy="true" role="status"> <div aria-busy="true" role="status">
{label} {label}
</div> </div>
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );
@ -41,7 +41,7 @@ export const AccountErrorState = ({
}: AccountErrorStateProps) => ( }: AccountErrorStateProps) => (
<RdxPageFrame contentClassName="bg-muted/20"> <RdxPageFrame contentClassName="bg-muted/20">
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="mx-auto w-full max-w-3xl"> <div className="mx-auto w-full max-w-3xl">
<PageErrorState <PageErrorState
description={description} description={description}
@ -59,7 +59,7 @@ export const AccountErrorState = ({
variant={variant} variant={variant}
/> />
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -4,8 +4,8 @@ import {
CancelActionButton, CancelActionButton,
RdxEditorActionBar, RdxEditorActionBar,
RdxEditorHeader, RdxEditorHeader,
RdxEditorLayout,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPanel, RdxPanel,
RhfSubmitActionButton, RhfSubmitActionButton,
@ -207,7 +207,7 @@ export const AccountPage = () => {
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="rounded-2xl border border-border/60 bg-background px-4 py-4 shadow-xs md:px-6 md:py-6"> <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"> <div className="flex min-w-0 flex-col gap-6">
<FormProvider {...ctrl.profileForm}> <FormProvider {...ctrl.profileForm}>
@ -352,7 +352,7 @@ export const AccountPage = () => {
</RdxPanel> </RdxPanel>
</div> </div>
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
<AccountPasswordDialog <AccountPasswordDialog

View File

@ -1,6 +1,6 @@
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 { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components"; import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
import { FormProvider } from "react-hook-form"; import { FormProvider } from "react-hook-form";
import { createSearchParams, useNavigate, useSearchParams } from "react-router-dom"; import { createSearchParams, useNavigate, useSearchParams } from "react-router-dom";
@ -39,7 +39,7 @@ export const SupplierCreatePage = () => {
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="rounded-2xl border border-border/60 bg-background p-4 shadow-xs md:p-6"> <div className="rounded-2xl border border-border/60 bg-background p-4 shadow-xs md:p-6">
<div className="flex min-w-0 flex-col gap-6"> <div className="flex min-w-0 flex-col gap-6">
{controller.mutation.isError ? ( {controller.mutation.isError ? (
@ -55,7 +55,7 @@ export const SupplierCreatePage = () => {
/> />
</div> </div>
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
</UnsavedChangesProvider> </UnsavedChangesProvider>

View File

@ -2,9 +2,9 @@ import { PageErrorState, SimpleSearchInput } from "@erp/core/components";
import { import {
RdxPageActions, RdxPageActions,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import { Button } from "@repo/shadcn-ui/components"; import { Button } from "@repo/shadcn-ui/components";
import { PlusIcon } from "lucide-react"; import { PlusIcon } from "lucide-react";
@ -58,7 +58,7 @@ export const SuppliersListPage = () => {
return ( return (
<RdxPageFrame> <RdxPageFrame>
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<RdxPageHeader <RdxPageHeader
actions={ actions={
<RdxPageActions <RdxPageActions
@ -96,7 +96,7 @@ export const SuppliersListPage = () => {
/> />
} }
/> />
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -1,6 +1,6 @@
import { ErrorAlert, PageErrorState } from "@erp/core/components"; import { ErrorAlert, PageErrorState } from "@erp/core/components";
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks"; import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components"; import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components";
import { FormProvider } from "react-hook-form"; import { FormProvider } from "react-hook-form";
import { createSearchParams, useNavigate, useParams, useSearchParams } from "react-router-dom"; import { createSearchParams, useNavigate, useParams, useSearchParams } from "react-router-dom";
@ -65,7 +65,7 @@ export const SupplierUpdatePage = () => {
} }
> >
<RdxPageContainer width="content"> <RdxPageContainer width="content">
<RdxEditorLayout> <RdxPageContent>
<div className="rounded-2xl border border-border/60 bg-background p-4 shadow-xs md:p-6"> <div className="rounded-2xl border border-border/60 bg-background p-4 shadow-xs md:p-6">
<div className="flex min-w-0 flex-col gap-6"> <div className="flex min-w-0 flex-col gap-6">
{controller.mutation.isError ? ( {controller.mutation.isError ? (
@ -81,7 +81,7 @@ export const SupplierUpdatePage = () => {
/> />
</div> </div>
</div> </div>
</RdxEditorLayout> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
</UnsavedChangesProvider> </UnsavedChangesProvider>

View File

@ -3,9 +3,9 @@ import { useReturnToNavigation } from "@erp/core/hooks";
import { import {
RdxPageActions, RdxPageActions,
RdxPageContainer, RdxPageContainer,
RdxPageContent,
RdxPageFrame, RdxPageFrame,
RdxPageHeader, RdxPageHeader,
RdxPageStack,
} from "@repo/rdx-ui/components"; } from "@repo/rdx-ui/components";
import { Button, Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components"; import { Button, Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components";
import { FileTextIcon, LanguagesIcon, MailIcon, MapPinIcon, PencilIcon } from "lucide-react"; import { FileTextIcon, LanguagesIcon, MailIcon, MapPinIcon, PencilIcon } from "lucide-react";
@ -99,7 +99,7 @@ export const SupplierDetailPage = () => {
} }
> >
<RdxPageContainer width="full"> <RdxPageContainer width="full">
<RdxPageStack spacing="default"> <RdxPageContent spacing="default">
<div className="grid gap-4 lg:grid-cols-2"> <div className="grid gap-4 lg:grid-cols-2">
<Card> <Card>
<CardHeader> <CardHeader>
@ -186,7 +186,7 @@ export const SupplierDetailPage = () => {
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</RdxPageStack> </RdxPageContent>
</RdxPageContainer> </RdxPageContainer>
</RdxPageFrame> </RdxPageFrame>
); );

View File

@ -1,4 +1,3 @@
export * from "./rdx-editor-action-bar.tsx"; export * from "./rdx-editor-action-bar.tsx";
export * from "./rdx-editor-header.tsx"; export * from "./rdx-editor-header.tsx";
export * from "./rdx-editor-layout.tsx";
export * from "./rdx-panel.tsx"; export * from "./rdx-panel.tsx";

View File

@ -7,18 +7,6 @@ import { RdxFormGrid } from "../form/rdx-form-grid.tsx";
import { RdxEditorActionBar } from "./rdx-editor-action-bar.tsx"; import { RdxEditorActionBar } from "./rdx-editor-action-bar.tsx";
import { RdxEditorHeader } from "./rdx-editor-header.tsx"; import { RdxEditorHeader } from "./rdx-editor-header.tsx";
import { RdxEditorLayout } from "./rdx-editor-layout.tsx";
test("RdxEditorLayout only distributes main content and an optional aside", () => {
const html = renderToStaticMarkup(
<RdxEditorLayout aside={<span>Aside</span>}>Main</RdxEditorLayout>
);
assert.match(html, /grid/);
assert.match(html, /<aside/);
assert.doesNotMatch(html, /overflow/);
assert.doesNotMatch(html, /px-4/);
});
test("RdxEditorHeader renderiza navegación, título, estado, acciones y contenido contextual", () => { test("RdxEditorHeader renderiza navegación, título, estado, acciones y contenido contextual", () => {
const html = renderToStaticMarkup( const html = renderToStaticMarkup(

View File

@ -1,31 +0,0 @@
import { cn } from "@repo/shadcn-ui/lib/utils";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
export interface RdxEditorLayoutProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
children: ReactNode;
aside?: ReactNode;
}
/**
* Distribuye el contenido interno de un editor entre una región principal
* y un aside opcional.
*/
export const RdxEditorLayout = ({ children, aside, className, ...props }: RdxEditorLayoutProps) => {
if (!aside) {
return (
<div className={cn("min-w-0", className)} {...props}>
{children}
</div>
);
}
return (
<div
className={cn("grid min-w-0 gap-6 xl:grid-cols-[minmax(0,1fr)_22rem]", className)}
{...props}
>
<div className="min-w-0">{children}</div>
<aside className="min-w-0">{aside}</aside>
</div>
);
};

View File

@ -3,6 +3,6 @@ export * from "./app-header.tsx";
export * from "./app-layout.tsx"; export * from "./app-layout.tsx";
export * from "./rdx-page-actions.tsx"; export * from "./rdx-page-actions.tsx";
export * from "./rdx-page-container.tsx"; export * from "./rdx-page-container.tsx";
export * from "./rdx-page-content.tsx";
export * from "./rdx-page-frame.tsx"; export * from "./rdx-page-frame.tsx";
export * from "./rdx-page-header.tsx"; export * from "./rdx-page-header.tsx";
export * from "./rdx-page-stack.tsx";

View File

@ -0,0 +1,52 @@
import { cn } from "@repo/shadcn-ui/lib/utils";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
export type RdxPageContentSpacing = "none" | "compact" | "default" | "relaxed";
export interface RdxPageContentProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
children: ReactNode;
aside?: ReactNode;
spacing?: RdxPageContentSpacing;
}
const SPACING_CLASSNAME: Record<RdxPageContentSpacing, string> = {
none: "gap-0",
compact: "gap-4",
default: "gap-6",
relaxed: "gap-8",
};
/**
* Compone el contenido interior de una página.
*
* Apila verticalmente el contenido principal y, cuando existe,
* distribuye un aside responsive junto al main.
*/
export const RdxPageContent = ({
children,
aside,
spacing = "default",
className,
...props
}: RdxPageContentProps) => {
if (!aside) {
return (
<div
className={cn("flex min-w-0 flex-col", SPACING_CLASSNAME[spacing], className)}
{...props}
>
{children}
</div>
);
}
return (
<div
className={cn("grid min-w-0 gap-6 xl:grid-cols-[minmax(0,1fr)_22rem]", className)}
{...props}
>
<div className={cn("flex min-w-0 flex-col", SPACING_CLASSNAME[spacing])}>{children}</div>
<aside className="min-w-0">{aside}</aside>
</div>
);
};

View File

@ -5,9 +5,9 @@ import { renderToStaticMarkup } from "react-dom/server";
import { RdxPageActions } from "./rdx-page-actions.tsx"; import { RdxPageActions } from "./rdx-page-actions.tsx";
import { RdxPageContainer } from "./rdx-page-container.tsx"; import { RdxPageContainer } from "./rdx-page-container.tsx";
import { RdxPageContent } from "./rdx-page-content.tsx";
import { RdxPageFrame } from "./rdx-page-frame.tsx"; import { RdxPageFrame } from "./rdx-page-frame.tsx";
import { RdxPageHeader } from "./rdx-page-header.tsx"; import { RdxPageHeader } from "./rdx-page-header.tsx";
import { RdxPageStack } from "./rdx-page-stack.tsx";
test("RdxPageFrame separates fixed regions from the scrollable content", () => { test("RdxPageFrame separates fixed regions from the scrollable content", () => {
const html = renderToStaticMarkup( const html = renderToStaticMarkup(
@ -57,11 +57,11 @@ test("RdxPageContainer permite eliminar el padding exterior", () => {
assert.doesNotMatch(html, /py-4/); assert.doesNotMatch(html, /py-4/);
}); });
test("RdxPageStack applies default spacing and className", () => { test("RdxPageContent applies default spacing and className", () => {
const html = renderToStaticMarkup( const html = renderToStaticMarkup(
<RdxPageStack className="custom-stack"> <RdxPageContent className="custom-stack">
<div>One</div> <div>One</div>
</RdxPageStack> </RdxPageContent>
); );
assert.match(html, /flex/); assert.match(html, /flex/);
@ -69,16 +69,38 @@ test("RdxPageStack applies default spacing and className", () => {
assert.match(html, /custom-stack/); assert.match(html, /custom-stack/);
}); });
test("RdxPageStack applies spacing variants", () => { test("RdxPageContent applies spacing variants", () => {
const noneHtml = renderToStaticMarkup(<RdxPageStack spacing="none">None</RdxPageStack>); const noneHtml = renderToStaticMarkup(<RdxPageContent spacing="none">None</RdxPageContent>);
const compactHtml = renderToStaticMarkup(<RdxPageStack spacing="compact">Compact</RdxPageStack>); const compactHtml = renderToStaticMarkup(
const relaxedHtml = renderToStaticMarkup(<RdxPageStack spacing="relaxed">Relaxed</RdxPageStack>); <RdxPageContent spacing="compact">Compact</RdxPageContent>
);
const relaxedHtml = renderToStaticMarkup(
<RdxPageContent spacing="relaxed">Relaxed</RdxPageContent>
);
assert.match(noneHtml, /gap-0/); assert.match(noneHtml, /gap-0/);
assert.match(compactHtml, /gap-4/); assert.match(compactHtml, /gap-4/);
assert.match(relaxedHtml, /gap-8/); assert.match(relaxedHtml, /gap-8/);
}); });
test("RdxPageContent renders an optional responsive aside and forwards HTML attributes", () => {
const withAsideHtml = renderToStaticMarkup(
<RdxPageContent aside={<span>Aside</span>} data-layout="content">
Main
</RdxPageContent>
);
const withoutAsideHtml = renderToStaticMarkup(<RdxPageContent>Main</RdxPageContent>);
const falseAsideHtml = renderToStaticMarkup(<RdxPageContent aside={false}>Main</RdxPageContent>);
assert.match(withAsideHtml, /data-layout="content"/);
assert.match(withAsideHtml, /grid/);
assert.match(withAsideHtml, /xl:grid-cols-\[minmax\(0,1fr\)_22rem\]/);
assert.match(withAsideHtml, /<aside/);
assert.doesNotMatch(withoutAsideHtml, /<aside/);
assert.doesNotMatch(withoutAsideHtml, /grid-cols/);
assert.doesNotMatch(falseAsideHtml, /<aside/);
});
test("RdxPageHeader renders semantic title, description and optional regions", () => { test("RdxPageHeader renders semantic title, description and optional regions", () => {
const html = renderToStaticMarkup( const html = renderToStaticMarkup(
<RdxPageHeader <RdxPageHeader

View File

@ -22,14 +22,22 @@ export const RdxPageFrame = ({
}: RdxPageFrameProps) => { }: RdxPageFrameProps) => {
return ( return (
<div <div
className={cn("flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", className)} className={cn(
"rdx-page-frame flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
className
)}
{...props} {...props}
> >
{header ? <div className="shrink-0">{header}</div> : null} {header ? <div className="shrink-0 rdx-page-frame-header">{header}</div> : null}
<div className={cn("min-h-0 min-w-0 flex-1 overflow-y-auto", contentClassName)}> <div
className={cn(
"rdx-page-frame-wrapper min-h-0 min-w-0 flex-1 overflow-y-auto",
contentClassName
)}
>
{children} {children}
</div> </div>
{footer ? <div className="shrink-0">{footer}</div> : null} {footer ? <div className="shrink-0 rdx-page-frame-footer">{footer}</div> : null}
</div> </div>
); );
}; };

View File

@ -1,34 +0,0 @@
import { cn } from "@repo/shadcn-ui/lib/utils";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
export type RdxPageStackSpacing = "none" | "compact" | "default" | "relaxed";
export interface RdxPageStackProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
children: ReactNode;
spacing?: RdxPageStackSpacing;
}
const SPACING_CLASSNAME: Record<RdxPageStackSpacing, string> = {
none: "gap-0",
compact: "gap-4",
default: "gap-6",
relaxed: "gap-8",
};
/**
* Apila contenido verticalmente aplicando un espaciado consistente.
* No controla viewport, scroll ni estructura de página.
*/
export const RdxPageStack = ({
children,
className,
spacing = "default",
...props
}: RdxPageStackProps) => {
return (
<div className={cn("flex min-w-0 flex-col", SPACING_CLASSNAME[spacing], className)} {...props}>
{children}
</div>
);
};