This commit is contained in:
David Arranz 2026-06-15 11:13:16 +02:00
parent 7bd20c22df
commit 8ecebc64bb
9 changed files with 406 additions and 143 deletions

View File

@ -28,25 +28,23 @@ const EMPTY_PROFORMAS_LIST: ProformaList = {
}; };
// Campos que se permiten ordenar para la lista de proformas (consulta). // Campos que se permiten ordenar para la lista de proformas (consulta).
type ProformaListSortField = "invoiceNumber" | "recipientName" | "invoiceDate"; const PROFORMA_LIST_SORT_FIELDS = {
type ProformaListApiSortField = "invoice_number" | "recipient_name" | "invoice_date";
const PROFORMA_LIST_SORT_FIELDS: Record<ProformaListSortField, ProformaListApiSortField> = {
invoiceNumber: "invoice_number", invoiceNumber: "invoice_number",
recipientName: "recipient_name", recipientName: "recipient_name",
invoiceDate: "invoice_date", invoiceDate: "invoice_date",
}; } as const;
const DEFAULT_API_SORT_FIELD: ProformaListApiSortField = "invoice_date"; type ProformaListSortField = keyof typeof PROFORMA_LIST_SORT_FIELDS;
const DEFAULT_SORT = { const DEFAULT_SORT = {
field: "invoiceDate", field: "invoiceDate",
direction: "desc", direction: "desc",
} satisfies DataTableSort; } satisfies DataTableSort;
const DEFAULT_API_SORT_FIELD = PROFORMA_LIST_SORT_FIELDS.invoiceDate;
const isProformaListSortField = (value: string | null): value is ProformaListSortField => { const isProformaListSortField = (value: string | null): value is ProformaListSortField => {
return value === "invoiceNumber" || value === "recipientName" || value === "invoiceDate"; return value !== null && value in PROFORMA_LIST_SORT_FIELDS;
}; };
const isSortDirection = (value: string | null): value is DataTableSortDirection => { const isSortDirection = (value: string | null): value is DataTableSortDirection => {
@ -117,7 +115,9 @@ export const useListProformasController = () => {
orderBy, orderBy,
order, order,
filters: filters:
statusFilter === "all" ? [] : [{ field: "status", operator: "eq", value: statusFilter }], statusFilter === "all"
? []
: [{ field: "status", operator: "EQUALS", value: statusFilter }],
}), }),
[debouncedSearch, pageIndex, pageSize, orderBy, order, statusFilter] [debouncedSearch, pageIndex, pageSize, orderBy, order, statusFilter]
); );
@ -134,7 +134,7 @@ export const useListProformasController = () => {
// Reset page to 1 when status filter changes // Reset page to 1 when status filter changes
setSearchParams((prev) => { setSearchParams((prev) => {
const params = new URLSearchParams(prev); const params = new URLSearchParams(prev);
params.set("page", "1"); params.set("page", String(INITIAL_PAGE_INDEX + 1));
return params; return params;
}); });
return nextValue; return nextValue;

View File

@ -22,7 +22,7 @@ import {
FilterIcon, FilterIcon,
PlusIcon, PlusIcon,
} from "lucide-react"; } from "lucide-react";
import { createSearchParams, useLocation, useNavigate } from "react-router-dom"; import { createSearchParams, useNavigate } from "react-router-dom";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../i18n";
import { ChangeProformaStatusDialog } from "../../../change-status"; import { ChangeProformaStatusDialog } from "../../../change-status";
@ -39,7 +39,6 @@ import { ProformaStatusBadge } from "../components";
export const ListProformasPage = () => { export const ListProformasPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const { currentReturnTo } = useReturnToNavigation({ const { currentReturnTo } = useReturnToNavigation({
fallbackPath: "/proformas", fallbackPath: "/proformas",

View File

@ -1,9 +1,5 @@
import type { ReactNode } from "react"; import type { PropsWithChildren } from "react";
interface ProformaLayoutProps { export function ProformaLayout({ children }: PropsWithChildren) {
children: ReactNode;
}
export function ProformaLayout({ children }: ProformaLayoutProps) {
return <div className="flex flex-col h-full w-full">{children}</div>; return <div className="flex flex-col h-full w-full">{children}</div>;
} }

View File

@ -6,13 +6,17 @@ import { Outlet, type RouteObject } from "react-router-dom";
const CustomerLayout = lazy(() => const CustomerLayout = lazy(() =>
import("./shared/ui").then((m) => ({ default: m.CustomerLayout })) import("./shared/ui").then((m) => ({ default: m.CustomerLayout }))
); );
const CustomersList = lazy(() => import("./list").then((m) => ({ default: m.ListCustomersPage }))); const CustomersListPage = lazy(() =>
const CustomerView = lazy(() => import("./view").then((m) => ({ default: m.CustomerViewPage }))); import("./list").then((m) => ({ default: m.ListCustomersPage }))
);
const CustomerViewPage = lazy(() =>
import("./view").then((m) => ({ default: m.CustomerViewPage }))
);
const CustomerCreate = lazy(() => const CustomerCreatePage = lazy(() =>
import("./create").then((m) => ({ default: m.CustomerCreatePage })) import("./create").then((m) => ({ default: m.CustomerCreatePage }))
); );
const CustomerUpdate = lazy(() => const CustomerUpdatePage = lazy(() =>
import("./update").then((m) => ({ default: m.CustomerUpdatePage })) import("./update").then((m) => ({ default: m.CustomerUpdatePage }))
); );
@ -20,21 +24,56 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
return [ return [
{ {
path: "customers", path: "customers",
handle: {
layout: "app-sidebar",
protected: true,
},
element: ( element: (
<CustomerLayout> <CustomerLayout>
<Outlet context={params} /> <Outlet context={params} />
</CustomerLayout> </CustomerLayout>
), ),
children: [ children: [
{ path: "", index: true, element: <CustomersList /> }, // index {
{ path: "list", element: <CustomersList /> }, index: true,
//{ path: "create", element: <CustomerAdd /> }, element: <CustomersListPage />,
{ path: ":id", element: <CustomerView /> }, },
{ path: ":id/edit", element: <CustomerUpdate /> }, {
{ path: "create", element: <CustomerCreate /> }, path: "list",
element: <CustomersListPage />,
},
],
},
// {
/* path: "customers/create",
handle: {
layout: "app-fullscreen",
protected: true,
},
element: <CustomerCreatePage />,
},
{
path: "customers/:id/edit",
handle: {
layout: "app-fullscreen",
protected: true,
},
element: <CustomerUpdatePage />,
},
{
path: "customers/:id",
handle: {
layout: "app-fullscreen",
protected: true,
},
element: <CustomerViewPage />,
},
/*
children: [
{ path: ":id", element: <CustomersList /> }, { path: ":id", element: <CustomersList /> },
{ path: ":id/edit", element: <CustomersList /> }, { path: ":id/edit", element: <CustomersList /> },
{ path: ":id/delete", element: <CustomersList /> }, { path: ":id/delete", element: <CustomersList /> },
@ -43,8 +82,8 @@ export const CustomerRoutes = (params: ModuleClientParams): RouteObject[] => {
{ path: ":id/email", element: <CustomersList /> }, { path: ":id/email", element: <CustomersList /> },
{ path: ":id/download", element: <CustomersList /> }, { path: ":id/download", element: <CustomersList /> },
{ path: ":id/duplicate", element: <CustomersList /> }, { path: ":id/duplicate", element: <CustomersList /> },
{ path: ":id/preview", element: <CustomersList /> },*/ { path: ":id/preview", element: <CustomersList /> },
], ],
}, */
]; ];
}; };

View File

@ -6,6 +6,7 @@ import { useListCustomersController } from "./use-list-customers.controller";
export const useListCustomersPageController = () => { export const useListCustomersPageController = () => {
const listCtrl = useListCustomersController(); const listCtrl = useListCustomersController();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const customerId = searchParams.get("customerId") ?? ""; const customerId = searchParams.get("customerId") ?? "";

View File

@ -1,55 +1,225 @@
import { useDebounce } from "@erp/core/hooks"; import { useDebounce } from "@erp/core/hooks";
import { useMemo, useState } from "react"; import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria";
import {
type DataTableSort,
type DataTableSortDirection,
useDataTablePreferences,
} from "@repo/rdx-ui/components";
import { NumberHelper } from "@repo/rdx-utils";
import { useCallback, useMemo, useState } from "react";
import { useSearchParams } from "react-router-dom";
import { type ListCustomersByCriteriaParams, useCustomersListQuery } from "../../shared"; import {
type CustomerList,
type CustomerStatus,
type ListCustomersByCriteriaParams,
useCustomersListQuery,
} from "../../shared";
type CustomerListStatusFilter = "all" | CustomerStatus;
// Datos por defecto mientras se carga la consulta o en caso de error.
const EMPTY_CUSTOMERS_LIST: CustomerList = {
items: [],
page: INITIAL_PAGE_INDEX,
perPage: INITIAL_PAGE_SIZE,
totalPages: 0,
totalItems: 0,
};
// Campos que se permiten ordenar para la lista de proformas (consulta).
const CUSTOMER_LIST_SORT_FIELDS = {
name: "name",
tradeName: "trade_name",
reference: "reference",
tin: "tin",
emailPrimary: "email_primary",
mobilePrimary: "mobile_primary",
} as const;
type CustomerListSortField = keyof typeof CUSTOMER_LIST_SORT_FIELDS;
const DEFAULT_SORT = {
field: "NAME",
direction: "desc",
} satisfies DataTableSort;
const DEFAULT_API_SORT_FIELD = CUSTOMER_LIST_SORT_FIELDS.name;
const isCustomerListSortField = (value: string | null): value is CustomerListSortField => {
return value !== null && value in CUSTOMER_LIST_SORT_FIELDS;
};
const isSortDirection = (value: string | null): value is DataTableSortDirection => {
return value === "asc" || value === "desc";
};
export const useListCustomersController = () => { export const useListCustomersController = () => {
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(5);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState<CustomerListStatusFilter>("all");
const [searchParams, setSearchParams] = useSearchParams();
const debouncedQ = useDebounce(search, 300); const tablePreferences = useDataTablePreferences({
storageKey: "customers:list:grid",
defaultPageSize: EMPTY_CUSTOMERS_LIST.perPage,
defaultColumnVisibility: {
reference: true,
recipientName: true,
status: true,
totalAmountFmt: true,
invoiceDate: true,
},
defaultSort: DEFAULT_SORT,
});
const { pageSize: preferencesPageSize, setPageSize: setPageSizePreference } = tablePreferences;
const { sort: preferencesSorting, setSort: setSortPreference } = tablePreferences;
// Parse page from URL (1-based) or default to 1
const urlPage = NumberHelper.parsePositiveInteger(
searchParams.get("page"),
INITIAL_PAGE_INDEX + 1
);
const pageIndex = urlPage - 1; // Convert to 0-based for internal use
// Parse pageSize from URL or use preferences
const urlPageSize = searchParams.get("pageSize");
const pageSize = urlPageSize
? NumberHelper.parsePositiveInteger(urlPageSize, preferencesPageSize)
: preferencesPageSize;
const debouncedSearch = useDebounce(search, 300);
// Criterios de ordenamiento
const urlSortFieldValue = searchParams.get("sortField");
const urlSortDirectionValue = searchParams.get("sortDirection");
const urlSort =
isCustomerListSortField(urlSortFieldValue) && isSortDirection(urlSortDirectionValue)
? {
field: urlSortFieldValue,
direction: urlSortDirectionValue,
}
: undefined;
const sort = urlSort ?? preferencesSorting ?? DEFAULT_SORT;
const orderBy =
CUSTOMER_LIST_SORT_FIELDS[sort.field as CustomerListSortField] ?? DEFAULT_API_SORT_FIELD;
const order = sort.direction;
// Construir criterios de consulta
const criteria = useMemo<NonNullable<ListCustomersByCriteriaParams["criteria"]>>( const criteria = useMemo<NonNullable<ListCustomersByCriteriaParams["criteria"]>>(
() => ({ () => ({
q: debouncedQ || "", q: debouncedSearch || "",
pageNumber: pageIndex, pageNumber: pageIndex,
pageSize, pageSize,
order: "desc", orderBy,
orderBy: "name", order,
//filters: statusFilter === "all" ? [] : [{ field: "status", operator: "eq", value: statusFilter }], filters:
statusFilter === "all"
? []
: [{ field: "status", operator: "EQUALS", value: statusFilter }],
}), }),
[debouncedQ, pageIndex, pageSize /*statusFilter*/] [debouncedSearch, pageIndex, pageSize, orderBy, order, statusFilter]
); );
const query = useCustomersListQuery({ criteria }); const query = useCustomersListQuery({ criteria });
const setSearchValue = (value: string) => { const setStatusFilterValue = useCallback(
const nextValue = value.trim().replace(/\s+/g, " "); (value: string) => {
const nextValue = (value || "all") as CustomerListStatusFilter;
setSearch((prev) => { setStatusFilter((prev) => {
if (prev === nextValue) return prev; if (prev === nextValue) return prev;
// Sólo si la búsqueda realmente cambia, // Reset page to 1 when status filter changes
// reseteamos la página a 0 para evitar inconsistencias setSearchParams((prev) => {
setPageIndex(0); const params = new URLSearchParams(prev);
return nextValue; params.set("page", String(INITIAL_PAGE_INDEX + 1));
}); return params;
}; });
return nextValue;
});
},
[setSearchParams]
);
const setPageSizeValue = (value: number) => { const setSearchValue = useCallback(
setPageSize((prev) => { (value: string) => {
if (prev === value) return prev; const nextValue = value.trim().replace(/\s+/g, " ");
// Sólo si el tamaño de página realmente cambia, setSearch((prev) => {
// reseteamos la página a 0 para evitar inconsistencias if (prev === nextValue) return prev;
setPageIndex(0);
return value; // Reset page to 1 when search changes
}); setSearchParams((prev) => {
}; const params = new URLSearchParams(prev);
params.set("page", String(INITIAL_PAGE_INDEX + 1)); // Convert to 1-based for URL
return params;
});
return nextValue;
});
},
[setSearchParams]
);
const setPageIndexValue = useCallback(
(newPageIndex: number) => {
const newPage = newPageIndex + 1; // Convert to 1-based for URL
setSearchParams((prev) => {
const params = new URLSearchParams(prev);
params.set("page", String(newPage));
return params;
});
},
[setSearchParams]
);
const setPageSizeValue = useCallback(
(value: number) => {
if (pageSize === value) return;
// Reset page to 1 and update pageSize when it changes
setSearchParams((prev) => {
const params = new URLSearchParams(prev);
params.set("page", String(INITIAL_PAGE_INDEX + 1)); // Convert to 1-based for URL
params.set("pageSize", String(value));
return params;
});
setPageSizePreference(value);
},
[pageSize, setSearchParams, setPageSizePreference]
);
const setSortValue = useCallback(
(nextSort: DataTableSort) => {
if (!isCustomerListSortField(nextSort.field)) {
return;
}
if (sort.field === nextSort.field && sort.direction === nextSort.direction) {
return;
}
setSearchParams((prev) => {
const params = new URLSearchParams(prev);
params.set("sortField", nextSort.field);
params.set("sortDirection", nextSort.direction);
params.set("page", String(INITIAL_PAGE_INDEX + 1));
return params;
});
setSortPreference(nextSort);
},
[setSearchParams, sort.field, sort.direction, setSortPreference]
);
return { return {
data: query.data, data: query.data ?? EMPTY_CUSTOMERS_LIST,
isLoading: query.isLoading, isLoading: query.isLoading,
isFetching: query.isFetching, isFetching: query.isFetching,
@ -58,12 +228,20 @@ export const useListCustomersController = () => {
refetch: query.refetch, refetch: query.refetch,
tablePreferences,
pageIndex, pageIndex,
pageSize, pageSize,
setPageIndex, setPageIndex: setPageIndexValue,
setPageSize: setPageSizeValue, setPageSize: setPageSizeValue,
search, search,
setSearchValue, setSearchValue,
sort,
setSort: setSortValue,
statusFilter,
setStatusFilter: setStatusFilterValue,
}; };
}; };

