Refactor pages to use RdxPageContent for consistent layout

- Updated IssuedInvoiceDetailPage to wrap PageErrorState in RdxPageContent.
- Modified ProformaDetailPage to include RdxPageContent around PageErrorState.
- Adjusted RecurringProformaDetailPage to utilize RdxPageContent for loading and error states.
- Refactored CustomerViewPage to implement RdxPageContent for PageErrorState and main content.
- Renamed SupplierDetailPage to SupplierViewPage and updated imports accordingly.
- Removed SupplierDetailPage and created SupplierViewPage with similar functionality.
This commit is contained in:
David Arranz 2026-09-15 12:04:27 +02:00
parent ba257fd489
commit 902f7806ed
10 changed files with 390 additions and 355 deletions

View File

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

View File

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

View File

@ -78,7 +78,7 @@ Compone header, contenido y footer y es propietario del scroll principal. No con
</RdxPageFrame>
```
`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

View File

@ -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 (
<RdxPageFrame>
<RdxPageContainer width="content">
<PageErrorState
description={ctrl.state.pageError.description}
primaryAction={ctrl.state.pageError.primaryAction}
reference={ctrl.state.pageError.reference}
secondaryAction={ctrl.state.pageError.secondaryAction}
title={ctrl.state.pageError.title}
variant={ctrl.state.pageError.variant}
/>
<RdxPageContent>
<PageErrorState
description={ctrl.state.pageError.description}
primaryAction={ctrl.state.pageError.primaryAction}
reference={ctrl.state.pageError.reference}
secondaryAction={ctrl.state.pageError.secondaryAction}
title={ctrl.state.pageError.title}
variant={ctrl.state.pageError.variant}
/>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);
@ -40,15 +42,17 @@ export const IssuedInvoiceDetailPage = () => {
return (
<RdxPageFrame>
<RdxPageContainer width="content">
<PageErrorState
description={t("pages.issued_invoices.detail.page_error.inconsistent.description")}
secondaryAction={{
label: t("pages.issued_invoices.detail.page_error.actions.back_to_list"),
onClick: ctrl.navigation.navigateBack,
}}
title={t("pages.issued_invoices.detail.page_error.unknown.title")}
variant="unknown"
/>
<RdxPageContent>
<PageErrorState
description={t("pages.issued_invoices.detail.page_error.inconsistent.description")}
secondaryAction={{
label: t("pages.issued_invoices.detail.page_error.actions.back_to_list"),
onClick: ctrl.navigation.navigateBack,
}}
title={t("pages.issued_invoices.detail.page_error.unknown.title")}
variant="unknown"
/>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);

View File

@ -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 (
<div className="p-4 sm:p-6">
<div className="mx-auto w-full max-w-3xl">
<PageErrorState
description={ctrl.state.pageError.description}
primaryAction={ctrl.state.pageError.primaryAction}
reference={ctrl.state.pageError.reference}
secondaryAction={ctrl.state.pageError.secondaryAction}
title={ctrl.state.pageError.title}
variant={ctrl.state.pageError.variant}
/>
</div>
</div>
<RdxPageFrame>
<RdxPageContainer width="content">
<RdxPageContent>
<PageErrorState
description={ctrl.state.pageError.description}
primaryAction={ctrl.state.pageError.primaryAction}
reference={ctrl.state.pageError.reference}
secondaryAction={ctrl.state.pageError.secondaryAction}
title={ctrl.state.pageError.title}
variant={ctrl.state.pageError.variant}
/>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);
}

View File

