refactor: replace PageFormHeader with RdxEditorHeader in customer and supplier pages
- Updated CustomerViewHeader to use RdxEditorHeader for a more consistent header layout. - Refactored CustomerViewPage to utilize RdxPageFrame and RdxPageContainer for improved structure. - Replaced PageFormHeader with RdxEditorHeader in AccountPage for uniformity. - Adjusted SupplierCreatePage and SupplierUpdatePage to use RdxPageFrame and RdxPageContainer for layout consistency. - Removed SupplierLayout component as it was no longer necessary. - Enhanced SupplierDetailPage to utilize RdxPageFrame for better layout management. - Introduced RdxEditorHeader component to encapsulate header functionality for editor pages. - Updated tests to cover new RdxEditorHeader functionality and removed obsolete tests related to RdxPageHeader.
This commit is contained in:
parent
85d2f11bde
commit
2f8122ca77
@ -1,5 +1,10 @@
|
||||
import { PageErrorState } from "@erp/core/components";
|
||||
import { RdxPageContainer, RdxPageHeader, RdxPageStack } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
RdxPageContainer,
|
||||
RdxPageFrame,
|
||||
RdxPageHeader,
|
||||
RdxPageStack,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import { Skeleton } from "@repo/shadcn-ui/components";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
||||
@ -28,6 +33,7 @@ const HomeKpisSkeleton = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** Renderiza el dashboard dentro del frame estándar de página. */
|
||||
export const HomePage = () => {
|
||||
const { t } = useAppTranslation();
|
||||
const navigate = useNavigate();
|
||||
@ -35,8 +41,8 @@ export const HomePage = () => {
|
||||
|
||||
if (ctrl.hasBlockingError && ctrl.pageError) {
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={ctrl.pageError.description}
|
||||
primaryAction={{
|
||||
@ -50,12 +56,13 @@ export const HomePage = () => {
|
||||
title={ctrl.pageError.title}
|
||||
variant="server"
|
||||
/>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<RdxPageHeader
|
||||
@ -108,5 +115,6 @@ export const HomePage = () => {
|
||||
</p>
|
||||
</RdxPageStack>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { RdxPageContainer, RdxPageHeader, RdxPageStack } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
RdxPageContainer,
|
||||
RdxPageFrame,
|
||||
RdxPageHeader,
|
||||
RdxPageStack,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import { Outlet } from "react-router-dom";
|
||||
|
||||
import type { SettingsNavigationSection } from "@/settings/build-settings-navigation";
|
||||
@ -11,10 +16,14 @@ interface SettingsLayoutProps {
|
||||
sections: SettingsNavigationSection[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compone la navegación y el contenido de configuración dentro del frame estándar de página.
|
||||
*/
|
||||
export const SettingsLayout = ({ sections }: SettingsLayoutProps) => {
|
||||
const { t } = useAppTranslation();
|
||||
|
||||
return (
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<RdxPageHeader
|
||||
@ -33,5 +42,6 @@ export const SettingsLayout = ({ sections }: SettingsLayoutProps) => {
|
||||
</div>
|
||||
</RdxPageStack>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { ErrorAlert, PageErrorState, SimpleSearchInput } from "@erp/core/components";
|
||||
import { RdxPageContainer, RdxPageHeader, RdxPageStack } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
RdxPageContainer,
|
||||
RdxPageFrame,
|
||||
RdxPageHeader,
|
||||
RdxPageStack,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Button,
|
||||
Spinner,
|
||||
@ -18,6 +23,7 @@ import { AuditFilterChips, AuditFiltersPanel } from "./audit-filters";
|
||||
import { AuditGrid } from "./audit-grid";
|
||||
import { useAuditGridColumns } from "./use-audit-grid-columns";
|
||||
|
||||
/** Compone el listado de auditoría dentro del frame estándar de página. */
|
||||
export const AuditPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const controller = useAuditPageController();
|
||||
@ -28,7 +34,8 @@ export const AuditPage = () => {
|
||||
|
||||
if (controller.isError && !controller.hasUsableData) {
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={t("audit.error.load")}
|
||||
primaryAction={{
|
||||
@ -40,10 +47,12 @@ export const AuditPage = () => {
|
||||
variant="unknown"
|
||||
/>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<RdxPageHeader description={t("audit.description")} title={t("audit.title")} />
|
||||
@ -90,6 +99,7 @@ export const AuditPage = () => {
|
||||
<AuditEntrySheet entryId={controller.entry} onClose={controller.closeEntry} />
|
||||
</RdxPageStack>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export * from './page-form-header';
|
||||
export * from './page-keyboard-shortcuts-button';
|
||||
export * from "./page-keyboard-shortcuts-button";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { PageFormHeader } from "@erp/core/components";
|
||||
import { RdxEditorHeader } from "@repo/rdx-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
@ -26,17 +26,17 @@ export const IssuedInvoicePageHeader = ({
|
||||
contentClassName,
|
||||
}: IssuedInvoicePageHeaderProps) => {
|
||||
return (
|
||||
<PageFormHeader
|
||||
<RdxEditorHeader
|
||||
actions={actions}
|
||||
backLabel={backLabel}
|
||||
className={cn("shrink-0", className)}
|
||||
contentClassName={contentClassName}
|
||||
onBack={onBack}
|
||||
showStatus={showStatus}
|
||||
statusLabel={statusLabel}
|
||||
status={statusLabel}
|
||||
title={title}
|
||||
>
|
||||
{children}
|
||||
</PageFormHeader>
|
||||
</RdxEditorHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { RdxPageHeader } from "@repo/rdx-ui/components";
|
||||
import { RdxEditorHeader } from "@repo/rdx-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
@ -26,18 +26,17 @@ export const ProformaPageHeader = ({
|
||||
contentClassName,
|
||||
}: ProformaPageHeaderProps) => {
|
||||
return (
|
||||
<RdxPageHeader
|
||||
<RdxEditorHeader
|
||||
actions={actions}
|
||||
backLabel={backLabel}
|
||||
className={cn("shrink-0", className)}
|
||||
contentClassName={contentClassName}
|
||||
onBack={onBack}
|
||||
showStatus={showStatus}
|
||||
statusLabel={statusLabel}
|
||||
sticky
|
||||
status={statusLabel}
|
||||
title={title}
|
||||
>
|
||||
{children}
|
||||
</RdxPageHeader>
|
||||
</RdxEditorHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { PageFormHeader } from "@erp/core/components";
|
||||
import { useHookForm } from "@erp/core/hooks";
|
||||
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
RdxEditorHeader,
|
||||
RdxEditorLayout,
|
||||
RdxPageContainer,
|
||||
RdxPageFrame,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@ -53,7 +57,7 @@ export const RecurringProformaCreatePage = () => {
|
||||
<RdxPageFrame
|
||||
contentClassName="bg-muted/20"
|
||||
header={
|
||||
<PageFormHeader
|
||||
<RdxEditorHeader
|
||||
actions={
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { PageFormHeader, PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import { PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import {
|
||||
CancelActionButton,
|
||||
FormActionsBar,
|
||||
type FormSecondaryAction,
|
||||
FormSecondaryActionsMenu,
|
||||
RdxEditorHeader,
|
||||
RhfSubmitActionButton,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import type { ReactNode } from "react";
|
||||
@ -74,7 +75,7 @@ export const CustomerCreateHeader = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<PageFormHeader
|
||||
<RdxEditorHeader
|
||||
actions={
|
||||
<FormActionsBar align="end" reverseOnMobile={false}>
|
||||
<PageKeyboardShortcutsButton
|
||||
@ -113,10 +114,10 @@ export const CustomerCreateHeader = ({
|
||||
className={className}
|
||||
onBack={onCancel}
|
||||
showStatus={hasChanges}
|
||||
statusLabel={labels.modified}
|
||||
status={labels.modified}
|
||||
title={labels.title}
|
||||
>
|
||||
{children}
|
||||
</PageFormHeader>
|
||||
</RdxEditorHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
||||
import { BackHistoryButton, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
|
||||
import { useTranslation } from "../../../i18n";
|
||||
@ -6,8 +6,8 @@ import { useTranslation } from "../../../i18n";
|
||||
export const CustomerEditorSkeleton = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<AppContent>
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<div className="flex items-center justify-between">
|
||||
<div aria-hidden="true" className="space-y-2">
|
||||
<div className="h-7 w-64 rounded-md bg-muted animate-pulse" />
|
||||
@ -26,7 +26,7 @@ export const CustomerEditorSkeleton = () => {
|
||||
<div className="h-28 w-full rounded-md bg-muted animate-pulse" />
|
||||
</div>
|
||||
<span className="sr-only">{t("update.loading")}</span>
|
||||
</AppContent>
|
||||
</>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ErrorAlert } from "@erp/core/components";
|
||||
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||
import { RdxEditorPageLayout } from "@repo/rdx-ui/components";
|
||||
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import { FormProvider } from "react-hook-form";
|
||||
|
||||
import { useTranslation } from "../../../i18n";
|
||||
@ -19,9 +19,11 @@ export const CustomerCreatePage = () => {
|
||||
const handleCancel = () => navigateBack();
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||
<FormProvider {...createCtrl.form}>
|
||||
<UnsavedChangesProvider isDirty={createCtrl.form.formState.isDirty}>
|
||||
<RdxPageFrame
|
||||
contentClassName="bg-muted/20"
|
||||
header={
|
||||
<CustomerCreateHeader
|
||||
formId={createCtrl.formId}
|
||||
hasChanges={createCtrl.form.formState.isDirty}
|
||||
@ -42,9 +44,10 @@ export const CustomerCreatePage = () => {
|
||||
//onDuplicate={handleDuplicate}
|
||||
//onExportPdf={handleExportPdf}
|
||||
/>
|
||||
|
||||
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||
<RdxEditorPageLayout width="simple">
|
||||
}
|
||||
>
|
||||
<RdxPageContainer width="content">
|
||||
<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="flex min-w-0 flex-col gap-6">
|
||||
{createCtrl.isCreateError ? (
|
||||
@ -65,10 +68,10 @@ export const CustomerCreatePage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RdxEditorPageLayout>
|
||||
</div>
|
||||
</RdxEditorLayout>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
</UnsavedChangesProvider>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,10 +2,6 @@ import type { ModuleClientParams } from "@erp/core/client";
|
||||
import { lazy } from "react";
|
||||
import { Outlet, type RouteObject } from "react-router-dom";
|
||||
|
||||
// Lazy load components
|
||||
const CustomerLayout = lazy(() =>
|
||||
import("./shared/ui").then((m) => ({ default: m.CustomerLayout }))
|
||||
);
|
||||
const CustomersListPage = lazy(() =>
|
||||
import("./list").then((m) => ({ default: m.ListCustomersPage }))
|
||||
);
|
||||
@ -28,11 +24,7 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
|
||||
layout: "app-sidebar",
|
||||
protected: true,
|
||||
},
|
||||
element: (
|
||||
<CustomerLayout>
|
||||
<Outlet context={params} />
|
||||
</CustomerLayout>
|
||||
),
|
||||
element: <Outlet context={params} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@ -51,11 +43,7 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
|
||||
layout: "app-fullscreen",
|
||||
protected: true,
|
||||
},
|
||||
element: (
|
||||
<CustomerLayout>
|
||||
<CustomerCreatePage />
|
||||
</CustomerLayout>
|
||||
),
|
||||
element: <CustomerCreatePage />,
|
||||
},
|
||||
|
||||
{
|
||||
@ -64,11 +52,7 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
|
||||
layout: "app-fullscreen",
|
||||
protected: true,
|
||||
},
|
||||
element: (
|
||||
<CustomerLayout>
|
||||
<CustomerUpdatePage />
|
||||
</CustomerLayout>
|
||||
),
|
||||
element: <CustomerUpdatePage />,
|
||||
},
|
||||
|
||||
{
|
||||
@ -77,11 +61,7 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
|
||||
layout: "app-fullscreen",
|
||||
protected: true,
|
||||
},
|
||||
element: (
|
||||
<CustomerLayout>
|
||||
<CustomerViewPage />
|
||||
</CustomerLayout>
|
||||
),
|
||||
element: <CustomerViewPage />,
|
||||
},
|
||||
|
||||
/*
|
||||
|
||||
@ -2,6 +2,7 @@ import { ErrorAlert } from "@erp/core/components";
|
||||
import {
|
||||
RdxPageActions,
|
||||
RdxPageContainer,
|
||||
RdxPageFrame,
|
||||
RdxPageHeader,
|
||||
RdxPageStack,
|
||||
} from "@repo/rdx-ui/components";
|
||||
@ -30,6 +31,7 @@ export const ListCustomersPageView = ({
|
||||
dialogs,
|
||||
}: ListCustomersPageViewProps) => {
|
||||
return (
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<RdxPageHeader
|
||||
@ -47,5 +49,6 @@ export const ListCustomersPageView = ({
|
||||
|
||||
{dialogs}
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { PageErrorState, SimpleSearchInput } from "@erp/core/components";
|
||||
import { useReturnToNavigation } from "@erp/core/hooks";
|
||||
import { RdxPageContainer } from "@repo/rdx-ui/components";
|
||||
import { RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Button,
|
||||
ResizableHandle,
|
||||
@ -14,6 +14,7 @@ import { createSearchParams, useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "../../../i18n";
|
||||
import { useListCustomersPageController } from "../../controllers";
|
||||
import { CustomerSummaryPanel, CustomersGrid, useCustomersGridColumns } from "../blocks";
|
||||
|
||||
import { ListCustomersPageView } from "./list-customers-page-view";
|
||||
|
||||
export const ListCustomersPage = () => {
|
||||
@ -90,8 +91,8 @@ export const ListCustomersPage = () => {
|
||||
|
||||
if (pageError) {
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={pageError.description}
|
||||
primaryAction={
|
||||
@ -110,8 +111,8 @@ export const ListCustomersPage = () => {
|
||||
title={pageError.title}
|
||||
variant={pageError.variant}
|
||||
/>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
export const CustomerLayout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<div className="flex flex-col h-full w-full" id="customers">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export * from "./customer-layout";
|
||||
@ -1,9 +1,10 @@
|
||||
import { PageFormHeader, PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import { PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import {
|
||||
CancelActionButton,
|
||||
FormActionsBar,
|
||||
type FormSecondaryAction,
|
||||
FormSecondaryActionsMenu,
|
||||
RdxEditorHeader,
|
||||
RhfSubmitActionButton,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import type { ReactNode } from "react";
|
||||
@ -75,7 +76,7 @@ export const CustomerUpdateHeader = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<PageFormHeader
|
||||
<RdxEditorHeader
|
||||
actions={
|
||||
<FormActionsBar align="end" reverseOnMobile={false}>
|
||||
<PageKeyboardShortcutsButton
|
||||
@ -114,10 +115,10 @@ export const CustomerUpdateHeader = ({
|
||||
className={className}
|
||||
onBack={onCancel}
|
||||
showStatus={hasChanges}
|
||||
statusLabel={labels.modified}
|
||||
status={labels.modified}
|
||||
title={labels.title}
|
||||
>
|
||||
{children}
|
||||
</PageFormHeader>
|
||||
</RdxEditorHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
||||
import { BackHistoryButton, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
|
||||
import { useTranslation } from "../../../i18n";
|
||||
@ -6,8 +6,8 @@ import { useTranslation } from "../../../i18n";
|
||||
export const CustomerEditorSkeleton = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<AppContent>
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<div className="flex items-center justify-between">
|
||||
<div aria-hidden="true" className="space-y-2">
|
||||
<div className="h-7 w-64 rounded-md bg-muted animate-pulse" />
|
||||
@ -26,7 +26,7 @@ export const CustomerEditorSkeleton = () => {
|
||||
<div className="h-28 w-full rounded-md bg-muted animate-pulse" />
|
||||
</div>
|
||||
<span className="sr-only">{t("update.loading")}</span>
|
||||
</AppContent>
|
||||
</>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ErrorAlert, PageErrorState } from "@erp/core/components";
|
||||
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||
import { RdxEditorPageLayout } from "@repo/rdx-ui/components";
|
||||
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import { FormProvider } from "react-hook-form";
|
||||
|
||||
import { useTranslation } from "../../../i18n";
|
||||
@ -26,8 +26,8 @@ export const CustomerUpdatePage = () => {
|
||||
|
||||
if (ctrl.pageError) {
|
||||
return (
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={ctrl.pageError.description}
|
||||
primaryAction={
|
||||
@ -61,15 +61,17 @@ export const CustomerUpdatePage = () => {
|
||||
title={ctrl.pageError.title}
|
||||
variant={ctrl.pageError.variant}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||
<FormProvider {...updateCtrl.form}>
|
||||
<UnsavedChangesProvider isDirty={updateCtrl.form.formState.isDirty}>
|
||||
<RdxPageFrame
|
||||
contentClassName="bg-muted/20"
|
||||
header={
|
||||
<CustomerUpdateHeader
|
||||
formId={updateCtrl.formId}
|
||||
hasChanges={updateCtrl.form.formState.isDirty}
|
||||
@ -91,9 +93,10 @@ export const CustomerUpdatePage = () => {
|
||||
//onDuplicate={handleDuplicate}
|
||||
//onExportPdf={handleExportPdf}
|
||||
/>
|
||||
|
||||
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||
<RdxEditorPageLayout width="simple">
|
||||
}
|
||||
>
|
||||
<RdxPageContainer width="content">
|
||||
<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="flex min-w-0 flex-col gap-6">
|
||||
{updateCtrl.isUpdateError ? (
|
||||
@ -121,10 +124,10 @@ export const CustomerUpdatePage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RdxEditorPageLayout>
|
||||
</div>
|
||||
</RdxEditorLayout>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
</UnsavedChangesProvider>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { PageFormHeader, PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import { PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import {
|
||||
CancelActionButton,
|
||||
FormActionsBar,
|
||||
type FormSecondaryAction,
|
||||
FormSecondaryActionsMenu,
|
||||
RdxEditorHeader,
|
||||
} from "@repo/rdx-ui/components";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
@ -70,7 +71,7 @@ export const CustomerViewHeader = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<PageFormHeader
|
||||
<RdxEditorHeader
|
||||
actions={
|
||||
<FormActionsBar align="end" reverseOnMobile={false}>
|
||||
<PageKeyboardShortcutsButton
|
||||
@ -101,10 +102,10 @@ export const CustomerViewHeader = ({
|
||||
className={className}
|
||||
onBack={onCancel}
|
||||
showStatus={hasChanges}
|
||||
statusLabel={labels.modified}
|
||||
status={labels.modified}
|
||||
title={labels.title}
|
||||
>
|
||||
{children}
|
||||
</PageFormHeader>
|
||||
</RdxEditorHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { PageErrorState } from "@erp/core/components";
|
||||
import { useReturnToNavigation } from "@erp/core/hooks";
|
||||
import { RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components";
|
||||
import {
|
||||
Banknote,
|
||||
@ -35,8 +36,8 @@ export const CustomerViewPage = () => {
|
||||
|
||||
if (ctrl.pageError) {
|
||||
return (
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={ctrl.pageError.description}
|
||||
primaryAction={
|
||||
@ -70,13 +71,14 @@ export const CustomerViewPage = () => {
|
||||
title={ctrl.pageError.title}
|
||||
variant={ctrl.pageError.variant}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||
<RdxPageFrame
|
||||
header={
|
||||
<CustomerViewHeader
|
||||
labels={{
|
||||
title: "Ver cliente",
|
||||
@ -94,8 +96,9 @@ export const CustomerViewPage = () => {
|
||||
//onDuplicate={handleDuplicate}
|
||||
//onExportPdf={handleExportPdf}
|
||||
/>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
}
|
||||
>
|
||||
<RdxPageContainer className="space-y-6" width="content">
|
||||
{/* Información Básica */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@ -369,7 +372,7 @@ export const CustomerViewPage = () => {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { ErrorAlert, PageErrorState, PageFormHeader } from "@erp/core/components";
|
||||
import { ErrorAlert, PageErrorState } from "@erp/core/components";
|
||||
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||
import {
|
||||
CancelActionButton,
|
||||
RdxEditorActionBar,
|
||||
RdxEditorHeader,
|
||||
RdxEditorPageLayout,
|
||||
RdxPanel,
|
||||
RhfSubmitActionButton,
|
||||
@ -252,7 +253,7 @@ export const AccountPage = () => {
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||
<PageFormHeader
|
||||
<RdxEditorHeader
|
||||
backLabel={t("account.header.back")}
|
||||
onBack={navigateBack}
|
||||
title={t("account.title")}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ErrorAlert } from "@erp/core/components";
|
||||
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||
import { RdxEditorPageLayout } from "@repo/rdx-ui/components";
|
||||
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import { FormProvider } from "react-hook-form";
|
||||
import { createSearchParams, useNavigate, useSearchParams } from "react-router-dom";
|
||||
|
||||
@ -24,9 +24,11 @@ export const SupplierCreatePage = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||
<FormProvider {...controller.form}>
|
||||
<UnsavedChangesProvider isDirty={controller.form.formState.isDirty}>
|
||||
<RdxPageFrame
|
||||
contentClassName="bg-muted/20"
|
||||
header={
|
||||
<SupplierEditorHeader
|
||||
formId={controller.formId}
|
||||
hasChanges={controller.form.formState.isDirty}
|
||||
@ -34,8 +36,10 @@ export const SupplierCreatePage = () => {
|
||||
onCancel={navigateBack}
|
||||
title={t("create.title")}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||
<RdxEditorPageLayout width="simple">
|
||||
}
|
||||
>
|
||||
<RdxPageContainer width="content">
|
||||
<RdxEditorLayout>
|
||||
<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">
|
||||
{controller.mutation.isError ? (
|
||||
@ -51,10 +55,10 @@ export const SupplierCreatePage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RdxEditorPageLayout>
|
||||
</div>
|
||||
</RdxEditorLayout>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
</UnsavedChangesProvider>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { PageErrorState, SimpleSearchInput } from "@erp/core/components";
|
||||
import {
|
||||
RdxPageActions,
|
||||
RdxPageContainer,
|
||||
RdxPageFrame,
|
||||
RdxPageHeader,
|
||||
RdxPageStack,
|
||||
} from "@repo/rdx-ui/components";
|
||||
@ -36,7 +37,8 @@ export const SuppliersListPage = () => {
|
||||
|
||||
if (controller.isError && controller.data.items.length === 0) {
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={t("list.error.description")}
|
||||
primaryAction={{
|
||||
@ -49,10 +51,12 @@ export const SuppliersListPage = () => {
|
||||
variant="unknown"
|
||||
/>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<RdxPageHeader
|
||||
@ -94,5 +98,6 @@ export const SuppliersListPage = () => {
|
||||
/>
|
||||
</RdxPageStack>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
export * from "./supplier-editor-form";
|
||||
export * from "./supplier-editor-header";
|
||||
export * from "./supplier-editor-skeleton";
|
||||
export * from "./supplier-layout";
|
||||
export * from "./supplier-status-badge";
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { PageFormHeader, PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import { CancelActionButton, FormActionsBar, RhfSubmitActionButton } from "@repo/rdx-ui/components";
|
||||
import { PageKeyboardShortcutsButton } from "@erp/core/components";
|
||||
import {
|
||||
CancelActionButton,
|
||||
FormActionsBar,
|
||||
RdxEditorHeader,
|
||||
RhfSubmitActionButton,
|
||||
} from "@repo/rdx-ui/components";
|
||||
|
||||
import { useTranslation } from "../../i18n";
|
||||
|
||||
@ -19,7 +24,7 @@ export const SupplierEditorHeader = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<PageFormHeader
|
||||
<RdxEditorHeader
|
||||
actions={
|
||||
<FormActionsBar align="end" reverseOnMobile={false}>
|
||||
<PageKeyboardShortcutsButton
|
||||
@ -49,7 +54,7 @@ export const SupplierEditorHeader = ({
|
||||
backLabel={t("common.actions.back")}
|
||||
onBack={onCancel}
|
||||
showStatus={hasChanges}
|
||||
statusLabel={t("form.modified")}
|
||||
status={t("form.modified")}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AppContent } from "@repo/rdx-ui/components";
|
||||
import { RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
|
||||
import { useTranslation } from "../../i18n";
|
||||
|
||||
@ -17,7 +17,8 @@ export const SupplierEditorSkeleton = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<AppContent>
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<div aria-hidden className="space-y-6">
|
||||
<div className="h-10 w-64 animate-pulse rounded-md bg-muted" />
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
@ -27,6 +28,7 @@ export const SupplierEditorSkeleton = () => {
|
||||
</div>
|
||||
</div>
|
||||
<span className="sr-only">{t("common.loading")}</span>
|
||||
</AppContent>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
export const SupplierLayout = ({ children }: PropsWithChildren) => (
|
||||
<div className="flex h-full w-full flex-col" id="suppliers">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@ -2,9 +2,6 @@ import type { ModuleClientParams } from "@erp/core/client";
|
||||
import { lazy } from "react";
|
||||
import { Outlet, type RouteObject } from "react-router-dom";
|
||||
|
||||
const SupplierLayout = lazy(() =>
|
||||
import("./shared/ui").then((module) => ({ default: module.SupplierLayout }))
|
||||
);
|
||||
const SuppliersListPage = lazy(() =>
|
||||
import("./list").then((module) => ({ default: module.SuppliersListPage }))
|
||||
);
|
||||
@ -22,38 +19,22 @@ export const SupplierRoutes = (params: ModuleClientParams): RouteObject[] => [
|
||||
{
|
||||
path: "suppliers",
|
||||
handle: { layout: "app-sidebar", protected: true },
|
||||
element: (
|
||||
<SupplierLayout>
|
||||
<Outlet context={params} />
|
||||
</SupplierLayout>
|
||||
),
|
||||
element: <Outlet context={params} />,
|
||||
children: [{ index: true, element: <SuppliersListPage /> }],
|
||||
},
|
||||
{
|
||||
path: "suppliers/create",
|
||||
handle: { layout: "app-fullscreen", protected: true },
|
||||
element: (
|
||||
<SupplierLayout>
|
||||
<SupplierCreatePage />
|
||||
</SupplierLayout>
|
||||
),
|
||||
element: <SupplierCreatePage />,
|
||||
},
|
||||
{
|
||||
path: "suppliers/:id/edit",
|
||||
handle: { layout: "app-fullscreen", protected: true },
|
||||
element: (
|
||||
<SupplierLayout>
|
||||
<SupplierUpdatePage />
|
||||
</SupplierLayout>
|
||||
),
|
||||
element: <SupplierUpdatePage />,
|
||||
},
|
||||
{
|
||||
path: "suppliers/:id",
|
||||
handle: { layout: "app-fullscreen", protected: true },
|
||||
element: (
|
||||
<SupplierLayout>
|
||||
<SupplierDetailPage />
|
||||
</SupplierLayout>
|
||||
),
|
||||
element: <SupplierDetailPage />,
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ErrorAlert, PageErrorState } from "@erp/core/components";
|
||||
import { UnsavedChangesProvider, useReturnToNavigation } from "@erp/core/hooks";
|
||||
import { RdxEditorPageLayout, RdxPageContainer } from "@repo/rdx-ui/components";
|
||||
import { RdxEditorLayout, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
||||
import { FormProvider } from "react-hook-form";
|
||||
import { createSearchParams, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
|
||||
@ -28,7 +28,8 @@ export const SupplierUpdatePage = () => {
|
||||
|
||||
if (controller.query.isError || !controller.query.data) {
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={t("detail.error.description")}
|
||||
primaryAction={{
|
||||
@ -42,13 +43,16 @@ export const SupplierUpdatePage = () => {
|
||||
variant="notFound"
|
||||
/>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex flex-col overflow-hidden">
|
||||
<FormProvider {...controller.form}>
|
||||
<UnsavedChangesProvider isDirty={controller.form.formState.isDirty}>
|
||||
<RdxPageFrame
|
||||
contentClassName="bg-muted/20"
|
||||
header={
|
||||
<SupplierEditorHeader
|
||||
formId={controller.formId}
|
||||
hasChanges={controller.form.formState.isDirty}
|
||||
@ -56,8 +60,10 @@ export const SupplierUpdatePage = () => {
|
||||
onCancel={navigateBack}
|
||||
title={t("update.title")}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto bg-muted/20">
|
||||
<RdxEditorPageLayout width="simple">
|
||||
}
|
||||
>
|
||||
<RdxPageContainer width="content">
|
||||
<RdxEditorLayout>
|
||||
<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">
|
||||
{controller.mutation.isError ? (
|
||||
@ -73,10 +79,10 @@ export const SupplierUpdatePage = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RdxEditorPageLayout>
|
||||
</div>
|
||||
</RdxEditorLayout>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
</UnsavedChangesProvider>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { useReturnToNavigation } from "@erp/core/hooks";
|
||||
import {
|
||||
RdxPageActions,
|
||||
RdxPageContainer,
|
||||
RdxPageFrame,
|
||||
RdxPageHeader,
|
||||
RdxPageStack,
|
||||
} from "@repo/rdx-ui/components";
|
||||
@ -35,7 +36,8 @@ export const SupplierDetailPage = () => {
|
||||
|
||||
if (query.isError || !query.data) {
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="content">
|
||||
<PageErrorState
|
||||
description={t("detail.error.description")}
|
||||
primaryAction={{
|
||||
@ -49,6 +51,7 @@ export const SupplierDetailPage = () => {
|
||||
variant="notFound"
|
||||
/>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
}
|
||||
|
||||
@ -56,8 +59,9 @@ export const SupplierDetailPage = () => {
|
||||
const context = supplier.reference || supplier.tin || supplier.tradeName || "-";
|
||||
|
||||
return (
|
||||
<RdxPageFrame
|
||||
header={
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<RdxPageHeader
|
||||
actions={
|
||||
<RdxPageActions
|
||||
@ -89,7 +93,11 @@ export const SupplierDetailPage = () => {
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
||||
</RdxPageContainer>
|
||||
}
|
||||
>
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@ -122,9 +130,15 @@ export const SupplierDetailPage = () => {
|
||||
<CardContent>
|
||||
<dl className="grid gap-4 sm:grid-cols-2">
|
||||
<DetailField label={t("fields.primary_email")} value={supplier.primaryEmail} />
|
||||
<DetailField label={t("fields.secondary_email")} value={supplier.secondaryEmail} />
|
||||
<DetailField
|
||||
label={t("fields.secondary_email")}
|
||||
value={supplier.secondaryEmail}
|
||||
/>
|
||||
<DetailField label={t("fields.primary_phone")} value={supplier.primaryPhone} />
|
||||
<DetailField label={t("fields.secondary_phone")} value={supplier.secondaryPhone} />
|
||||
<DetailField
|
||||
label={t("fields.secondary_phone")}
|
||||
value={supplier.secondaryPhone}
|
||||
/>
|
||||
<DetailField label={t("fields.primary_mobile")} value={supplier.primaryMobile} />
|
||||
<DetailField
|
||||
label={t("fields.secondary_mobile")}
|
||||
@ -172,5 +186,6 @@ export const SupplierDetailPage = () => {
|
||||
</div>
|
||||
</RdxPageStack>
|
||||
</RdxPageContainer>
|
||||
</RdxPageFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./rdx-editor-action-bar.tsx";
|
||||
export * from "./rdx-editor-header.tsx";
|
||||
export * from "./rdx-editor-layout.tsx";
|
||||
export * from "./rdx-editor-page-layout.tsx";
|
||||
export * from "./rdx-panel.tsx";
|
||||
|
||||
@ -6,6 +6,7 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { RdxFormGrid } from "../form/rdx-form-grid.tsx";
|
||||
|
||||
import { RdxEditorActionBar } from "./rdx-editor-action-bar.tsx";
|
||||
import { RdxEditorHeader } from "./rdx-editor-header.tsx";
|
||||
import { RdxEditorLayout } from "./rdx-editor-layout.tsx";
|
||||
import { RdxEditorPageLayout } from "./rdx-editor-page-layout.tsx";
|
||||
|
||||
@ -20,6 +21,33 @@ test("RdxEditorLayout only distributes main content and an optional aside", () =
|
||||
assert.doesNotMatch(html, /px-4/);
|
||||
});
|
||||
|
||||
test("RdxEditorHeader renderiza navegación, título, estado, acciones y contenido contextual", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<RdxEditorHeader
|
||||
actions={<span>Actions</span>}
|
||||
backLabel="Volver"
|
||||
onBack={() => undefined}
|
||||
status="Modificada"
|
||||
title="Editar proforma"
|
||||
>
|
||||
<div>Secondary content</div>
|
||||
</RdxEditorHeader>
|
||||
);
|
||||
|
||||
assert.match(html, /aria-label="Volver"/);
|
||||
assert.match(html, /<h1[^>]*>Editar proforma<\/h1>/);
|
||||
assert.match(html, /Modificada/);
|
||||
assert.match(html, /Actions/);
|
||||
assert.match(html, /Secondary content/);
|
||||
assert.doesNotMatch(html, /sticky top-0/);
|
||||
});
|
||||
|
||||
test("RdxEditorHeader no renderiza back sin callback", () => {
|
||||
const html = renderToStaticMarkup(<RdxEditorHeader backLabel="Volver" title="Detalle" />);
|
||||
|
||||
assert.doesNotMatch(html, /aria-label="Volver"/);
|
||||
});
|
||||
|
||||
import { RdxPanel } from "./rdx-panel.tsx";
|
||||
|
||||
test("RdxEditorPageLayout aplica complejidad simple por defecto y renderiza children", () => {
|
||||
|
||||
@ -1,53 +1,53 @@
|
||||
import { RdxBadge } from "@repo/rdx-ui/components";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
||||
|
||||
export interface PageFormHeaderProps {
|
||||
title: string | ReactNode;
|
||||
|
||||
backLabel: string;
|
||||
onBack?: () => void;
|
||||
|
||||
statusLabel?: string;
|
||||
showStatus?: boolean;
|
||||
import { RdxBadge } from "../rdx-badge.tsx";
|
||||
|
||||
export interface RdxEditorHeaderProps
|
||||
extends Omit<ComponentPropsWithoutRef<"header">, "children" | "title"> {
|
||||
title: ReactNode;
|
||||
actions?: ReactNode;
|
||||
backLabel?: string;
|
||||
onBack?: () => void;
|
||||
status?: ReactNode;
|
||||
showStatus?: boolean;
|
||||
children?: ReactNode;
|
||||
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
}
|
||||
|
||||
export const PageFormHeader = ({
|
||||
/**
|
||||
* Cabecera compacta para páginas fullscreen.
|
||||
* Presenta navegación de retorno, título, estado y acciones.
|
||||
*/
|
||||
export const RdxEditorHeader = ({
|
||||
title,
|
||||
actions,
|
||||
backLabel,
|
||||
onBack,
|
||||
statusLabel,
|
||||
showStatus = false,
|
||||
actions,
|
||||
status,
|
||||
showStatus = true,
|
||||
children,
|
||||
className,
|
||||
contentClassName,
|
||||
}: PageFormHeaderProps) => {
|
||||
className,
|
||||
...props
|
||||
}: RdxEditorHeaderProps) => {
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
"sticky top-0 z-50 ring-1 ring-foreground/10 bg-background shadow-xs",
|
||||
className
|
||||
)}
|
||||
className={cn("z-50 shrink-0 bg-background ring-1 ring-foreground/10 shadow-xs", className)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-14 items-center justify-between gap-6 border-b px-4 py-2 sm:px-6 md:h-18",
|
||||
"flex min-h-14 items-center justify-between gap-6 border-b px-4 py-2 sm:px-6 md:min-h-18",
|
||||
contentClassName
|
||||
)}
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
{onBack ? (
|
||||
<Button
|
||||
aria-label={backLabel}
|
||||
disabled={!onBack}
|
||||
onClick={onBack}
|
||||
size="icon"
|
||||
type="button"
|
||||
@ -55,20 +55,21 @@ export const PageFormHeader = ({
|
||||
>
|
||||
<ArrowLeftIcon aria-hidden="true" className="size-5" />
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<div className="flex min-w-0 items-center gap-4">
|
||||
<h1 className="min-w-0 truncate text-xl font-semibold tracking-tight text-foreground lg:text-2xl">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
{statusLabel ? (
|
||||
{status ? (
|
||||
<RdxBadge
|
||||
className={cn(
|
||||
"border-amber-500 bg-amber-200 text-amber-900 transition-opacity duration-200",
|
||||
showStatus ? "opacity-100" : "pointer-events-none opacity-0"
|
||||
)}
|
||||
>
|
||||
{statusLabel}
|
||||
{status}
|
||||
</RdxBadge>
|
||||
) : null}
|
||||
</div>
|
||||
@ -106,29 +106,6 @@ test("RdxPageHeader omits optional wrappers when content is absent", () => {
|
||||
assert.doesNotMatch(html, /Metadata/);
|
||||
});
|
||||
|
||||
test("RdxPageHeader soporta el modo sticky de editor con navegación, estado y contenido secundario", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<RdxPageHeader
|
||||
actions={<span>Actions</span>}
|
||||
backLabel="Volver"
|
||||
onBack={() => undefined}
|
||||
showStatus
|
||||
statusLabel="Modificada"
|
||||
sticky
|
||||
title="Editar proforma"
|
||||
>
|
||||
<div>Secondary content</div>
|
||||
</RdxPageHeader>
|
||||
);
|
||||
|
||||
assert.match(html, /sticky top-0/);
|
||||
assert.match(html, /aria-label="Volver"/);
|
||||
assert.match(html, /Editar proforma/);
|
||||
assert.match(html, /Modificada/);
|
||||
assert.match(html, /Actions/);
|
||||
assert.match(html, /Secondary content/);
|
||||
});
|
||||
|
||||
test("RdxPageActions renders only populated regions in the expected order", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<RdxPageActions
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
||||
|
||||
import { RdxBadge } from "../rdx-badge.tsx";
|
||||
|
||||
export interface RdxPageHeaderProps
|
||||
extends Omit<ComponentPropsWithoutRef<"header">, "children" | "title"> {
|
||||
title: ReactNode;
|
||||
@ -12,27 +8,13 @@ export interface RdxPageHeaderProps
|
||||
leading?: ReactNode;
|
||||
metadata?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
backLabel?: string;
|
||||
onBack?: () => void;
|
||||
statusLabel?: ReactNode;
|
||||
showStatus?: boolean;
|
||||
sticky?: boolean;
|
||||
contentClassName?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cabecera estándar de una página.
|
||||
*
|
||||
* Muestra:
|
||||
* - navegación de retorno opcional
|
||||
* - título
|
||||
* - descripción o metadata opcional
|
||||
* - estado opcional
|
||||
* - acciones de página
|
||||
*
|
||||
* Puede permanecer fija dentro de un contenedor scrollable
|
||||
* mediante `sticky`.
|
||||
* Presenta título, descripción, metadata y acciones.
|
||||
*/
|
||||
|
||||
export const RdxPageHeader = ({
|
||||
@ -41,73 +23,10 @@ export const RdxPageHeader = ({
|
||||
leading,
|
||||
metadata,
|
||||
actions,
|
||||
backLabel,
|
||||
onBack,
|
||||
statusLabel,
|
||||
showStatus = false,
|
||||
sticky = false,
|
||||
contentClassName,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: RdxPageHeaderProps) => {
|
||||
if (sticky) {
|
||||
const shouldRenderBackButton = Boolean(backLabel || onBack);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
"sticky top-0 z-50 shrink-0 bg-background ring-1 ring-foreground/10 shadow-xs",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-h-14 items-center justify-between gap-6 border-b px-4 py-2 sm:px-6 md:min-h-18",
|
||||
contentClassName
|
||||
)}
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
{shouldRenderBackButton ? (
|
||||
<Button
|
||||
aria-label={backLabel}
|
||||
disabled={!onBack}
|
||||
onClick={onBack}
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<ArrowLeftIcon aria-hidden="true" className="size-5" />
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<div className="flex min-w-0 items-center gap-4">
|
||||
<h1 className="min-w-0 truncate text-xl font-semibold tracking-tight text-foreground lg:text-2xl">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
{statusLabel ? (
|
||||
<RdxBadge
|
||||
className={cn(
|
||||
"border-amber-500 bg-amber-200 text-amber-900 transition-opacity duration-200",
|
||||
showStatus ? "opacity-100" : "pointer-events-none opacity-0"
|
||||
)}
|
||||
>
|
||||
{statusLabel}
|
||||
</RdxBadge>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{actions ? <div className="flex items-center gap-4">{actions}</div> : null}
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user