diff --git a/docs/frontend/frontend-architecture.md b/docs/frontend/frontend-architecture.md index c7912e25..c67f747b 100644 --- a/docs/frontend/frontend-architecture.md +++ b/docs/frontend/frontend-architecture.md @@ -82,7 +82,7 @@ RdxPageContent → composición de los hijos principales ``` -Regla: Las Pages no deben reconstruir estas responsabilidades con wrappers HTML locales. En Create/Update, la composición canónica es RdxPageFrame → RdxPageContainer → RdxPageContent: los errores inline y el editor son hijos directos de RdxPageContent; los dialogs son hermanos del frame; RdxPanel se reserva para secciones semánticas reales. +Regla: Las Pages no deben reconstruir estas responsabilidades con wrappers HTML locales. En Create/Update y Detail/Show, la composición canónica es RdxPageFrame → RdxPageContainer → RdxPageContent: errores, alertas, secciones semánticas y contenido funcional son hijos directos de RdxPageContent; los dialogs son hermanos del frame; RdxPanel se reserva para secciones semánticas reales. ## 6. Identity y sesión diff --git a/docs/frontend/ui-patterns.md b/docs/frontend/ui-patterns.md index eaeb4d34..c3ceee03 100644 --- a/docs/frontend/ui-patterns.md +++ b/docs/frontend/ui-patterns.md @@ -59,7 +59,7 @@ El error bloqueante conserva la misma jerarquía: RdxPageFrame → RdxPageContai ## 3. Detail -Un detalle normal usa `RdxPageFrame`, `RdxEditorHeader` cuando es fullscreen y el container apropiado. +Un detalle normal usa `RdxPageFrame`, `RdxEditorHeader` cuando es fullscreen y la jerarquía `RdxPageContainer → RdxPageContent`; errores, alertas, secciones semánticas y contenido funcional viven dentro de `RdxPageContent`. Un detalle documental especializado puede usar: @@ -69,7 +69,7 @@ RdxPageFrame content overflow-hidden └── inspector → scroll interno ``` -Solo se usa esta excepción cuando los panes necesitan permanecer visibles simultáneamente. No se crea un layout genérico de detalle si no existe repetición real. +Solo se usa esta excepción cuando los panes necesitan permanecer visibles simultáneamente. Su scroll interno es parte del workspace documental, no el scroll estructural de la Page. No se crea un layout genérico de detalle si no existe repetición real. ## 4. Settings diff --git a/docs/frontend/ui-system.md b/docs/frontend/ui-system.md index a4b89670..47c87262 100644 --- a/docs/frontend/ui-system.md +++ b/docs/frontend/ui-system.md @@ -78,7 +78,7 @@ Compone header, contenido y footer y es propietario del scroll principal. No con ``` -`contentClassName="overflow-hidden"` se reserva para layouts especializados con panes y scrolls internos. +`contentClassName="overflow-hidden"` se reserva para layouts especializados con panes y scrolls internos; no desplaza la responsabilidad del scroll estructural de Page a wrappers locales. ### Headers @@ -134,7 +134,7 @@ AppMain (espacio disponible, sin scroll) Una Page no debe usar `fixed inset-0`, `h-screen`, `h-dvh`, `overflow-y-auto` ni `overflow-auto` para reconstruir viewport o scroll principal. -Excepción: un detalle documental puede configurar el contenido del frame con `overflow-hidden` y delegar scroll interno a document panes o inspectors. Dialogs, sheets, drawers, overlays, viewers y paneles internos también pueden necesitar posicionamiento o scroll propios. +Excepción: un detalle documental puede configurar el contenido del frame con `overflow-hidden` y delegar scroll interno a document panes o inspectors. Ese scroll es funcional al workspace —por ejemplo, `ProformaDetailContent` e `IssuedInvoiceDetailContent`— y no sustituye el scroll estructural de Page. Dialogs, sheets, drawers, overlays, viewers y paneles internos también pueden necesitar posicionamiento o scroll propios. ## 7. Superficies diff --git a/modules/customer-invoices/src/web/issued-invoices/detail/ui/pages/issued-invoice-detail-page.tsx b/modules/customer-invoices/src/web/issued-invoices/detail/ui/pages/issued-invoice-detail-page.tsx index b1e77523..9008f86d 100644 --- a/modules/customer-invoices/src/web/issued-invoices/detail/ui/pages/issued-invoice-detail-page.tsx +++ b/modules/customer-invoices/src/web/issued-invoices/detail/ui/pages/issued-invoice-detail-page.tsx @@ -1,5 +1,5 @@ import { PageErrorState } from "@erp/core/components"; -import { RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components"; +import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components"; import { useTranslation } from "../../../../i18n"; import { useIssuedInvoiceDetailPageController } from "../../controllers"; @@ -23,14 +23,16 @@ export const IssuedInvoiceDetailPage = () => { return ( - + + + ); @@ -40,15 +42,17 @@ export const IssuedInvoiceDetailPage = () => { return ( - + + + ); diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx index 171895e0..51ad4277 100644 --- a/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx +++ b/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx @@ -1,5 +1,5 @@ import { PageErrorState } from "@erp/core/components"; -import { RdxPageFrame } from "@repo/rdx-ui/components"; +import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components"; import { ChangeProformaStatusDialog } from "../../../change-status"; import { IssueProformaDialog } from "../../../issue-proforma"; @@ -21,18 +21,20 @@ export const ProformaDetailPage = () => { if (ctrl.state.pageError) { return ( -
-
- -
-
+ + + + + + + ); } diff --git a/modules/customer-invoices/src/web/recurring-proformas/detail/ui/pages/recurring-proforma-detail-page.tsx b/modules/customer-invoices/src/web/recurring-proformas/detail/ui/pages/recurring-proforma-detail-page.tsx index 547e7773..66f104d4 100644 --- a/modules/customer-invoices/src/web/recurring-proformas/detail/ui/pages/recurring-proforma-detail-page.tsx +++ b/modules/customer-invoices/src/web/recurring-proformas/detail/ui/pages/recurring-proforma-detail-page.tsx @@ -34,6 +34,14 @@ const DetailField = ({ label, value }: DetailFieldProps) => ( ); +const RecurringProformaDetailSkeleton = ({ label }: { label: string }) => ( +
+
+
+ {label} +
+); + export const RecurringProformaDetailPage = () => { const { t, i18n } = useTranslation(); const navigate = useNavigate(); @@ -48,29 +56,35 @@ export const RecurringProformaDetailPage = () => { if (feedbackState === "loading") { return ( -
-
-
- {t("recurring_proformas.detail.loading")} -
+ + + + + + + ); } if (feedbackState === "error") { return ( -
- { - await query.refetch(); - }, - }} - title={t("recurring_proformas.detail.error.title")} - variant="unknown" - /> -
+ + + + { + await query.refetch(); + }, + }} + title={t("recurring_proformas.detail.error.title")} + variant="unknown" + /> + + + ); } diff --git a/modules/customers/src/web/view/ui/pages/customer-view-page.tsx b/modules/customers/src/web/view/ui/pages/customer-view-page.tsx index 7862900b..564897dc 100644 --- a/modules/customers/src/web/view/ui/pages/customer-view-page.tsx +++ b/modules/customers/src/web/view/ui/pages/customer-view-page.tsx @@ -1,6 +1,6 @@ import { PageErrorState } from "@erp/core/components"; import { useReturnToNavigation } from "@erp/core/hooks"; -import { RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components"; +import { RdxPageContainer, RdxPageContent, RdxPageFrame } from "@repo/rdx-ui/components"; import { Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components"; import { Banknote, @@ -38,39 +38,41 @@ export const CustomerViewPage = () => { return ( - + + : undefined + } + title={ctrl.pageError.title} + variant={ctrl.pageError.variant} + /> + ); @@ -98,282 +100,288 @@ export const CustomerViewPage = () => { /> } > - - {/* Información Básica */} - - - - - {t("view.sections.basic_info")} - - - -
-
{t("view.fields.name")}
-
- {viewCtrl.customerData?.name} -
-
-
-
- {t("view.fields.reference")} -
-
- {viewCtrl.customerData?.reference} -
-
-
-
- {t("view.fields.legal_record")} -
-
- {viewCtrl.customerData?.legalRecord} -
-
-
-
- - {/* Dirección */} - - - - - {t("view.sections.address")} - - - -
-
- {t("view.fields.street")} -
-
- {viewCtrl.customerData?.street} - {viewCtrl.customerData?.street2 && ( - <> -
- {viewCtrl.customerData?.street2} - - )} -
-
-
+ + + {/* Información Básica */} + + + + + {t("view.sections.basic_info")} + + +
- {t("view.fields.city")} + {t("view.fields.name")}
-
{viewCtrl.customerData?.city}
-
-
-
- {t("view.fields.postal_code")} -
-
- {viewCtrl.customerData?.postalCode} -
-
-
-
-
-
- {t("view.fields.province")} -
-
- {viewCtrl.customerData?.province} +
+ {viewCtrl.customerData?.name}
- {t("view.fields.country")} + {t("view.fields.reference")}
-
{viewCtrl.customerData?.country}
+
+ {viewCtrl.customerData?.reference} +
-
-
-
- - {/* Información de Contacto */} - - - - - {t("view.sections.contact_info")} - - - -
- {/* Contacto Principal */} -
-

- {t("view.sections.primary_contact")} -

- {viewCtrl.customerData?.primaryEmail && ( -
- -
-
- {t("view.fields.email")} -
-
- {viewCtrl.customerData?.primaryEmail} -
-
-
- )} - {viewCtrl.customerData?.primaryMobile && ( -
- -
-
- {t("view.fields.mobile")} -
-
- {viewCtrl.customerData?.primaryMobile} -
-
-
- )} - {viewCtrl.customerData?.primaryPhone && ( -
- -
-
- {t("view.fields.phone")} -
-
- {viewCtrl.customerData?.primaryPhone} -
-
-
- )} +
+
+ {t("view.fields.legal_record")} +
+
+ {viewCtrl.customerData?.legalRecord} +
+ + - {/* Contacto Secundario */} -
-

- {t("view.sections.secondary_contact")} -

- {viewCtrl.customerData?.secondaryEmail && ( -
- -
-
- {t("view.fields.email")} -
-
- {viewCtrl.customerData?.secondaryEmail} -
-
-
- )} - {viewCtrl.customerData?.secondaryMobile && ( -
- -
-
- {t("view.fields.mobile")} -
-
- {viewCtrl.customerData?.secondaryMobile} -
-
-
- )} - {viewCtrl.customerData?.secondaryPhone && ( -
- -
-
- {t("view.fields.phone")} -
-
- {viewCtrl.customerData?.secondaryPhone} -
-
-
- )} + {/* Dirección */} + + + + + {t("view.sections.address")} + + + +
+
+ {t("view.fields.street")} +
+
+ {viewCtrl.customerData?.street} + {viewCtrl.customerData?.street2 && ( + <> +
+ {viewCtrl.customerData?.street2} + + )} +
+
+
+
+ {t("view.fields.city")} +
+
{viewCtrl.customerData?.city}
+
+
+
+ {t("view.fields.postal_code")} +
+
+ {viewCtrl.customerData?.postalCode} +
+
+
+
+
+
+ {t("view.fields.province")} +
+
+ {viewCtrl.customerData?.province} +
+
+
+
+ {t("view.fields.country")} +
+
+ {viewCtrl.customerData?.country} +
+
+
+
+
- {/* Otros Contactos */} - {(viewCtrl.customerData?.website || viewCtrl.customerData?.fax) && ( -
+ {/* Información de Contacto */} + + + + + {t("view.sections.contact_info")} + + + +
+ {/* Contacto Principal */} +

- {t("view.sections.other_contacts")} + {t("view.sections.primary_contact")}

-
- {viewCtrl.customerData?.website && ( -
- -
-
- {t("view.fields.website")} -
-
- - {viewCtrl.customerData?.website} - -
-
+ {viewCtrl.customerData?.primaryEmail && ( +
+ +
+
+ {t("view.fields.email")} +
+
+ {viewCtrl.customerData?.primaryEmail} +
- )} - {viewCtrl.customerData?.fax && ( -
- -
-
- {t("view.fields.fax")} -
-
- {viewCtrl.customerData?.fax} -
-
+
+ )} + {viewCtrl.customerData?.primaryMobile && ( +
+ +
+
+ {t("view.fields.mobile")} +
+
+ {viewCtrl.customerData?.primaryMobile} +
- )} +
+ )} + {viewCtrl.customerData?.primaryPhone && ( +
+ +
+
+ {t("view.fields.phone")} +
+
+ {viewCtrl.customerData?.primaryPhone} +
+
+
+ )} +
+ + {/* Contacto Secundario */} +
+

+ {t("view.sections.secondary_contact")} +

+ {viewCtrl.customerData?.secondaryEmail && ( +
+ +
+
+ {t("view.fields.email")} +
+
+ {viewCtrl.customerData?.secondaryEmail} +
+
+
+ )} + {viewCtrl.customerData?.secondaryMobile && ( +
+ +
+
+ {t("view.fields.mobile")} +
+
+ {viewCtrl.customerData?.secondaryMobile} +
+
+
+ )} + {viewCtrl.customerData?.secondaryPhone && ( +
+ +
+
+ {t("view.fields.phone")} +
+
+ {viewCtrl.customerData?.secondaryPhone} +
+
+
+ )} +
+ + {/* Otros Contactos */} + {(viewCtrl.customerData?.website || viewCtrl.customerData?.fax) && ( +
+

+ {t("view.sections.other_contacts")} +

+
+ {viewCtrl.customerData?.website && ( +
+ +
+
+ {t("view.fields.website")} +
+
+ + {viewCtrl.customerData?.website} + +
+
+
+ )} + {viewCtrl.customerData?.fax && ( +
+ +
+
+ {t("view.fields.fax")} +
+
+ {viewCtrl.customerData?.fax} +
+
+
+ )} +
+
+ )} +
+ + + + {/* Preferencias */} + + + + + {t("view.sections.preferences")} + + + +
+
+ +
+
+ {t("view.fields.language_code")} +
+
+ {viewCtrl.customerData?.languageCode} +
- )} -
-
-
- - {/* Preferencias */} - - - - - {t("view.sections.preferences")} - - - -
-
- -
-
- {t("view.fields.language_code")} -
-
- {viewCtrl.customerData?.languageCode} -
+
+ +
+
+ {t("view.fields.currency_code")} +
+
+ {viewCtrl.customerData?.currencyCode} +
+
-
- -
-
- {t("view.fields.currency_code")} -
-
- {viewCtrl.customerData?.currencyCode} -
-
-
-
- - + + + ); diff --git a/modules/suppliers/src/web/supplier-routes.tsx b/modules/suppliers/src/web/supplier-routes.tsx index 0c5e2e1a..2e0bf751 100644 --- a/modules/suppliers/src/web/supplier-routes.tsx +++ b/modules/suppliers/src/web/supplier-routes.tsx @@ -6,7 +6,7 @@ const SuppliersListPage = lazy(() => import("./list").then((module) => ({ default: module.SuppliersListPage })) ); const SupplierDetailPage = lazy(() => - import("./view").then((module) => ({ default: module.SupplierDetailPage })) + import("./view").then((module) => ({ default: module.SupplierViewPage })) ); const SupplierCreatePage = lazy(() => import("./create").then((module) => ({ default: module.SupplierCreatePage })) diff --git a/modules/suppliers/src/web/view/index.ts b/modules/suppliers/src/web/view/index.ts index 49bea6af..6a5125ca 100644 --- a/modules/suppliers/src/web/view/index.ts +++ b/modules/suppliers/src/web/view/index.ts @@ -1 +1 @@ -export * from "./supplier-detail-page"; +export * from "./supplier-view-page"; diff --git a/modules/suppliers/src/web/view/supplier-detail-page.tsx b/modules/suppliers/src/web/view/supplier-view-page.tsx similarity index 90% rename from modules/suppliers/src/web/view/supplier-detail-page.tsx rename to modules/suppliers/src/web/view/supplier-view-page.tsx index 91a54f19..d04e4fe4 100644 --- a/modules/suppliers/src/web/view/supplier-detail-page.tsx +++ b/modules/suppliers/src/web/view/supplier-view-page.tsx @@ -23,8 +23,13 @@ const DetailField = ({ label, value }: { label: ReactNode; value?: ReactNode })
); -export const SupplierDetailPage = () => { +const SupplierViewContent = ({ children }: { children: ReactNode }) => ( +
{children}
+); + +export const SupplierViewPage = () => { const { t } = useTranslation(); + const { id } = useParams(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); @@ -40,18 +45,20 @@ export const SupplierDetailPage = () => { return ( - { - await query.refetch(); - }, - }} - secondaryAction={{ label: t("common.actions.back"), onClick: navigateBack }} - title={t("detail.error.title")} - variant="notFound" - /> + + { + await query.refetch(); + }, + }} + secondaryAction={{ label: t("common.actions.back"), onClick: navigateBack }} + title={t("detail.error.title")} + variant="notFound" + /> + ); @@ -100,7 +107,7 @@ export const SupplierDetailPage = () => { > -
+ @@ -185,7 +192,7 @@ export const SupplierDetailPage = () => { -
+