.
This commit is contained in:
parent
7bd20c22df
commit
8ecebc64bb
@ -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;
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 /> },
|
||||||
],
|
],
|
||||||
},
|
*/
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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") ?? "";
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user