.
This commit is contained in:
parent
87eb51a44b
commit
bead394ebd
@ -4,9 +4,10 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
|
||||
import { createAxiosDataProvider } from "@/lib/axios/create-axios-data-provider";
|
||||
import { DataSourceProvider, UnsavedWarnProvider } from "@/lib/hooks";
|
||||
import { UnsavedWarnProvider } from "@/lib/hooks";
|
||||
import { i18n } from "@/locales";
|
||||
|
||||
import { DataSourceProvider } from "@erp/core/hooks";
|
||||
import { AppRoutes } from "./app-routes";
|
||||
import "./app.css";
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ListResponseDTO } from "@erp/core";
|
||||
import type { IListResponseDTO } from "@erp/core";
|
||||
import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria";
|
||||
import type { IDataSource } from "../../../../../modules/core/src/web/hooks/use-datasource/datasource.interface";
|
||||
import { getApiAuthorization as getApiAuthLib } from "../api";
|
||||
import type {
|
||||
ICreateOneDataProviderParams,
|
||||
@ -15,7 +16,6 @@ import type {
|
||||
IUpdateOneDataProviderParams,
|
||||
IUploadFileDataProviderParam,
|
||||
} from "../hooks/use-datasource";
|
||||
import type { IDataSource } from "../hooks/use-datasource/datasource";
|
||||
import { createAxiosInstance, defaultAxiosRequestConfig } from "./axios-instance";
|
||||
|
||||
export const createAxiosDataProvider = (
|
||||
@ -28,7 +28,7 @@ export const createAxiosDataProvider = (
|
||||
|
||||
getApiAuthorization: getApiAuthLib,
|
||||
|
||||
getList: async <R>(params: IGetListDataProviderParams): Promise<ListResponseDTO<R>> => {
|
||||
getList: async <R>(params: IGetListDataProviderParams): Promise<IListResponseDTO<R>> => {
|
||||
const { resource, quickSearchTerm, pagination, filters = [], sort = [] } = params;
|
||||
|
||||
const url = `${apiUrl}/${resource}`;
|
||||
@ -53,7 +53,7 @@ export const createAxiosDataProvider = (
|
||||
urlParams.append("$filters", queryFilters.join(","));
|
||||
}
|
||||
|
||||
const response = await httpClient.request<ListResponseDTO<R>>({
|
||||
const response = await httpClient.request<IListResponseDTO<R>>({
|
||||
url: `${url}?${urlParams.toString()}`,
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
export * from "./use-datasource";
|
||||
export * from "./use-theme";
|
||||
export * from "./use-unsaved-changes-notifier";
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
import { type PropsWithChildren, createContext } from "react";
|
||||
import { type IDataSource } from "./datasource";
|
||||
|
||||
export const DataSourceContext = createContext<IDataSource | undefined>(undefined);
|
||||
|
||||
export const DataSourceProvider = ({
|
||||
dataSource,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
dataSource: IDataSource;
|
||||
}>) => <DataSourceContext.Provider value={dataSource}>{children}</DataSourceContext.Provider>;
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./datasource-context";
|
||||
export * from "./datasource";
|
||||
@ -27,6 +27,10 @@
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"allowUnreachableCode": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": [
|
||||
"src",
|
||||
"../../modules/core/src/web/hooks/use-datasource/use-datasource.tsx",
|
||||
"../../modules/core/src/web/hooks/use-datasource/datasource.interface.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@ -5,11 +5,13 @@
|
||||
"types": "src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./dto": "./src/dto/index.ts",
|
||||
"./hooks": "./src/web/hooks/index.ts",
|
||||
"./components": "./src/web/components/index.tsx",
|
||||
"./components/*": "./src/web/components/*.tsx"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"joi": "^17.13.3",
|
||||
"react": "^18 || ^19",
|
||||
"react-dom": "^18 || ^19",
|
||||
"sequelize": "^6.37.5"
|
||||
@ -17,13 +19,18 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/axios": "^0.14.4",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.3",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@repo/rdx-utils": "workspace:*",
|
||||
"@repo/rdx-criteria": "workspace:*",
|
||||
"@tanstack/react-query": "^5.75.4",
|
||||
"axios": "^1.9.0",
|
||||
"joi": "^17.13.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^6.26.0",
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
export interface ListResponseDTO<T> {
|
||||
export interface IListResponseDTO<T> {
|
||||
page: number;
|
||||
perpage: number;
|
||||
totalpages: number;
|
||||
totalitems: number;
|
||||
total_pages: number;
|
||||
total_items: number;
|
||||
items: T[];
|
||||
}
|
||||
|
||||
export const IsResponseAListDTO = <T>(response: any): response is ListResponseDTO<T> => {
|
||||
return (
|
||||
typeof response === "object" &&
|
||||
response !== null &&
|
||||
Object.prototype.hasOwnProperty.call(response, "totalitems")
|
||||
);
|
||||
export const isResponseAListDTO = <T>(data: any): data is IListResponseDTO<T> => {
|
||||
return data && typeof data.total_items === "number";
|
||||
};
|
||||
|
||||
export const existsMoreReponsePages = <T>(response: any): response is ListResponseDTO<T> => {
|
||||
return IsResponseAListDTO(response) && response.page + 1 < response.totalpages;
|
||||
export const existsMoreReponsePages = <T>(response: any): response is IListResponseDTO<T> => {
|
||||
return isResponseAListDTO(response) && response.page + 1 < response.total_pages;
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Result, RuleValidator } from "@repo/rdx-utils";
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../domain";
|
||||
|
||||
export interface IMoneyDTO {
|
||||
amount: number | null;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Result, RuleValidator } from "@repo/rdx-utils";
|
||||
import Joi from "joi";
|
||||
import { Result, RuleValidator } from "../../domain";
|
||||
|
||||
export interface IQuantityDTO {
|
||||
amount: number | null;
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export * from "./use-datasource";
|
||||
export * from "./use-pagination";
|
||||
export * from "./use-query-key";
|
||||
export * from "./use-toggle";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ListResponseDTO } from "@erp/core";
|
||||
import type { IListResponseDTO } from "@erp/core";
|
||||
import { type AxiosHeaderValue, type ResponseType } from "axios";
|
||||
|
||||
export interface IPaginationDataProviderParam {
|
||||
@ -87,7 +87,7 @@ export interface ICustomDataProviderParam {
|
||||
|
||||
export interface IDataSource {
|
||||
name: () => string;
|
||||
getList: <R>(params: IGetListDataProviderParams) => Promise<ListResponseDTO<R>>;
|
||||
getList: <R>(params: IGetListDataProviderParams) => Promise<IListResponseDTO<R>>;
|
||||
getOne: <R>(params: IGetOneDataProviderParams) => Promise<R>;
|
||||
//saveOne: <P, R>(params: ISaveOneDataProviderParams<P>) => Promise<R>;
|
||||
createOne: <P, R>(params: ICreateOneDataProviderParams<P>) => Promise<R>;
|
||||
3
modules/core/src/web/hooks/use-datasource/index.ts
Normal file
3
modules/core/src/web/hooks/use-datasource/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./datasource.interface";
|
||||
export * from "./use-datasource";
|
||||
export * from "./use-list";
|
||||
19
modules/core/src/web/hooks/use-datasource/use-datasource.tsx
Normal file
19
modules/core/src/web/hooks/use-datasource/use-datasource.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { type PropsWithChildren, createContext, useContext } from "react";
|
||||
import { IDataSource } from "./datasource.interface";
|
||||
|
||||
export const DataSourceContext = createContext<IDataSource | undefined>(undefined);
|
||||
|
||||
export const DataSourceProvider = ({
|
||||
dataSource,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
dataSource: IDataSource;
|
||||
}>) => <DataSourceContext.Provider value={dataSource}>{children}</DataSourceContext.Provider>;
|
||||
|
||||
export const useDataSource = () => {
|
||||
const context = useContext(DataSourceContext);
|
||||
if (context === undefined)
|
||||
throw new Error("useDataSource must be used within a DataSourceProvider");
|
||||
|
||||
return context;
|
||||
};
|
||||
87
modules/core/src/web/hooks/use-datasource/use-list.ts
Normal file
87
modules/core/src/web/hooks/use-datasource/use-list.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
QueryFunctionContext,
|
||||
QueryKey,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
keepPreviousData,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { isResponseAListDTO } from "@erp/core/dto";
|
||||
import {
|
||||
UseLoadingOvertimeOptionsProps,
|
||||
UseLoadingOvertimeReturnType,
|
||||
useLoadingOvertime,
|
||||
} from "./use-loading-overtime";
|
||||
|
||||
const DEFAULT_REFETCH_INTERVAL = 2 * 60 * 1000; // 2 minutes
|
||||
const DEFAULT_STALE_TIME = 60 * 1000; // 1 minute
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export type UseListQueryOptions<TUseListQueryData, TUseListQueryError> = {
|
||||
queryKey: QueryKey;
|
||||
queryFn: (context: QueryFunctionContext) => Promise<TUseListQueryData>;
|
||||
enabled?: boolean;
|
||||
refetchInterval?: number | false;
|
||||
select?: (data: TUseListQueryData) => TUseListQueryData;
|
||||
queryOptions?: Partial<UseQueryOptions<TUseListQueryData, TUseListQueryError>>;
|
||||
} & UseLoadingOvertimeOptionsProps;
|
||||
|
||||
export type UseListQueryResult<TUseListQueryData, TUseListQueryError> = UseQueryResult<
|
||||
TUseListQueryData,
|
||||
TUseListQueryError
|
||||
> & {
|
||||
isEmpty: boolean;
|
||||
} & UseLoadingOvertimeReturnType;
|
||||
|
||||
/**
|
||||
* Hook para manejar consultas de listas con React Query,
|
||||
* incluye detección de listas vacías y control de sobretiempo de carga.
|
||||
*/
|
||||
export const useList = <TUseListQueryData, TUseListQueryError>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled,
|
||||
refetchInterval,
|
||||
select,
|
||||
queryOptions = {},
|
||||
overtimeOptions,
|
||||
}: UseListQueryOptions<TUseListQueryData, TUseListQueryError>): UseListQueryResult<
|
||||
TUseListQueryData,
|
||||
TUseListQueryError
|
||||
> => {
|
||||
if (!queryFn) {
|
||||
console.error("queryFn es requerido en useList");
|
||||
throw new Error("queryFn es requerido en useList");
|
||||
}
|
||||
|
||||
const queryResponse = useQuery<TUseListQueryData, TUseListQueryError>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
placeholderData: keepPreviousData,
|
||||
staleTime: DEFAULT_STALE_TIME,
|
||||
refetchInterval: refetchInterval ?? DEFAULT_REFETCH_INTERVAL,
|
||||
refetchOnWindowFocus: true,
|
||||
enabled: enabled && !!queryFn,
|
||||
select,
|
||||
...queryOptions,
|
||||
});
|
||||
|
||||
const { elapsedTime } = useLoadingOvertime({
|
||||
isPending: queryResponse.isFetching,
|
||||
interval: overtimeOptions?.interval,
|
||||
onInterval: overtimeOptions?.onInterval,
|
||||
});
|
||||
|
||||
const isEmpty =
|
||||
queryResponse.isSuccess &&
|
||||
isResponseAListDTO(queryResponse.data) &&
|
||||
queryResponse.data.total_items === 0;
|
||||
|
||||
const result = {
|
||||
...queryResponse,
|
||||
overtime: { elapsedTime },
|
||||
isEmpty,
|
||||
};
|
||||
return result;
|
||||
};
|
||||
@ -0,0 +1,101 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type UseLoadingOvertimeRefineContext = Omit<
|
||||
UseLoadingOvertimeCoreProps,
|
||||
"isPending" | "interval"
|
||||
> &
|
||||
Required<Pick<UseLoadingOvertimeCoreProps, "interval">>;
|
||||
|
||||
export type UseLoadingOvertimeOptionsProps = {
|
||||
overtimeOptions?: UseLoadingOvertimeCoreOptions;
|
||||
};
|
||||
|
||||
export type UseLoadingOvertimeReturnType = {
|
||||
overtime: {
|
||||
elapsedTime?: number;
|
||||
};
|
||||
};
|
||||
|
||||
type UseLoadingOvertimeCoreOptions = Omit<UseLoadingOvertimeCoreProps, "isPending">;
|
||||
|
||||
type UseLoadingOvertimeCoreReturnType = {
|
||||
elapsedTime?: number;
|
||||
};
|
||||
|
||||
export type UseLoadingOvertimeCoreProps = {
|
||||
/**
|
||||
* The pengind state. If true, the elapsed time will be calculated.
|
||||
*/
|
||||
isPending: boolean;
|
||||
|
||||
/**
|
||||
* The interval in milliseconds. If the pending time exceeds this time, the `onInterval` callback will be called.
|
||||
* If not specified, the `interval` value from the `overtime` option of the `RefineProvider` will be used.
|
||||
*
|
||||
* @default: 1000 (1 second)
|
||||
*/
|
||||
interval?: number;
|
||||
|
||||
/**
|
||||
* The callback function that will be called when the pending time exceeds the specified time.
|
||||
* If not specified, the `onInterval` value from the `overtime` option of the `RefineProvider` will be used.
|
||||
*
|
||||
* @param elapsedInterval The elapsed time in milliseconds.
|
||||
*/
|
||||
onInterval?: (elapsedInterval: number) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* if you need to do something when the loading time exceeds the specified time, refine provides the `useLoadingOvertime` hook.
|
||||
* It returns the elapsed time in milliseconds.
|
||||
*
|
||||
* @example
|
||||
* const { elapsedTime } = useLoadingOvertime({
|
||||
* isLoading,
|
||||
* interval: 1000,
|
||||
* onInterval(elapsedInterval) {
|
||||
* console.log("loading overtime", elapsedInterval);
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export const useLoadingOvertime = ({
|
||||
isPending,
|
||||
interval = 1000,
|
||||
onInterval,
|
||||
}: UseLoadingOvertimeCoreProps): UseLoadingOvertimeCoreReturnType => {
|
||||
const [elapsedTime, setElapsedTime] = useState<number | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
let intervalFn: ReturnType<typeof setInterval>;
|
||||
|
||||
if (isPending) {
|
||||
intervalFn = setInterval(() => {
|
||||
// increase elapsed time
|
||||
setElapsedTime((prevElapsedTime) => {
|
||||
if (prevElapsedTime === undefined) {
|
||||
return interval;
|
||||
}
|
||||
|
||||
return prevElapsedTime + interval;
|
||||
});
|
||||
}, interval);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalFn);
|
||||
// reset elapsed time
|
||||
setElapsedTime(undefined);
|
||||
};
|
||||
}, [isPending, interval]);
|
||||
|
||||
useEffect(() => {
|
||||
// call onInterval callback
|
||||
if (onInterval && elapsedTime) {
|
||||
onInterval(elapsedTime);
|
||||
}
|
||||
}, [onInterval, elapsedTime]);
|
||||
|
||||
return {
|
||||
elapsedTime,
|
||||
};
|
||||
};
|
||||
5
modules/core/src/web/hooks/use-query-key/index.ts
Normal file
5
modules/core/src/web/hooks/use-query-key/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { keys } from "./key-builder";
|
||||
|
||||
export const useQueryKey = () => {
|
||||
return keys;
|
||||
};
|
||||
181
modules/core/src/web/hooks/use-query-key/key-builder.ts
Normal file
181
modules/core/src/web/hooks/use-query-key/key-builder.ts
Normal file
@ -0,0 +1,181 @@
|
||||
type BaseKey = string | number;
|
||||
|
||||
type ParametrizedDataActions = "list" | "infinite";
|
||||
type IdRequiredDataActions = "one" | "report" | "upload";
|
||||
type IdsRequiredDataActions = "many";
|
||||
type DataMutationActions =
|
||||
| "custom"
|
||||
| "customMutation"
|
||||
| "create"
|
||||
| "createMany"
|
||||
| "update"
|
||||
| "updateMany"
|
||||
| "delete"
|
||||
| "deleteMany";
|
||||
|
||||
type AuthActionType =
|
||||
| "login"
|
||||
| "logout"
|
||||
| "profile"
|
||||
| "register"
|
||||
| "forgotPassword"
|
||||
| "check"
|
||||
| "onError"
|
||||
| "permissions"
|
||||
| "updatePassword";
|
||||
|
||||
type AuditActionType = "list" | "log" | "rename";
|
||||
|
||||
type IdType = BaseKey;
|
||||
type IdsType = IdType[];
|
||||
|
||||
type ParamsType = any;
|
||||
|
||||
type KeySegment = string | IdType | IdsType | ParamsType;
|
||||
|
||||
export function arrayFindIndex<T>(array: T[], slice: T[]): number {
|
||||
return array.findIndex(
|
||||
(_, index) =>
|
||||
index <= array.length - slice.length &&
|
||||
slice.every((sliceItem, sliceIndex) => array[index + sliceIndex] === sliceItem)
|
||||
);
|
||||
}
|
||||
|
||||
export function arrayReplace<T>(array: T[], partToBeReplaced: T[], newPart: T[]): T[] {
|
||||
const newArray: T[] = [...array];
|
||||
const startIndex = arrayFindIndex(array, partToBeReplaced);
|
||||
|
||||
if (startIndex !== -1) {
|
||||
newArray.splice(startIndex, partToBeReplaced.length, ...newPart);
|
||||
}
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
export function stripUndefined(segments: KeySegment[]) {
|
||||
return segments.filter((segment) => segment !== undefined);
|
||||
}
|
||||
|
||||
class BaseKeyBuilder {
|
||||
segments: KeySegment[] = [];
|
||||
|
||||
constructor(segments: KeySegment[] = []) {
|
||||
this.segments = segments;
|
||||
}
|
||||
|
||||
key() {
|
||||
return this.segments;
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.segments;
|
||||
}
|
||||
}
|
||||
|
||||
class ParamsKeyBuilder extends BaseKeyBuilder {
|
||||
params(paramsValue?: ParamsType) {
|
||||
return new BaseKeyBuilder([...this.segments, paramsValue]);
|
||||
}
|
||||
}
|
||||
|
||||
class DataIdRequiringKeyBuilder extends BaseKeyBuilder {
|
||||
id(idValue?: IdType) {
|
||||
return new ParamsKeyBuilder([...this.segments, idValue ? String(idValue) : undefined]);
|
||||
}
|
||||
}
|
||||
|
||||
class DataIdsRequiringKeyBuilder extends BaseKeyBuilder {
|
||||
ids(...idsValue: IdsType) {
|
||||
return new ParamsKeyBuilder([
|
||||
...this.segments,
|
||||
...(idsValue.length ? [idsValue.map((el) => String(el))] : []),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class DataResourceKeyBuilder extends BaseKeyBuilder {
|
||||
action(actionType: ParametrizedDataActions): ParamsKeyBuilder;
|
||||
action(actionType: IdRequiredDataActions): DataIdRequiringKeyBuilder;
|
||||
action(actionType: IdsRequiredDataActions): DataIdsRequiringKeyBuilder;
|
||||
action(
|
||||
actionType: ParametrizedDataActions | IdRequiredDataActions | IdsRequiredDataActions
|
||||
): ParamsKeyBuilder | DataIdRequiringKeyBuilder | DataIdsRequiringKeyBuilder {
|
||||
if (["one", "report"].includes(actionType)) {
|
||||
return new DataIdRequiringKeyBuilder([...this.segments, actionType]);
|
||||
}
|
||||
if (actionType === "many") {
|
||||
return new DataIdsRequiringKeyBuilder([...this.segments, actionType]);
|
||||
}
|
||||
if (["list", "infinite"].includes(actionType)) {
|
||||
return new ParamsKeyBuilder([...this.segments, actionType]);
|
||||
}
|
||||
throw new Error("Invalid action type");
|
||||
}
|
||||
}
|
||||
|
||||
class DataKeyBuilder extends BaseKeyBuilder {
|
||||
resource(resourceName?: string) {
|
||||
return new DataResourceKeyBuilder([...this.segments, resourceName]);
|
||||
}
|
||||
|
||||
mutation(mutationName: DataMutationActions) {
|
||||
return new ParamsKeyBuilder([
|
||||
...(mutationName === "custom" ? this.segments : [this.segments[0]]),
|
||||
mutationName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthKeyBuilder extends BaseKeyBuilder {
|
||||
action(actionType: AuthActionType) {
|
||||
return new ParamsKeyBuilder([...this.segments, actionType]);
|
||||
}
|
||||
}
|
||||
|
||||
class AccessResourceKeyBuilder extends BaseKeyBuilder {
|
||||
action(resourceName: string) {
|
||||
return new ParamsKeyBuilder([...this.segments, resourceName]);
|
||||
}
|
||||
}
|
||||
|
||||
class AccessKeyBuilder extends BaseKeyBuilder {
|
||||
resource(resourceName?: string) {
|
||||
return new AccessResourceKeyBuilder([...this.segments, resourceName]);
|
||||
}
|
||||
}
|
||||
|
||||
class AuditActionKeyBuilder extends BaseKeyBuilder {
|
||||
action(actionType: Extract<AuditActionType, "list">) {
|
||||
return new ParamsKeyBuilder([...this.segments, actionType]);
|
||||
}
|
||||
}
|
||||
|
||||
class AuditKeyBuilder extends BaseKeyBuilder {
|
||||
resource(resourceName?: string) {
|
||||
return new AuditActionKeyBuilder([...this.segments, resourceName]);
|
||||
}
|
||||
|
||||
action(actionType: Extract<AuditActionType, "rename" | "log">) {
|
||||
return new ParamsKeyBuilder([...this.segments, actionType]);
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyBuilder extends BaseKeyBuilder {
|
||||
data(name?: string) {
|
||||
return new DataKeyBuilder(["data", name || "default"]);
|
||||
}
|
||||
|
||||
auth() {
|
||||
return new AuthKeyBuilder(["auth"]);
|
||||
}
|
||||
|
||||
access() {
|
||||
return new AccessKeyBuilder(["access"]);
|
||||
}
|
||||
|
||||
audit() {
|
||||
return new AuditKeyBuilder(["audit"]);
|
||||
}
|
||||
}
|
||||
|
||||
export const keys = () => new KeyBuilder([]);
|
||||
@ -8,14 +8,17 @@
|
||||
"./api": "./src/api/index.ts",
|
||||
"./dto": "./src/common/dto/index.ts",
|
||||
"./manifest": "./src/web/manifest.ts",
|
||||
"./hooks/*": ["./src/web/hooks/*.tsx", "./src/hooks/*.ts"],
|
||||
"./hooks/*": [
|
||||
"./src/web/hooks/*.tsx",
|
||||
"./src/hooks/*.ts"
|
||||
],
|
||||
"./components": "./src/web/components/index.tsx",
|
||||
"./components/*": "./src/web/components/*.tsx",
|
||||
"./locales": "./src/common/locales/index.tsx"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ag-grid-react": "^33.3.0",
|
||||
"ag-grid-community": "^33.3.0",
|
||||
"ag-grid-react": "^33.3.0",
|
||||
"i18next": "^25.1.1",
|
||||
"react": "^18 || ^19",
|
||||
"react-dom": "^18 || ^19",
|
||||
@ -38,6 +41,8 @@
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-router-dom": "^6.26.0"
|
||||
"react-router-dom": "^6.26.0",
|
||||
"slugify": "^1.6.6",
|
||||
"zod": "^3.24.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { logger } from "@/core/logger";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { ICreateInvoiceRequestDTO } from "../presentation/dto";
|
||||
import { ICreateInvoiceRequestDTO } from "../../common/dto";
|
||||
|
||||
export class CreateInvoiceUseCase {
|
||||
constructor(
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { IUpdateInvoiceRequestDTO } from "../../common/dto";
|
||||
import { IInvoiceService, Invoice } from "../domain";
|
||||
import { IUpdateInvoiceRequestDTO } from "../presentation/dto";
|
||||
|
||||
export class CreateInvoiceUseCase {
|
||||
constructor(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CreateInvoiceUseCase } from "@/contexts/invoices/application/create-invoice.use-case";
|
||||
import { ExpressController, UniqueID } from "@/core";
|
||||
import { ICreateInvoiceRequestDTO } from "../../dto";
|
||||
import { ICreateInvoiceRequestDTO } from "../../../../common/dto";
|
||||
import { ICreateInvoicePresenter } from "./presenter";
|
||||
|
||||
export class CreateInvoiceController extends ExpressController {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Invoice } from "@/contexts/invoices/domain";
|
||||
import { ICreateInvoiceResponseDTO } from "../../../dto";
|
||||
import { ICreateInvoiceResponseDTO } from "../../../../../common/dto";
|
||||
|
||||
export interface ICreateInvoicePresenter {
|
||||
toDTO: (invoice: Invoice) => ICreateInvoiceResponseDTO;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { IGetInvoiceResponseDTO } from "../../../../../common/dto";
|
||||
import { Invoice, InvoiceItem } from "../../../../domain";
|
||||
import { IGetInvoiceResponseDTO } from "../../../dto";
|
||||
|
||||
export interface IGetInvoicePresenter {
|
||||
toDTO: (invoice: Invoice) => IGetInvoiceResponseDTO;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Invoice } from "@/contexts/invoices/domain";
|
||||
import { Collection } from "@/core";
|
||||
import { IListInvoicesResponseDTO } from "../../../dto";
|
||||
import { IListInvoicesResponseDTO } from "../../../../../common/dto";
|
||||
|
||||
export interface IListInvoicesPresenter {
|
||||
toDTO: (invoices: Collection<Invoice>) => IListInvoicesResponseDTO[];
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export * from "./controllers";
|
||||
export * from "./dto";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { IMoneyDTO, IQuantityDTO } from "@/core/common/presentation";
|
||||
import { IMoneyDTO, IQuantityDTO } from "@erp/core";
|
||||
|
||||
export interface IListInvoicesResponseDTO {
|
||||
id: string;
|
||||
@ -1,15 +1,10 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
import { SectionCards } from "@repo/rdx-ui/components/layout/section-cards";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InvoicesProvider } from "../hooks";
|
||||
|
||||
export const InvoicesLayout = ({ children }: PropsWithChildren) => {
|
||||
const { t } = useTranslation("invoices");
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionCards />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
return <InvoicesProvider>{children}</InvoicesProvider>;
|
||||
};
|
||||
|
||||
@ -15,7 +15,7 @@ export const InvoicesProvider = ({ children }: PropsWithChildren) => {
|
||||
setPagination,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<div className='invoices-layout'>{children}</div>
|
||||
</InvoicesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
82
modules/invoices/src/web/hooks/useInvoices.tsx
Normal file
82
modules/invoices/src/web/hooks/useInvoices.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import { IListResponseDTO } from "@erp/core";
|
||||
import {
|
||||
IGetListDataProviderParams,
|
||||
UseListQueryResult,
|
||||
useDataSource,
|
||||
useList,
|
||||
useQueryKey,
|
||||
} from "@erp/core/hooks";
|
||||
import { IListInvoicesResponseDTO } from "@erp/invoices/common/dto";
|
||||
|
||||
export type UseInvoicesListParams = Omit<IGetListDataProviderParams, "filters" | "resource"> & {
|
||||
status?: string;
|
||||
enabled?: boolean;
|
||||
queryOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type UseInvoicesListResponse = UseListQueryResult<
|
||||
IListResponseDTO<IListInvoicesResponseDTO>,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseInvoicesGetParamsType = {
|
||||
enabled?: boolean;
|
||||
queryOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type UseInvoicesReportParamsType = {
|
||||
enabled?: boolean;
|
||||
queryOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export const useInvoices = () => {
|
||||
const actions = {
|
||||
/**
|
||||
* Hook para obtener la lista de facturas
|
||||
* @param params - Parámetros para la consulta de la lista de facturas
|
||||
* @returns - Respuesta de la consulta de la lista de facturas
|
||||
*/
|
||||
useList: (params: UseInvoicesListParams): UseInvoicesListResponse => {
|
||||
const dataSource = useDataSource();
|
||||
const keys = useQueryKey();
|
||||
|
||||
const {
|
||||
pagination,
|
||||
status = "draft",
|
||||
quickSearchTerm = undefined,
|
||||
enabled = true,
|
||||
queryOptions,
|
||||
} = params;
|
||||
|
||||
return useList({
|
||||
queryKey: keys().data().resource("invoices").action("list").params(params).get(),
|
||||
queryFn: () => {
|
||||
return dataSource.getList({
|
||||
resource: "invoices",
|
||||
quickSearchTerm,
|
||||
filters:
|
||||
status !== "all"
|
||||
? [
|
||||
{
|
||||
field: "status",
|
||||
operator: "eq",
|
||||
value: status,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
field: "status",
|
||||
operator: "ne",
|
||||
value: "archived",
|
||||
},
|
||||
],
|
||||
pagination,
|
||||
});
|
||||
},
|
||||
enabled,
|
||||
queryOptions,
|
||||
});
|
||||
},
|
||||
};
|
||||
return actions;
|
||||
};
|
||||
@ -1 +1 @@
|
||||
export * from "./invoices-list";
|
||||
export * from "./list";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Result, UndefinedOr } from "@repo/rdx-utils";
|
||||
import { ResultCollection, RuleValidator, StringValueObject } from "../helpers";
|
||||
import { Result, ResultCollection, RuleValidator, UndefinedOr } from "@repo/rdx-utils";
|
||||
import { StringValueObject } from "../helpers";
|
||||
import { Filter, IFilter } from "./Filter";
|
||||
|
||||
export interface IFilterCriteria {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Result, RuleValidator } from "@repo/rdx-utils";
|
||||
import Joi from "joi";
|
||||
import { RuleValidator } from "../helpers";
|
||||
|
||||
export interface IOrderProps {
|
||||
type: string;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Result, UndefinedOr } from "@repo/rdx-utils";
|
||||
import { ResultCollection, RuleValidator, StringValueObject } from "../helpers";
|
||||
import { Result, ResultCollection, RuleValidator, UndefinedOr } from "@repo/rdx-utils";
|
||||
import { StringValueObject } from "../helpers";
|
||||
import { IOrderProps, Order } from "./Order";
|
||||
import { IOrderCollection, OrderCollection } from "./OrderCollection";
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { DomainError } from "../../../errors";
|
||||
import { Result } from "../../Result";
|
||||
import { ValueObject } from "../../ValueObject";
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Order } from "./Order";
|
||||
import { OrderCollection } from "./OrderCollection";
|
||||
|
||||
@ -17,19 +16,16 @@ export interface IOrderRoot {
|
||||
toObject(): Record<string, any>;
|
||||
}
|
||||
|
||||
export class OrderRoot
|
||||
extends ValueObject<IOrderRootProps>
|
||||
implements IOrderRoot
|
||||
{
|
||||
export class OrderRoot extends ValueObject<IOrderRootProps> implements IOrderRoot {
|
||||
public static createASC(value: Order[]): Result<IOrderRoot> {
|
||||
return this.create({
|
||||
return OrderRoot.create({
|
||||
type: "asc",
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
public static createDESC(value: Order[]): Result<IOrderRoot> {
|
||||
return this.create({
|
||||
return OrderRoot.create({
|
||||
type: "desc",
|
||||
value,
|
||||
});
|
||||
@ -37,7 +33,7 @@ export class OrderRoot
|
||||
|
||||
public static create(orderRootProps: any): Result<IOrderRoot> {
|
||||
// Validación de props
|
||||
const valid = this.validate(orderRootProps);
|
||||
const valid = OrderRoot.validate(orderRootProps);
|
||||
if (valid.isFailure) {
|
||||
return Result.fail(valid.error);
|
||||
}
|
||||
@ -51,7 +47,7 @@ export class OrderRoot
|
||||
}
|
||||
|
||||
protected static validate(OrderRootRoot: IOrderRootProps): Result<any> {
|
||||
throw DomainError.create("NOT IMPLEMENT", "OrderRoot.validate()");
|
||||
throw new Error("NOT IMPLEMENT OrderRoot.validate()");
|
||||
|
||||
/*return Validator.isOneOf(
|
||||
{
|
||||
@ -75,6 +71,10 @@ export class OrderRoot
|
||||
this._items = props.items;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import Joi from "joi";
|
||||
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { RuleValidator } from "../helpers";
|
||||
import { Result, RuleValidator } from "@repo/rdx-utils";
|
||||
import {
|
||||
INITIAL_PAGE_INDEX,
|
||||
INITIAL_PAGE_SIZE,
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result, UndefinedOr } from "@repo/rdx-utils";
|
||||
import { Result, RuleValidator, UndefinedOr } from "@repo/rdx-utils";
|
||||
import Joi from "joi";
|
||||
import { RuleValidator } from "../helpers";
|
||||
|
||||
export interface IQuickSearchCriteria {
|
||||
searchTerms: string[];
|
||||
|
||||
@ -1,3 +1 @@
|
||||
export * from "./RuleValidator";
|
||||
export * from "./ResultCollection";
|
||||
export * from "./StringValueObject";
|
||||
|
||||
@ -13,5 +13,11 @@
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/node": "^22.15.12",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"joi": "^17.13.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"joi": "^17.13.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export * from "./collection";
|
||||
export * from "./maybe";
|
||||
export * from "./result";
|
||||
export * from "./result-collection";
|
||||
export * from "./rule-validator";
|
||||
export * from "./utils";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Result } from "./result";
|
||||
|
||||
export interface IResultCollection<T, E extends Error = Error> {
|
||||
add(result: Result<T, E>): void;
|
||||
@ -1,5 +1,5 @@
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import Joi, { ValidationError } from "joi";
|
||||
import { Result } from "./result";
|
||||
|
||||
export type TRuleValidatorResult<T> = Result<T, ValidationError>;
|
||||
|
||||
@ -110,6 +110,7 @@ function SidebarProvider({
|
||||
// This makes it easier to style the sidebar with Tailwind classes.
|
||||
const state = open ? "expanded" : "collapsed";
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||
const contextValue = React.useMemo<SidebarContextProps>(
|
||||
() => ({
|
||||
state,
|
||||
|
||||
@ -338,6 +338,18 @@ importers:
|
||||
'@repo/rdx-criteria':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-criteria
|
||||
'@repo/rdx-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/rdx-utils
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.75.4
|
||||
version: 5.75.4(react@19.1.0)
|
||||
axios:
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
joi:
|
||||
specifier: ^17.13.3
|
||||
version: 17.13.3
|
||||
react:
|
||||
specifier: ^19.1.0
|
||||
version: 19.1.0
|
||||
@ -357,6 +369,9 @@ importers:
|
||||
'@testing-library/react-hooks':
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@types/axios':
|
||||
specifier: ^0.14.4
|
||||
version: 0.14.4
|
||||
'@types/jest':
|
||||
specifier: 29.5.14
|
||||
version: 29.5.14
|
||||
@ -405,6 +420,12 @@ importers:
|
||||
react-router-dom:
|
||||
specifier: ^6.26.0
|
||||
version: 6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
slugify:
|
||||
specifier: ^1.6.6
|
||||
version: 1.6.6
|
||||
zod:
|
||||
specifier: ^3.24.4
|
||||
version: 3.24.4
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: 1.9.4
|
||||
@ -570,6 +591,10 @@ importers:
|
||||
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.12)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.29.2)(sass@1.87.0)(stylus@0.62.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1))
|
||||
|
||||
packages/rdx-utils:
|
||||
dependencies:
|
||||
joi:
|
||||
specifier: ^17.13.3
|
||||
version: 17.13.3
|
||||
devDependencies:
|
||||
'@repo/typescript-config':
|
||||
specifier: workspace:*
|
||||
@ -2611,6 +2636,10 @@ packages:
|
||||
resolution: {integrity: sha512-NxPRAT/mywJ6agqLuVsOag1btEUbPYacVqCndQjvkm5EN0DfjvBIYCsXA/i2Q+Z0hqX84UeIIfIXAQiXpAXZmA==}
|
||||
hasBin: true
|
||||
|
||||
'@types/axios@0.14.4':
|
||||
resolution: {integrity: sha512-9JgOaunvQdsQ/qW2OPmE5+hCeUB52lQSolecrFrthct55QekhmXEwT203s20RL+UHtCQc15y3VXpby9E7Kkh/g==}
|
||||
deprecated: This is a stub types definition. axios provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@ -5468,6 +5497,10 @@ packages:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
slugify@1.6.6:
|
||||
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
@ -8028,6 +8061,12 @@ snapshots:
|
||||
semver: 7.6.2
|
||||
update-check: 1.5.4
|
||||
|
||||
'@types/axios@0.14.4':
|
||||
dependencies:
|
||||
axios: 1.9.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.27.1
|
||||
@ -11136,6 +11175,8 @@ snapshots:
|
||||
|
||||
slash@3.0.0: {}
|
||||
|
||||
slugify@1.6.6: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
snake-case@2.1.0:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user