View File

@ -1,6 +1,5 @@
import { DataTable, SkeletonDataTable } from "@repo/rdx-ui/components"; import { DataTable, type DataTableSort, SkeletonDataTable } from "@repo/rdx-ui/components";
import type { ColumnDef } from "@tanstack/react-table"; import type { ColumnDef, OnChangeFn, VisibilityState } from "@tanstack/react-table";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../i18n";
import type { CustomerList, CustomerListRow } from "../../../../shared"; import type { CustomerList, CustomerListRow } from "../../../../shared";
@ -8,6 +7,7 @@ import type { CustomerList, CustomerListRow } from "../../../../shared";
interface CustomersGridProps { interface CustomersGridProps {
data?: CustomerList; data?: CustomerList;
loading: boolean; loading: boolean;
fetching?: boolean;
columns: ColumnDef<CustomerListRow, unknown>[]; columns: ColumnDef<CustomerListRow, unknown>[];
@ -16,46 +16,67 @@ interface CustomersGridProps {
onPageChange: (pageIndex: number) => void; onPageChange: (pageIndex: number) => void;
onPageSizeChange: (size: number) => void; onPageSizeChange: (size: number) => void;
onRowClick?: (row: CustomerListRow) => void; sort: DataTableSort;
onSortChange?: (nextSort: DataTableSort) => void;
columnVisibility?: VisibilityState;
onColumnVisibilityChange?: OnChangeFn<VisibilityState>;
onRowClick?: (customerId: string) => void;
} }
export const CustomersGrid = ({ export const CustomersGrid = ({
data, data,
loading,
columns, columns,
loading,
fetching,
pageIndex, pageIndex,
pageSize, pageSize,
onPageChange, onPageChange,
onPageSizeChange, onPageSizeChange,
sort,
onSortChange,
columnVisibility,
onColumnVisibilityChange,
onRowClick, onRowClick,
}: CustomersGridProps) => { }: CustomersGridProps) => {
const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const { items, totalItems: total_items } = data || { items: [], totalItems: 0 }; const { items, totalItems } = data || { items: [], totalItems: 0 };
if (loading) if (loading) {
return ( return (
<SkeletonDataTable <SkeletonDataTable
columns={columns.length} columns={columns.length}
footerProps={{ pageIndex, pageSize, totalItems: total_items ?? 0 }} footerProps={{ pageIndex, pageSize, totalItems: totalItems ?? 0 }}
rows={Math.max(6, pageSize)} rows={Math.max(6, pageSize)}
showFooter showFooter
/> />
); );
}
return ( return (
<DataTable <DataTable
columns={columns} columns={columns}
columnVisibility={columnVisibility}
data={items} data={items}
enablePagination enablePagination
enableRowSelection enableRowSelection
manualPagination manualPagination
manualSorting
onColumnVisibilityChange={onColumnVisibilityChange}
onPageChange={onPageChange} onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange} onPageSizeChange={onPageSizeChange}
onRowClick={(row, _index) => onRowClick?.(row)} onSortChange={onSortChange}
//onRowClick={(row) => onRowClick?.(row.id)}
pageIndex={pageIndex} pageIndex={pageIndex}
pageSize={pageSize} pageSize={pageSize}
totalItems={total_items} sort={sort}
totalItems={totalItems}
/> />
); );
}; };