@ -34,6 +34,14 @@ const DetailField = ({ label, value }: DetailFieldProps) => (
</div>
);
const RecurringProformaDetailSkeleton = ({ label }: { label: string }) => (
<div aria-busy="true" className="space-y-6" role="status">
<div className="h-8 w-64 animate-pulse rounded bg-muted" />
<div className="h-48 animate-pulse rounded-lg bg-muted" />
<span className="sr-only">{label}</span>
</div>
);
export const RecurringProformaDetailPage = () => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
@ -48,29 +56,35 @@ export const RecurringProformaDetailPage = () => {
if (feedbackState === "loading") {
return (
<div aria-busy="true" className="space-y-6 p-4 sm:p-6" role="status">
<div className="h-8 w-64 animate-pulse rounded bg-muted" />
<div className="h-48 animate-pulse rounded-lg bg-muted" />
<span className="sr-only">{t("recurring_proformas.detail.loading")}</span>
</div>
<RdxPageFrame>
<RdxPageContainer width="wide">
<RdxPageContent>
<RecurringProformaDetailSkeleton label={t("recurring_proformas.detail.loading")} />
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);
}
if (feedbackState === "error") {
return (
<div className="p-4 sm:p-6">
<PageErrorState
description={t("recurring_proformas.detail.error.description")}
primaryAction={{
label: t("recurring_proformas.detail.error.retry"),
onClick: async () => {
await query.refetch();
},
}}
title={t("recurring_proformas.detail.error.title")}
variant="unknown"
/>
</div>
<RdxPageFrame>
<RdxPageContainer width="content">
<RdxPageContent>
<PageErrorState
description={t("recurring_proformas.detail.error.description")}
primaryAction={{
label: t("recurring_proformas.detail.error.retry"),
onClick: async () => {
await query.refetch();
},
}}
title={t("recurring_proformas.detail.error.title")}
variant="unknown"
/>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);
}

View File

@ -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 (
<RdxPageFrame>
<RdxPageContainer width="content">
<PageErrorState
description={ctrl.pageError.description}
primaryAction={
ctrl.pageError.canRetry
? {
label: ctrl.isRetrying
? t("view.page_error.actions.retrying")
: t("view.page_error.actions.retry"),
onClick: ctrl.retryView,
isLoading: ctrl.isRetrying,
}
: ctrl.pageError.variant === "forbidden"
<RdxPageContent>
<PageErrorState
description={ctrl.pageError.description}
primaryAction={
ctrl.pageError.canRetry
? {
label: t("view.page_error.actions.go_home"),
onClick: ctrl.goHome,
label: ctrl.isRetrying
? t("view.page_error.actions.retrying")
: t("view.page_error.actions.retry"),
onClick: ctrl.retryView,
isLoading: ctrl.isRetrying,
}
: {
: ctrl.pageError.variant === "forbidden"
? {
label: t("view.page_error.actions.go_home"),
onClick: ctrl.goHome,
}
: {
label: t("view.page_error.actions.back_to_list"),
onClick: ctrl.goBackToList,
}
}
reference={ctrl.pageError.reference}
secondaryAction={
ctrl.pageError.canRetry
? {
label: t("view.page_error.actions.back_to_list"),
onClick: ctrl.goBackToList,
}
}
reference={ctrl.pageError.reference}
secondaryAction={
ctrl.pageError.canRetry
? {
label: t("view.page_error.actions.back_to_list"),
onClick: ctrl.goBackToList,
}
: undefined
}
title={ctrl.pageError.title}
variant={ctrl.pageError.variant}
/>
: undefined
}
title={ctrl.pageError.title}
variant={ctrl.pageError.variant}
/>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);
@ -98,282 +100,288 @@ export const CustomerViewPage = () => {
/>
}
>
<RdxPageContainer className="space-y-6" width="content">
{/* Información Básica */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<FileText className="size-5 text-primary" />
{t("view.sections.basic_info")}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<dt className="text-sm font-medium text-muted-foreground">{t("view.fields.name")}</dt>
<dd className="mt-1 text-base text-foreground" id="customer-view-name">
{viewCtrl.customerData?.name}
</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.reference")}
</dt>
<dd className="mt-1 font-mono text-base text-foreground">
{viewCtrl.customerData?.reference}
</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.legal_record")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.legalRecord}
</dd>
</div>
</CardContent>
</Card>
{/* Dirección */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<MapPin className="size-5 text-primary" />
{t("view.sections.address")}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.street")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.street}
{viewCtrl.customerData?.street2 && (
<>
<br />
{viewCtrl.customerData?.street2}
</>
)}
</dd>
</div>
<div className="grid grid-cols-2 gap-4">
<RdxPageContainer width="content">
<RdxPageContent>
{/* Información Básica */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<FileText className="size-5 text-primary" />
{t("view.sections.basic_info")}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.city")}
{t("view.fields.name")}
</dt>
<dd className="mt-1 text-base text-foreground">{viewCtrl.customerData?.city}</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.postal_code")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.postalCode}
</dd>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.province")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.province}
<dd className="mt-1 text-base text-foreground" id="customer-view-name">
{viewCtrl.customerData?.name}
</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.country")}
{t("view.fields.reference")}
</dt>
<dd className="mt-1 text-base text-foreground">{viewCtrl.customerData?.country}</dd>
<dd className="mt-1 font-mono text-base text-foreground">
{viewCtrl.customerData?.reference}
</dd>
</div>
</div>
</CardContent>
</Card>
{/* Información de Contacto */}
<Card className="md:col-span-2">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Mail className="size-5 text-primary" />
{t("view.sections.contact_info")}
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-6 md:grid-cols-2">
{/* Contacto Principal */}
<div className="space-y-4">
<h3 className="font-semibold text-foreground">
{t("view.sections.primary_contact")}
</h3>
{viewCtrl.customerData?.primaryEmail && (
<div className="flex items-start gap-3">
<Mail className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.email")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.primaryEmail}
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.primaryMobile && (
<div className="flex items-start gap-3">
<Smartphone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.mobile")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.primaryMobile}
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.primaryPhone && (
<div className="flex items-start gap-3">
<Phone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.phone")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.primaryPhone}
</dd>
</div>
</div>
)}
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.legal_record")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.legalRecord}
</dd>
</div>
</CardContent>
</Card>
{/* Contacto Secundario */}
<div className="space-y-4">
<h3 className="font-semibold text-foreground">
{t("view.sections.secondary_contact")}
</h3>
{viewCtrl.customerData?.secondaryEmail && (
<div className="flex items-start gap-3">
<Mail className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.email")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.secondaryEmail}
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.secondaryMobile && (
<div className="flex items-start gap-3">
<Smartphone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.mobile")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.secondaryMobile}
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.secondaryPhone && (
<div className="flex items-start gap-3">
<Phone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.phone")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.secondaryPhone}
</dd>
</div>
</div>
)}
{/* Dirección */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<MapPin className="size-5 text-primary" />
{t("view.sections.address")}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.street")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.street}
{viewCtrl.customerData?.street2 && (
<>
<br />
{viewCtrl.customerData?.street2}
</>
)}
</dd>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.city")}
</dt>
<dd className="mt-1 text-base text-foreground">{viewCtrl.customerData?.city}</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.postal_code")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.postalCode}
</dd>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.province")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.province}
</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.country")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.country}
</dd>
</div>
</div>
</CardContent>
</Card>
{/* Otros Contactos */}
{(viewCtrl.customerData?.website || viewCtrl.customerData?.fax) && (
<div className="space-y-4 md:col-span-2">
{/* Información de Contacto */}
<Card className="md:col-span-2">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Mail className="size-5 text-primary" />
{t("view.sections.contact_info")}
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-6 md:grid-cols-2">
{/* Contacto Principal */}
<div className="space-y-4">
<h3 className="font-semibold text-foreground">
{t("view.sections.other_contacts")}
{t("view.sections.primary_contact")}
</h3>
<div className="grid gap-4 md:grid-cols-2">
{viewCtrl.customerData?.website && (
<div className="flex items-start gap-3">
<Globe className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.website")}
</dt>
<dd className="mt-1 text-base text-primary hover:underline">
<a
href={viewCtrl.customerData?.website}
rel="noopener noreferrer"
target="_blank"
>
{viewCtrl.customerData?.website}
</a>
</dd>
</div>
{viewCtrl.customerData?.primaryEmail && (
<div className="flex items-start gap-3">
<Mail className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.email")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.primaryEmail}
</dd>
</div>
)}
{viewCtrl.customerData?.fax && (
<div className="flex items-start gap-3">
<Phone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.fax")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.fax}
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.primaryMobile && (
<div className="flex items-start gap-3">
<Smartphone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.mobile")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.primaryMobile}
</dd>
</div>
)}
</div>
)}
{viewCtrl.customerData?.primaryPhone && (
<div className="flex items-start gap-3">
<Phone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.phone")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.primaryPhone}
</dd>
</div>
</div>
)}
</div>
{/* Contacto Secundario */}
<div className="space-y-4">
<h3 className="font-semibold text-foreground">
{t("view.sections.secondary_contact")}
</h3>
{viewCtrl.customerData?.secondaryEmail && (
<div className="flex items-start gap-3">
<Mail className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.email")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.secondaryEmail}
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.secondaryMobile && (
<div className="flex items-start gap-3">
<Smartphone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.mobile")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.secondaryMobile}
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.secondaryPhone && (
<div className="flex items-start gap-3">
<Phone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.phone")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.secondaryPhone}
</dd>
</div>
</div>
)}
</div>
{/* Otros Contactos */}
{(viewCtrl.customerData?.website || viewCtrl.customerData?.fax) && (
<div className="space-y-4 md:col-span-2">
<h3 className="font-semibold text-foreground">
{t("view.sections.other_contacts")}
</h3>
<div className="grid gap-4 md:grid-cols-2">
{viewCtrl.customerData?.website && (
<div className="flex items-start gap-3">
<Globe className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.website")}
</dt>
<dd className="mt-1 text-base text-primary hover:underline">
<a
href={viewCtrl.customerData?.website}
rel="noopener noreferrer"
target="_blank"
>
{viewCtrl.customerData?.website}
</a>
</dd>
</div>
</div>
)}
{viewCtrl.customerData?.fax && (
<div className="flex items-start gap-3">
<Phone className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.fax")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.fax}
</dd>
</div>
</div>
)}
</div>
</div>
)}
</div>
</CardContent>
</Card>
{/* Preferencias */}
<Card className="md:col-span-2">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Languages className="size-5 text-primary" />
{t("view.sections.preferences")}
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-6 md:grid-cols-2">
<div className="flex items-start gap-3">
<Languages className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.language_code")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.languageCode}
</dd>
</div>
</div>
)}
</div>
</CardContent>
</Card>
{/* Preferencias */}
<Card className="md:col-span-2">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Languages className="size-5 text-primary" />
{t("view.sections.preferences")}
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-6 md:grid-cols-2">
<div className="flex items-start gap-3">
<Languages className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.language_code")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.languageCode}
</dd>
<div className="flex items-start gap-3">
<Banknote className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.currency_code")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.currencyCode}
</dd>
</div>
</div>
</div>
<div className="flex items-start gap-3">
<Banknote className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<dt className="text-sm font-medium text-muted-foreground">
{t("view.fields.currency_code")}
</dt>
<dd className="mt-1 text-base text-foreground">
{viewCtrl.customerData?.currencyCode}
</dd>
</div>
</div>
</div>
</CardContent>
</Card>
</CardContent>
</Card>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);

View File

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

View File

@ -1 +1 @@
export * from "./supplier-detail-page";
export * from "./supplier-view-page";

View File

@ -23,8 +23,13 @@ const DetailField = ({ label, value }: { label: ReactNode; value?: ReactNode })
</div>
);
export const SupplierDetailPage = () => {
const SupplierViewContent = ({ children }: { children: ReactNode }) => (
<div className="grid gap-4 lg:grid-cols-2">{children}</div>
);
export const SupplierViewPage = () => {
const { t } = useTranslation();
const { id } = useParams();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
@ -40,18 +45,20 @@ export const SupplierDetailPage = () => {
return (
<RdxPageFrame>
<RdxPageContainer width="content">
<PageErrorState
description={t("detail.error.description")}
primaryAction={{
label: t("common.actions.retry"),
onClick: async () => {
await query.refetch();
},
}}
secondaryAction={{ label: t("common.actions.back"), onClick: navigateBack }}
title={t("detail.error.title")}
variant="notFound"
/>
<RdxPageContent>
<PageErrorState
description={t("detail.error.description")}
primaryAction={{
label: t("common.actions.retry"),
onClick: async () => {
await query.refetch();
},
}}
secondaryAction={{ label: t("common.actions.back"), onClick: navigateBack }}
title={t("detail.error.title")}
variant="notFound"
/>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
);
@ -100,7 +107,7 @@ export const SupplierDetailPage = () => {
>
<RdxPageContainer width="full">
<RdxPageContent spacing="default">
<div className="grid gap-4 lg:grid-cols-2">
<SupplierViewContent>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
@ -185,7 +192,7 @@ export const SupplierDetailPage = () => {
</dl>
</CardContent>
</Card>
</div>
</SupplierViewContent>
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>