View File

@ -1,5 +1,6 @@
import { PageHeader, SimpleSearchInput } from "@erp/core/components"; import { PageHeader, SimpleSearchInput } from "@erp/core/components";
import { AppContent, AppHeader, BackHistoryButton } from "@repo/rdx-ui/components"; import { useReturnToNavigation } from "@erp/core/hooks";
import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
import { import {
Button, Button,
ResizableHandle, ResizableHandle,
@ -7,7 +8,7 @@ import {
ResizablePanelGroup, ResizablePanelGroup,
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { PlusIcon } from "lucide-react"; import { PlusIcon } from "lucide-react";
import { useNavigate } from "react-router-dom"; import { createSearchParams, useNavigate } from "react-router-dom";
import { useTranslation } from "../../../i18n"; import { useTranslation } from "../../../i18n";
import { ErrorAlert } from "../../../shared/ui"; import { ErrorAlert } from "../../../shared/ui";
@ -18,11 +19,33 @@ export const ListCustomersPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const { currentReturnTo } = useReturnToNavigation({
fallbackPath: "/customers",
});
const { listCtrl, panelCtrl } = useListCustomersPageController(); const { listCtrl, panelCtrl } = useListCustomersPageController();
const handleEditClick = (customerId: string) => {
navigate({
pathname: `/customers/${customerId}/edit`,
search: createSearchParams({
returnTo: currentReturnTo,
}).toString(),
});
};
const handleViewClick = (customerId: string) => {
navigate({
pathname: `/customers/${customerId}/`,
search: createSearchParams({
returnTo: currentReturnTo,
}).toString(),
});
};
const columns = useCustomersGridColumns({ const columns = useCustomersGridColumns({
onEditClick: (customer) => navigate(`/customers/${customer.id}/edit`), onEditClick: (customer) => handleEditClick(customer.id),
onViewClick: (customer) => navigate(`/customers/${customer.id}`), onViewClick: (customer) => handleViewClick(customer.id),
onSummaryClick: (customer) => panelCtrl.openCustomerPanel(customer.id, "view"), onSummaryClick: (customer) => panelCtrl.openCustomerPanel(customer.id, "view"),
//onDeleteClick: (customer) => null, //confirmDelete(inv.id), //onDeleteClick: (customer) => null, //confirmDelete(inv.id),
}); });
@ -30,8 +53,8 @@ export const ListCustomersPage = () => {
const isPanelOpen = panelCtrl.panelState.isOpen; const isPanelOpen = panelCtrl.panelState.isOpen;
const listContent = ( const listContent = (
<div className="h-full min-w-0 overflow-auto w-full"> <div className="flex h-full min-w-0 flex-col gap-4 overflow-hidden">
<div className="flex items-center justify-between gap-16"> <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<SimpleSearchInput <SimpleSearchInput
loading={listCtrl.isFetching} loading={listCtrl.isFetching}
onSearchChange={listCtrl.setSearchValue} onSearchChange={listCtrl.setSearchValue}
@ -39,15 +62,23 @@ export const ListCustomersPage = () => {
/> />
</div> </div>
<CustomersGrid <div>
columns={columns} <CustomersGrid
data={listCtrl.data} columns={columns}
loading={listCtrl.isLoading} columnVisibility={listCtrl.tablePreferences.columnVisibility}
onPageChange={listCtrl.setPageIndex} data={listCtrl.data}
onPageSizeChange={listCtrl.setPageSize} fetching={listCtrl.isFetching}
pageIndex={listCtrl.pageIndex} loading={listCtrl.isLoading}
pageSize={listCtrl.pageSize} onColumnVisibilityChange={listCtrl.tablePreferences.setColumnVisibility}
/> onPageChange={listCtrl.setPageIndex}
onPageSizeChange={listCtrl.setPageSize}
onRowClick={(customerId) => panelCtrl.openCustomerPanel(customerId, "view")}
onSortChange={listCtrl.setSort}
pageIndex={listCtrl.pageIndex}
pageSize={listCtrl.pageSize}
sort={listCtrl.sort}
/>
</div>
</div> </div>
); );
@ -64,61 +95,60 @@ export const ListCustomersPage = () => {
} }
return ( return (
<> <div className="p-6 space-y-6">
<AppHeader> {/* Header */}
<PageHeader <PageHeader
description={t("pages.list.description")} description={t("pages.proformas.list.description")}
rightSlot={ rightSlot={
<Button <Button
aria-label={t("pages.create.title")} aria-label={t("pages.proformas.create.title")}
onClick={() => navigate("/customers/create")} onClick={() => navigate("/proformas/create")}
> size={"default"}
<PlusIcon aria-hidden className="mr-2 size-4" />
{t("pages.create.title")}
</Button>
}
title={t("pages.list.title")}
/>
</AppHeader>
<AppContent>
{isPanelOpen ? (
<ResizablePanelGroup
autoSave="list-customers-page"
className="h-full"
orientation="horizontal"
> >
<ResizablePanel defaultSize="70%" maxSize="75%" minSize="70%"> <PlusIcon aria-hidden className="mr-2 size-4" />
{listContent} {t("pages.proformas.create.title")}
</ResizablePanel> </Button>
}
title={t("pages.proformas.list.title")}
/>
<ResizableHandle className="mx-4" withHandle /> {/* Table */}
{isPanelOpen ? (
<ResizablePanelGroup
autoSave="list-customers-page"
className="h-full"
orientation="horizontal"
>
<ResizablePanel defaultSize="70%" maxSize="75%" minSize="70%">
{listContent}
</ResizablePanel>
<ResizablePanel defaultSize="30%" maxSize="30%" minSize="25%"> <ResizableHandle className="mx-4" withHandle />
<div className="h-full">
<CustomerSummaryPanel
className="border bg-background"
customer={panelCtrl.customer}
mode={panelCtrl.panelState.mode}
onEdit={(customer) => navigate(`/customers/${customer.id}/edit`)}
onOpenChange={(open) => {
if (!open) {
panelCtrl.closePanel();
return;
}
panelCtrl.panelState.onOpenChange(true); <ResizablePanel defaultSize="30%" maxSize="30%" minSize="25%">
}} <div className="h-full">
open={panelCtrl.panelState.isOpen} <CustomerSummaryPanel
visibility={panelCtrl.panelState.visibility} className="border bg-background"
/> customer={panelCtrl.customer}
</div> mode={panelCtrl.panelState.mode}
</ResizablePanel> onEdit={(customer) => navigate(`/customers/${customer.id}/edit`)}
</ResizablePanelGroup> onOpenChange={(open) => {
) : ( if (!open) {
<div className="flex min-h-0 flex-1 overflow-hidden">{listContent}</div> panelCtrl.closePanel();
)} return;
</AppContent> }
</>
panelCtrl.panelState.onOpenChange(true);
}}
open={panelCtrl.panelState.isOpen}
visibility={panelCtrl.panelState.visibility}
/>
</div>
</ResizablePanel>
</ResizablePanelGroup>
) : (
<div className="mx-auto w-full space-y-4">{listContent}</div>
)}
</div>
); );
}; };

View File

@ -1,6 +1,5 @@
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
export const CustomerLayout = ({ children }: PropsWithChildren) => { export const CustomerLayout = ({ children }: PropsWithChildren) => {
//return <CustomersProvider>{children}</CustomersProvider>; return <div className="flex flex-col h-full w-full">{children}</div>;
return <div>{children}</div>;
}; };