.
This commit is contained in:
parent
e8352ae24e
commit
eabd63ec09
1
apps/web/.env.development
Normal file
1
apps/web/.env.development
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_API_SERVER_URL=http://192.168.0.104:3002/api/v1
|
||||||
@ -8,6 +8,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
||||||
<title>FactuGES 2025</title>
|
<title>FactuGES 2025</title>
|
||||||
|
<!-- FAVICONS -->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -12,8 +12,26 @@
|
|||||||
"lint": "biome lint --fix",
|
"lint": "biome lint --fix",
|
||||||
"format": "biome format --write"
|
"format": "biome format --write"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "1.9.4",
|
||||||
|
"@hookform/devtools": "^4.4.0",
|
||||||
|
"@peterek/vite-plugin-favicons": "^2.1.0",
|
||||||
|
"@repo/typescript-config": "workspace:*",
|
||||||
|
"@tailwindcss/postcss": "^4.1.5",
|
||||||
|
"@tailwindcss/vite": "^4.1.6",
|
||||||
|
"@tanstack/react-query-devtools": "^5.74.11",
|
||||||
|
"@types/node": "^22.15.12",
|
||||||
|
"@types/react": "^19.1.2",
|
||||||
|
"@types/react-dom": "^19.1.3",
|
||||||
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"globals": "^16.0.0",
|
||||||
|
"typescript": "~5.8.3",
|
||||||
|
"vite": "^6.3.5"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
|
"@erp/auth": "workspace:*",
|
||||||
"@erp/invoices": "workspace:*",
|
"@erp/invoices": "workspace:*",
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
"@repo/rdx-ui": "workspace:*",
|
"@repo/rdx-ui": "workspace:*",
|
||||||
@ -28,28 +46,10 @@
|
|||||||
"react-hook-form-persist": "^3.0.0",
|
"react-hook-form-persist": "^3.0.0",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
"react-secure-storage": "^1.3.2",
|
"sequelize": "^6.37.5",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^4.1.6",
|
"tailwindcss": "^4.1.6",
|
||||||
"tw-animate-css": "^1.2.9",
|
"tw-animate-css": "^1.2.9",
|
||||||
"vite-plugin-html": "^3.2.2"
|
"vite-plugin-html": "^3.2.2"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@biomejs/biome": "1.9.4",
|
|
||||||
"@hookform/devtools": "^4.4.0",
|
|
||||||
"@repo/typescript-config": "workspace:*",
|
|
||||||
"@tailwindcss/postcss": "^4.1.5",
|
|
||||||
"@tailwindcss/vite": "^4.1.6",
|
|
||||||
"@tanstack/react-query-devtools": "^5.74.11",
|
|
||||||
"@types/node": "^22.15.12",
|
|
||||||
"@types/react": "^19.1.2",
|
|
||||||
"@types/react-dom": "^19.1.3",
|
|
||||||
"@vitejs/plugin-react": "^4.4.1",
|
|
||||||
"autoprefixer": "^10.4.20",
|
|
||||||
"globals": "^16.0.0",
|
|
||||||
"typescript": "~5.8.3",
|
|
||||||
"vite": "^6.3.5",
|
|
||||||
"vite-plugin-robots": "^1.0.5",
|
|
||||||
"vite-plugin-static-copy": "^2.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB |
@ -3,14 +3,19 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
|
|
||||||
import { createAxiosDataProvider } from "@/lib/axios/create-axios-data-provider";
|
|
||||||
import { UnsavedWarnProvider } from "@/lib/hooks";
|
import { UnsavedWarnProvider } from "@/lib/hooks";
|
||||||
import { i18n } from "@/locales";
|
import { i18n } from "@/locales";
|
||||||
|
|
||||||
import { DataSourceProvider } from "@erp/core/hooks";
|
import { clearAccessToken, getAccessToken } from "@erp/auth/client";
|
||||||
|
import { DataSourceProvider, createAxiosDataSource, createAxiosInstance } from "@erp/core/client";
|
||||||
import { AppRoutes } from "./app-routes";
|
import { AppRoutes } from "./app-routes";
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clave utilizada en el almacenamiento local para el token JWT.
|
||||||
|
*/
|
||||||
|
const TOKEN_STORAGE_KEY = "factuges.auth";
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@ -21,17 +26,26 @@ export const App = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dataSource = createAxiosDataSource(createAxiosInstance({
|
||||||
|
baseURL: import.meta.env.VITE_API_URL,
|
||||||
|
getAccessToken: () => getAccessToken(TOKEN_STORAGE_KEY),
|
||||||
|
onAuthError: () => {
|
||||||
|
clearAccessToken(TOKEN_STORAGE_KEY);
|
||||||
|
window.location.href = "/login"; // o usar navegación programática
|
||||||
|
},
|
||||||
|
}););
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<DataSourceProvider dataSource={createAxiosDataProvider(import.meta.env.VITE_API_URL)}>
|
<DataSourceProvider dataSource={dataSource}>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<UnsavedWarnProvider>
|
<UnsavedWarnProvider>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</UnsavedWarnProvider>
|
</UnsavedWarnProvider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
{import.meta.env.MODE === "development" && <ReactQueryDevtools initialIsOpen={false} />}
|
{import.meta.env.DEV && <ReactQueryDevtools initialIsOpen={false} />}
|
||||||
</DataSourceProvider>
|
</DataSourceProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
import type { ILoginResponseDTO } from "@erp/auth";
|
|
||||||
import secureLocalStorage from "react-secure-storage";
|
|
||||||
|
|
||||||
export const getApiAuthorization = () => {
|
|
||||||
const authInfo = secureLocalStorage.getItem("uecko.auth") as ILoginResponseDTO;
|
|
||||||
|
|
||||||
return authInfo?.token ? `Bearer ${authInfo.token}` : "";
|
|
||||||
};
|
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./get-api-authorization";
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import axiosClient from "axios";
|
|
||||||
import { setupInterceptorsTo } from "./setup-interceptors";
|
|
||||||
|
|
||||||
// extend the AxiosRequestConfig interface and add two optional options raw and silent. I
|
|
||||||
// https://dev.to/mperon/axios-error-handling-like-a-boss-333d
|
|
||||||
declare module "axios" {
|
|
||||||
export interface AxiosRequestConfig {
|
|
||||||
raw?: boolean;
|
|
||||||
silent?: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultAxiosRequestConfig = {
|
|
||||||
mode: "cors",
|
|
||||||
cache: "no-cache",
|
|
||||||
credentials: "same-origin",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json; charset=utf-8",
|
|
||||||
"Cache-Control": "no-cache",
|
|
||||||
"Access-Control-Allow-Origin": "*", // Could work and fix the previous problem, but not in all APIs
|
|
||||||
//'api-key': SERVER_API_KEY,
|
|
||||||
},
|
|
||||||
//timeout: 300,
|
|
||||||
|
|
||||||
// `onUploadProgress` allows handling of progress events for uploads
|
|
||||||
// browser only
|
|
||||||
//onUploadProgress: function (progressEvent) {
|
|
||||||
// Do whatever you want with the native progress event
|
|
||||||
//},
|
|
||||||
|
|
||||||
// `onDownloadProgress` allows handling of progress events for downloads
|
|
||||||
// browser only
|
|
||||||
//onDownloadProgress: function (progressEvent) {
|
|
||||||
// Do whatever you want with the native progress event
|
|
||||||
//},
|
|
||||||
|
|
||||||
// `cancelToken` specifies a cancel token that can be used to cancel the request
|
|
||||||
/*cancelToken: new CancelToken(function (cancel) {
|
|
||||||
}),*/
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an initial 'axios' instance with custom settings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const createAxiosInstance = () =>
|
|
||||||
setupInterceptorsTo(axiosClient.create(defaultAxiosRequestConfig));
|
|
||||||
@ -1,101 +1,51 @@
|
|||||||
import secureLocalStorage from "react-secure-storage";
|
import { AxiosInstance } from "axios";
|
||||||
import { createAxiosInstance } from "./axios-instance";
|
|
||||||
|
|
||||||
export const createAxiosAuthActions = (
|
|
||||||
apiUrl: string,
|
|
||||||
httpClient = createAxiosInstance()
|
|
||||||
): IAuthActions => ({
|
|
||||||
login: async ({ email, password }: ILogin_DTO) => {
|
|
||||||
secureLocalStorage.clear();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await httpClient.request<ILogin_Response_DTO>({
|
|
||||||
url: `${apiUrl}/auth/login`,
|
|
||||||
method: "POST",
|
|
||||||
data: {
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data } = result;
|
|
||||||
secureLocalStorage.setItem("uecko.auth", data);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: {
|
|
||||||
message: "Login failed",
|
|
||||||
name: "Invalid email or password",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
logout: () => {
|
|
||||||
secureLocalStorage.clear();
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
success: true,
|
|
||||||
redirectTo: "/login",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
check: () => {
|
|
||||||
const authUser = secureLocalStorage.getItem("uecko.auth") as ILogin_Response_DTO;
|
|
||||||
const isAuthenticated = !!authUser?.token;
|
|
||||||
|
|
||||||
if (!isAuthenticated) secureLocalStorage.clear();
|
|
||||||
|
|
||||||
return Promise.resolve(
|
|
||||||
isAuthenticated
|
|
||||||
? {
|
|
||||||
authenticated: true,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
authenticated: false,
|
|
||||||
redirectTo: "/login",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
getProfile: async () => {
|
|
||||||
/**
|
/**
|
||||||
* id: string;
|
* Datos requeridos para iniciar sesión.
|
||||||
* name: string;
|
|
||||||
* email: string;
|
|
||||||
* lang_code: string;
|
|
||||||
* roles: string[];
|
|
||||||
*/
|
*/
|
||||||
|
export interface LoginCredentials {
|
||||||
try {
|
email: string;
|
||||||
const result = await httpClient.request<IGetProfileResponse_DTO>({
|
password: string;
|
||||||
url: `${apiUrl}/profile`,
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: profile } = result;
|
|
||||||
const authUser = secureLocalStorage.getItem("uecko.auth") as ILogin_Response_DTO;
|
|
||||||
|
|
||||||
if (authUser?.id === profile?.id) {
|
|
||||||
secureLocalStorage.setItem("uecko.profile", profile);
|
|
||||||
return Promise.resolve(profile);
|
|
||||||
}
|
}
|
||||||
return Promise.resolve(null);
|
|
||||||
} catch (error) {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onError: (error: any) => {
|
/**
|
||||||
secureLocalStorage.clear();
|
* Respuesta esperada al iniciar sesión correctamente.
|
||||||
return Promise.resolve({
|
*/
|
||||||
error,
|
export interface AuthResponse {
|
||||||
logout: true,
|
access_token: string;
|
||||||
});
|
}
|
||||||
},
|
|
||||||
});
|
/**
|
||||||
|
* Crea funciones de autenticación usando una instancia Axios.
|
||||||
|
*
|
||||||
|
* @param api - Instancia Axios preconfigurada.
|
||||||
|
* @returns Funciones para login y logout.
|
||||||
|
*/
|
||||||
|
export const createAuthActions = (api: AxiosInstance) => {
|
||||||
|
/**
|
||||||
|
* Envía credenciales al endpoint de login y devuelve el token.
|
||||||
|
*
|
||||||
|
* @param credentials - Correo y contraseña del usuario.
|
||||||
|
* @returns Promesa con el token JWT.
|
||||||
|
*/
|
||||||
|
const login = async (credentials: LoginCredentials): Promise<AuthResponse> => {
|
||||||
|
const response = await api.post<AuthResponse>("/auth/login", credentials);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realiza un logout desde el backend si aplica, o simplemente resuelve.
|
||||||
|
* Puede modificarse para llamar a un endpoint si se requiere.
|
||||||
|
*
|
||||||
|
* @returns Promesa resuelta.
|
||||||
|
*/
|
||||||
|
const logout = async (): Promise<void> => {
|
||||||
|
// Ejemplo: await api.post('/auth/logout');
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -1,416 +0,0 @@
|
|||||||
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,
|
|
||||||
ICustomDataProviderParam,
|
|
||||||
IDownloadPDFDataProviderParams,
|
|
||||||
IDownloadPDFDataProviderResponse,
|
|
||||||
IFilterItemDataProviderParam,
|
|
||||||
IGetListDataProviderParams,
|
|
||||||
IGetOneDataProviderParams,
|
|
||||||
IPaginationDataProviderParam,
|
|
||||||
IRemoveOneDataProviderParams,
|
|
||||||
ISortItemDataProviderParam,
|
|
||||||
IUpdateOneDataProviderParams,
|
|
||||||
IUploadFileDataProviderParam,
|
|
||||||
} from "../hooks/use-datasource";
|
|
||||||
import { createAxiosInstance, defaultAxiosRequestConfig } from "./axios-instance";
|
|
||||||
|
|
||||||
export const createAxiosDataProvider = (
|
|
||||||
apiUrl: string,
|
|
||||||
httpClient = createAxiosInstance()
|
|
||||||
): IDataSource => ({
|
|
||||||
name: () => "AxiosDataProvider",
|
|
||||||
|
|
||||||
getApiUrl: () => apiUrl,
|
|
||||||
|
|
||||||
getApiAuthorization: getApiAuthLib,
|
|
||||||
|
|
||||||
getList: async <R>(params: IGetListDataProviderParams): Promise<IListResponseDTO<R>> => {
|
|
||||||
const { resource, quickSearchTerm, pagination, filters = [], sort = [] } = params;
|
|
||||||
|
|
||||||
const url = `${apiUrl}/${resource}`;
|
|
||||||
const urlParams = new URLSearchParams();
|
|
||||||
|
|
||||||
const { page, limit } = extractPaginationParams(pagination);
|
|
||||||
urlParams.append("page", String(page));
|
|
||||||
urlParams.append("limit", String(limit));
|
|
||||||
|
|
||||||
const generatedSort = extractSortParams(sort);
|
|
||||||
if (generatedSort.length) {
|
|
||||||
urlParams.append("$sort_by", generatedSort.join(","));
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryQuickSearch = generateQuickSearch(quickSearchTerm, filters);
|
|
||||||
if (queryQuickSearch.length) {
|
|
||||||
urlParams.append("q", queryQuickSearch.join(","));
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryFilters = extractFilterParams(filters);
|
|
||||||
if (queryFilters.length) {
|
|
||||||
urlParams.append("$filters", queryFilters.join(","));
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await httpClient.request<IListResponseDTO<R>>({
|
|
||||||
url: `${url}?${urlParams.toString()}`,
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
getOne: async <R>(params: IGetOneDataProviderParams): Promise<R> => {
|
|
||||||
const { resource, id } = params;
|
|
||||||
|
|
||||||
const response = await httpClient.request<R>({
|
|
||||||
url: `${apiUrl}/${resource}/${id}`,
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
/*saveOne: async <P, R>(params: ISaveOneDataProviderParams<P>): Promise<R> => {
|
|
||||||
const { resource, data, id } = params;
|
|
||||||
|
|
||||||
console.log(params);
|
|
||||||
|
|
||||||
const result = await httpClient.request<R>({
|
|
||||||
url: `${apiUrl}/${resource}/${id}`,
|
|
||||||
method: "PUT",
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
},*/
|
|
||||||
|
|
||||||
createOne: async <P, R>(params: ICreateOneDataProviderParams<P>): Promise<R> => {
|
|
||||||
const { resource, data } = params;
|
|
||||||
|
|
||||||
const response = await httpClient.request<R>({
|
|
||||||
url: `${apiUrl}/${resource}`,
|
|
||||||
method: "POST",
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateOne: async <P, R>(params: IUpdateOneDataProviderParams<P>): Promise<R> => {
|
|
||||||
const { resource, data, id } = params;
|
|
||||||
|
|
||||||
const response = await httpClient.request<R>({
|
|
||||||
url: `${apiUrl}/${resource}/${id}`,
|
|
||||||
method: "PUT",
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
removeOne: async <R>(params: IRemoveOneDataProviderParams) => {
|
|
||||||
const { resource, id } = params;
|
|
||||||
|
|
||||||
await httpClient.request<R>({
|
|
||||||
url: `${apiUrl}/${resource}/${id}`,
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
uploadFile: async <R>(params: IUploadFileDataProviderParam): Promise<R> => {
|
|
||||||
const { path, file, key, onUploadProgress } = params;
|
|
||||||
const url = `${apiUrl}/${path}`;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append(key || "file", file);
|
|
||||||
|
|
||||||
//console.log(file);
|
|
||||||
|
|
||||||
const response = await httpClient.post<R>(url, formData, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
},
|
|
||||||
onUploadProgress,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
downloadPDF: async (
|
|
||||||
params: IDownloadPDFDataProviderParams
|
|
||||||
): Promise<IDownloadPDFDataProviderResponse> => {
|
|
||||||
const { url, config } = params;
|
|
||||||
|
|
||||||
const response = await httpClient.get<ArrayBuffer>(url, {
|
|
||||||
responseType: "arraybuffer", // Esto es necesario para recibir los datos en formato ArrayBuffer
|
|
||||||
...config,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extraer el nombre del archivo de la cabecera Content-Disposition
|
|
||||||
const contentDisposition = response.headers["content-disposition"];
|
|
||||||
let filename = "downloaded-file.pdf"; // Valor por defecto si no se encuentra el nombre en la cabecera
|
|
||||||
|
|
||||||
if (contentDisposition) {
|
|
||||||
const match = contentDisposition.match(/filename="?(.+)"?/);
|
|
||||||
if (match && match[1]) {
|
|
||||||
filename = match[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear un Blob con los datos descargados
|
|
||||||
const filedata = new Blob([response.data], { type: "application/pdf" });
|
|
||||||
|
|
||||||
return {
|
|
||||||
filename,
|
|
||||||
filedata,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
custom: async <R>(params: ICustomDataProviderParam): Promise<R> => {
|
|
||||||
const { url, path, method, responseType, headers, signal, data, ...payload } = params;
|
|
||||||
let requestUrl: string;
|
|
||||||
|
|
||||||
if (path) {
|
|
||||||
requestUrl = `${apiUrl}/${path}`;
|
|
||||||
} else if (url) {
|
|
||||||
requestUrl = url;
|
|
||||||
} else {
|
|
||||||
throw new Error('"url" or "path" param is missing');
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log(apiUrl, path, url, requestUrl.toString());
|
|
||||||
|
|
||||||
// Preparar la respuesta personalizada
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
let customResponse: any;
|
|
||||||
|
|
||||||
// Configurar opciones comunes para la petición
|
|
||||||
const config = {
|
|
||||||
url: requestUrl.toString(),
|
|
||||||
method,
|
|
||||||
responseType,
|
|
||||||
signal,
|
|
||||||
...payload,
|
|
||||||
...defaultAxiosRequestConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case "put":
|
|
||||||
case "post":
|
|
||||||
case "patch":
|
|
||||||
customResponse = await httpClient.request<R>({
|
|
||||||
...config,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
customResponse = await httpClient.delete<R>(requestUrl.toString(), {
|
|
||||||
responseType,
|
|
||||||
headers,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
customResponse = await httpClient.get<R>(requestUrl.toString(), {
|
|
||||||
responseType,
|
|
||||||
signal,
|
|
||||||
headers,
|
|
||||||
...payload,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return customResponse.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
/*getMany: async ({ resource }) => {
|
|
||||||
const { body } = await httpClient.request({
|
|
||||||
url: `${apiUrl}/${resource}`,
|
|
||||||
method: "GET",
|
|
||||||
//...defaultRequestConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
return body;
|
|
||||||
},*/
|
|
||||||
|
|
||||||
/*create: async ({ resource, values }) => {
|
|
||||||
const url = `${apiUrl}/${resource}`;
|
|
||||||
|
|
||||||
const { body } = await httpClient.post(url, values, defaultRequestConfig);
|
|
||||||
|
|
||||||
return body;
|
|
||||||
},*/
|
|
||||||
|
|
||||||
/*createMany: async ({ resource, values }) => {
|
|
||||||
const response = await Promise.all(
|
|
||||||
values.map(async (param) => {
|
|
||||||
const { body } = await httpClient.post(
|
|
||||||
`${apiUrl}/${resource}`,
|
|
||||||
param
|
|
||||||
//defaultRequestConfig,
|
|
||||||
);
|
|
||||||
return body;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},*/
|
|
||||||
|
|
||||||
/*update: async ({ resource, id, values }) => {
|
|
||||||
const url = `${apiUrl}/${resource}/${id}`;
|
|
||||||
const { body } = await httpClient.patch(url, values, defaultRequestConfig);
|
|
||||||
return body;
|
|
||||||
},*/
|
|
||||||
|
|
||||||
/*updateMany: async ({ resource, ids, values }) => {
|
|
||||||
const response = await Promise.all(
|
|
||||||
ids.map(async (id) => {
|
|
||||||
const { body } = await httpClient.patch(
|
|
||||||
`${apiUrl}/${resource}/${id}`,
|
|
||||||
values
|
|
||||||
//defaultRequestConfig,
|
|
||||||
);
|
|
||||||
return body;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},*/
|
|
||||||
|
|
||||||
// removeMany: async ({ resource, ids }) => {
|
|
||||||
// const url = `${apiUrl}/${resource}/bulk-delete`;
|
|
||||||
|
|
||||||
// const { body } = await httpClient.request({
|
|
||||||
// url,
|
|
||||||
// method: "PATCH",
|
|
||||||
// data: {
|
|
||||||
// ids,
|
|
||||||
// },
|
|
||||||
// //defaultRequestConfig,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return body;
|
|
||||||
// },
|
|
||||||
|
|
||||||
// upload: async ({ resource, file, onUploadProgress }) => {
|
|
||||||
// const url = `${apiUrl}/${resource}`;
|
|
||||||
// const options = {
|
|
||||||
// //...defaultRequestConfig,
|
|
||||||
// onUploadProgress,
|
|
||||||
// headers: {
|
|
||||||
// //...defaultRequestConfig.headers,
|
|
||||||
// "Content-Type": "multipart/form-data",
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const formData = new FormData();
|
|
||||||
// formData.append("file", file);
|
|
||||||
|
|
||||||
// const { body } = await httpClient.post(url, formData, options);
|
|
||||||
// return body;
|
|
||||||
// },
|
|
||||||
|
|
||||||
/*uploadMany: async ({ resource, values }) => {
|
|
||||||
const url = `${apiUrl}/${resource}`;
|
|
||||||
const options = {
|
|
||||||
//...defaultRequestConfig,
|
|
||||||
headers: {
|
|
||||||
...defaultRequestConfig.headers,
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await Promise.all(
|
|
||||||
values.map(async (value) => {
|
|
||||||
const { body } = await httpClient.post(
|
|
||||||
url,
|
|
||||||
value,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
return body;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},*/
|
|
||||||
|
|
||||||
/*custom: async ({ url, method, filters, sort, payload, query, headers }) => {
|
|
||||||
let requestUrl = `${url}?`;
|
|
||||||
|
|
||||||
if (sort) {
|
|
||||||
const generatedSort = extractSortParams(sort);
|
|
||||||
if (generatedSort) {
|
|
||||||
const { _sort, _order } = generatedSort;
|
|
||||||
const sortQuery = {
|
|
||||||
_sort: _sort.join(","),
|
|
||||||
_order: _order.join(","),
|
|
||||||
};
|
|
||||||
requestUrl = `${requestUrl}&${queryString.stringify(sortQuery)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters) {
|
|
||||||
const filterQuery = extractFilterParams(filters);
|
|
||||||
requestUrl = `${requestUrl}&${queryString.stringify(filterQuery)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
requestUrl = `${requestUrl}&${queryString.stringify(query)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headers) {
|
|
||||||
httpClient.defaults.headers = {
|
|
||||||
...httpClient.defaults.headers,
|
|
||||||
...headers,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let axiosResponse;
|
|
||||||
switch (method) {
|
|
||||||
case "put":
|
|
||||||
case "post":
|
|
||||||
case "patch":
|
|
||||||
axiosResponse = await httpClient[method](url, payload);
|
|
||||||
break;
|
|
||||||
case "remove":
|
|
||||||
axiosResponse = await httpClient.delete(url);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
axiosResponse = await httpClient.get(requestUrl);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = axiosResponse;
|
|
||||||
|
|
||||||
return Promise.resolve({ data });
|
|
||||||
},*/
|
|
||||||
});
|
|
||||||
|
|
||||||
const extractSortParams = (sort: ISortItemDataProviderParam[] = []) =>
|
|
||||||
sort.map(({ field, order }) => `${order === "DESC" ? "-" : "+"}${field}`);
|
|
||||||
|
|
||||||
const extractFilterParams = (filters: IFilterItemDataProviderParam[] = []) =>
|
|
||||||
filters
|
|
||||||
.filter(({ field }) => field !== "q")
|
|
||||||
.map(({ field, operator, value }) => `${field}[${operator}]${value}`);
|
|
||||||
|
|
||||||
const generateQuickSearch = (
|
|
||||||
quickSearchTerm: string[] = [],
|
|
||||||
filters: IFilterItemDataProviderParam[] = []
|
|
||||||
) =>
|
|
||||||
filters.find(({ field }) => field === "q")?.value
|
|
||||||
? [filters.find(({ field }) => field === "q")?.value ?? ""]
|
|
||||||
: quickSearchTerm;
|
|
||||||
|
|
||||||
const extractPaginationParams = (pagination?: IPaginationDataProviderParam) => {
|
|
||||||
const { pageIndex = INITIAL_PAGE_INDEX, pageSize = INITIAL_PAGE_SIZE } = pagination || {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
page: pageIndex,
|
|
||||||
limit: pageSize,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
import {
|
|
||||||
AxiosError,
|
|
||||||
type AxiosInstance,
|
|
||||||
type AxiosResponse,
|
|
||||||
type InternalAxiosRequestConfig,
|
|
||||||
} from "axios";
|
|
||||||
import { getApiAuthorization } from "../api";
|
|
||||||
|
|
||||||
const onRequest = (request: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
|
|
||||||
request.headers.Authorization = getApiAuthorization();
|
|
||||||
return request;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRequestError = (error: AxiosError): Promise<AxiosError> => {
|
|
||||||
/*console.group("[request error]");
|
|
||||||
console.dir(error);
|
|
||||||
console.groupEnd();*/
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onResponse = (response: AxiosResponse): AxiosResponse => {
|
|
||||||
/*console.group("[response]");
|
|
||||||
console.dir(response);
|
|
||||||
console.groupEnd();*/
|
|
||||||
|
|
||||||
const config = response?.config;
|
|
||||||
if (config.raw) {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if (response.status === 200) {
|
|
||||||
const data = response?.data;
|
|
||||||
if (!data) {
|
|
||||||
throw new HttpError("API Error. No data!");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}*/
|
|
||||||
//throw new HttpError("API Error! Invalid status code!");
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onResponseError = (error: AxiosError): Promise<AxiosError> => {
|
|
||||||
console.debug("[response error]");
|
|
||||||
|
|
||||||
if (error.response) {
|
|
||||||
// La respuesta fue hecha y el servidor respondió con un código de estado
|
|
||||||
// que esta fuera del rango de 2xx
|
|
||||||
console.debug("1 => El servidor respondió con un código de estado > 200");
|
|
||||||
|
|
||||||
const data = error.response.data;
|
|
||||||
const status = error.response.status;
|
|
||||||
|
|
||||||
console.debug(data);
|
|
||||||
console.debug(status);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
{
|
|
||||||
detail: "Quote data not valid",
|
|
||||||
instance: "/api/v1/quotes",
|
|
||||||
status: 422,
|
|
||||||
title: "Unprocessable Entity",
|
|
||||||
type: "about:blank",
|
|
||||||
context: {
|
|
||||||
params: {
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
date: "2024-08-13",
|
|
||||||
customer_information: "",
|
|
||||||
reference: "",
|
|
||||||
status: "draft",
|
|
||||||
id: "9c1c6073-127a-4bde-a73c-6229efb51ad0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extra: {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
reference: "{reference} is not allowed to be empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_information: "{customer_information} is not allowed to be empty",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case 400:
|
|
||||||
console.error("Bad Request");
|
|
||||||
break;
|
|
||||||
case 401:
|
|
||||||
console.error("UnAuthorized");
|
|
||||||
//window.location.href = "/logout";
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
console.error("Forbidden");
|
|
||||||
break;
|
|
||||||
/*AppEvents.publish(Events.N_Error, {
|
|
||||||
message: "Forbidden",
|
|
||||||
description: "Operation ",
|
|
||||||
});*/
|
|
||||||
case 404:
|
|
||||||
console.error("Not found");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 422:
|
|
||||||
console.error("Unprocessable Content");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return Promise.reject(data);
|
|
||||||
} else if (error.request) {
|
|
||||||
// La petición fue hecha pero no se recibió respuesta
|
|
||||||
console.debug("2 => El servidor no respondió");
|
|
||||||
console.error(error);
|
|
||||||
} else {
|
|
||||||
if (error.code === "ERR_CANCELED") {
|
|
||||||
// La petición fue hecha pero se ha cancelado
|
|
||||||
console.debug("3 => Petición cancelada");
|
|
||||||
} else {
|
|
||||||
// Algo paso al preparar la petición que lanzo un Error
|
|
||||||
console.debug("4 => Error desconocido");
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.groupEnd();
|
|
||||||
return Promise.reject(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function setupInterceptorsTo(axiosInstance: AxiosInstance): AxiosInstance {
|
|
||||||
axiosInstance.interceptors.request.use(onRequest, onRequestError);
|
|
||||||
axiosInstance.interceptors.response.use(onResponse, onResponseError);
|
|
||||||
return axiosInstance;
|
|
||||||
}
|
|
||||||
@ -20,7 +20,7 @@ i18n
|
|||||||
detection: {
|
detection: {
|
||||||
order: ["navigator"],
|
order: ["navigator"],
|
||||||
},
|
},
|
||||||
debug: import.meta.env.MODE === "development",
|
debug: import.meta.env.DEV,
|
||||||
fallbackLng: "es",
|
fallbackLng: "es",
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
|
|||||||
@ -27,10 +27,6 @@
|
|||||||
"noUncheckedSideEffectImports": true,
|
"noUncheckedSideEffectImports": true,
|
||||||
"allowUnreachableCode": true
|
"allowUnreachableCode": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"],
|
||||||
"src",
|
|
||||||
"../../modules/core/src/web/hooks/use-datasource/use-datasource.tsx",
|
|
||||||
"../../modules/core/src/web/hooks/use-datasource/datasource.interface.ts"
|
|
||||||
],
|
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { defineConfig } from "vite";
|
import favicons from "@peterek/vite-plugin-favicons";
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss(), favicons("public/logo.svg")],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
|||||||
@ -4,13 +4,9 @@
|
|||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/common/index.ts",
|
||||||
"./api": "./src/api/index.ts",
|
"./api": "./src/api/index.ts",
|
||||||
"./dto": "./src/common/dto/index.ts",
|
"./client": "./src/web/index.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": {
|
"peerDependencies": {
|
||||||
"react": "^18 || ^19",
|
"react": "^18 || ^19",
|
||||||
@ -28,6 +24,7 @@
|
|||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-router-dom": "^6.26.0"
|
"react-router-dom": "^6.26.0",
|
||||||
|
"react-secure-storage": "^1.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./api";
|
|
||||||
export * from "./common";
|
|
||||||
export * from "./web";
|
|
||||||
15
modules/auth/src/web/components/auth-guard.tsx
Normal file
15
modules/auth/src/web/components/auth-guard.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { JSX } from "react";
|
||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protege una ruta para usuarios autenticados.
|
||||||
|
*/
|
||||||
|
export const AuthGuard = ({ children }: { children: JSX.Element }) => {
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return <Navigate to='/login' replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { ErrorOverlay, LoadingOverlay } from "@repo/rdx-ui/components";
|
import { ErrorOverlay, LoadingOverlay } from "@repo/rdx-ui/components";
|
||||||
import React, { useId } from "react";
|
import React, { useId } from "react";
|
||||||
import { Navigate, useLocation } from "react-router-dom";
|
import { Navigate, useLocation } from "react-router-dom";
|
||||||
import { useGetProfile, useIsLoggedIn } from "../hooks";
|
import { useGetProfile, useIsLoggedIn } from "../hooks.old";
|
||||||
|
|
||||||
type ProctectRouteProps = {
|
type ProctectRouteProps = {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|||||||
6
modules/auth/src/web/hooks.old/index.ts
Normal file
6
modules/auth/src/web/hooks.old/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from "./auth-actions";
|
||||||
|
export * from "./auth-context";
|
||||||
|
export * from "./use-auth";
|
||||||
|
export * from "./use-get-profile";
|
||||||
|
export * from "./use-is-logged-in";
|
||||||
|
export * from "./use-login";
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export * from "./auth-actions";
|
|
||||||
export * from "./auth-context";
|
|
||||||
export * from "./use-auth";
|
|
||||||
export * from "./use-get-profile";
|
|
||||||
export * from "./use-is-logged-in";
|
|
||||||
export * from "./use-login";
|
|
||||||
49
modules/auth/src/web/hooks/useAuth.tsx
Normal file
49
modules/auth/src/web/hooks/useAuth.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
token: string | null;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
login: (email: string, password: string) => Promise<void>;
|
||||||
|
logout: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proveedor de autenticación para toda la app.
|
||||||
|
*/
|
||||||
|
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [token, setToken] = useState<string | null>(getAccessToken());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setToken(getAccessToken());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const login = async (email: string, password: string) => {
|
||||||
|
const { access_token } = await authService.login({ email, password });
|
||||||
|
localStorage.setItem("access_token", access_token);
|
||||||
|
setToken(access_token);
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
clearAccessToken();
|
||||||
|
setToken(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ token, isAuthenticated: !!token, login, logout }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook para acceder al contexto de autenticación.
|
||||||
|
*/
|
||||||
|
export const useAuth = (): AuthContextType => {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useAuth debe usarse dentro de <AuthProvider>");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
0
modules/auth/src/web/hooks/useCurrentUser.ts
Normal file
0
modules/auth/src/web/hooks/useCurrentUser.ts
Normal file
9
modules/auth/src/web/hooks/useIsAuthenticated.ts
Normal file
9
modules/auth/src/web/hooks/useIsAuthenticated.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { useAuth } from "./useAuth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve un booleano reactivo si el usuario está autenticado.
|
||||||
|
*/
|
||||||
|
export const useIsAuthenticated = () => {
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
return isAuthenticated;
|
||||||
|
};
|
||||||
2
modules/auth/src/web/index.ts
Normal file
2
modules/auth/src/web/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./components";
|
||||||
|
export * from "./lib";
|
||||||
23
modules/auth/src/web/lib/access-token-setup.ts
Normal file
23
modules/auth/src/web/lib/access-token-setup.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import secureLocalStorage from "react-secure-storage";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servicio para manejar la obtención del token JWT desde el almacenamiento local.
|
||||||
|
* Este archivo puede evolucionar a un AuthService más completo en el futuro.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el token JWT almacenado localmente.
|
||||||
|
*
|
||||||
|
* @returns El token como string, o null si no está disponible.
|
||||||
|
*/
|
||||||
|
export const getAccessToken = (tokenStorageKey: string): string | null => {
|
||||||
|
const authInfo = secureLocalStorage.getItem(tokenStorageKey) as { token?: string } | null;
|
||||||
|
return typeof authInfo?.token === "string" ? authInfo.token : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el token JWT del almacenamiento local.
|
||||||
|
*/
|
||||||
|
export const clearAccessToken = (tokenStorageKey: string): void => {
|
||||||
|
secureLocalStorage.removeItem(tokenStorageKey);
|
||||||
|
};
|
||||||
1
modules/auth/src/web/lib/index.ts
Normal file
1
modules/auth/src/web/lib/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./access-token-setup";
|
||||||
17
modules/auth/src/web/services/auth-service.ts
Normal file
17
modules/auth/src/web/services/auth-service.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export const authService = createAuthActions(axiosClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autentica al usuario con email y password, y guarda el token en localStorage.
|
||||||
|
*/
|
||||||
|
export const login = async (email: string, password: string): Promise<void> => {
|
||||||
|
const { access_token } = await authService.login({ email, password });
|
||||||
|
setAccessToken(access_token);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el token local y ejecuta el logout remoto (si aplica).
|
||||||
|
*/
|
||||||
|
export const logout = async (): Promise<void> => {
|
||||||
|
await authService.logout(); // opcional: puede no existir en backend
|
||||||
|
clearAccessToken();
|
||||||
|
};
|
||||||
0
modules/auth/src/web/services/index.ts
Normal file
0
modules/auth/src/web/services/index.ts
Normal file
0
modules/auth/src/web/services/me-service.ts
Normal file
0
modules/auth/src/web/services/me-service.ts
Normal file
0
modules/auth/src/web/services/types.ts
Normal file
0
modules/auth/src/web/services/types.ts
Normal file
0
modules/auth/src/web/services/user-service.ts
Normal file
0
modules/auth/src/web/services/user-service.ts
Normal file
@ -4,18 +4,9 @@
|
|||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/common/index.ts",
|
||||||
"./dto": "./src/dto/index.ts",
|
"./api": "./src/api/index.ts",
|
||||||
"./hooks": "./src/web/hooks/index.ts",
|
"./client": "./src/web/index.ts"
|
||||||
"./components": "./src/web/components/index.tsx",
|
|
||||||
"./components/*": "./src/web/components/*.tsx"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"express": "^4.18.2",
|
|
||||||
"joi": "^17.13.3",
|
|
||||||
"react": "^18 || ^19",
|
|
||||||
"react-dom": "^18 || ^19",
|
|
||||||
"sequelize": "^6.37.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
@ -33,12 +24,9 @@
|
|||||||
"@repo/rdx-criteria": "workspace:*",
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
"@tanstack/react-query": "^5.75.4",
|
"@tanstack/react-query": "^5.75.4",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"express": "^4.18.2",
|
|
||||||
"http-status": "^2.1.0",
|
"http-status": "^2.1.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"libphonenumber-js": "^1.11.20",
|
"libphonenumber-js": "^1.11.20",
|
||||||
"react": "^19.1.0",
|
|
||||||
"react-dom": "^19.1.0",
|
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
"sequelize": "^6.37.5"
|
"sequelize": "^6.37.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export * from "./dto";
|
export * from "../common/dto";
|
||||||
export * from "./infrastructure";
|
export * from "./infrastructure";
|
||||||
export * from "./logger";
|
export * from "./logger";
|
||||||
export * from "./modules";
|
export * from "./modules";
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Criteria, CriteriaFromUrlConverter } from "@repo/rdx-criteria";
|
import { Criteria, CriteriaFromUrlConverter } from "@repo/rdx-criteria/server";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import httpStatus from "http-status";
|
import httpStatus from "http-status";
|
||||||
import { ApiError } from "./api-error";
|
import { ApiError } from "./api-error";
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria";
|
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||||
import { IAggregateRootRepository, UniqueID } from "@repo/rdx-ddd";
|
import { IAggregateRootRepository, UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { FindOptions, ModelDefined, Sequelize, Transaction } from "sequelize";
|
import { FindOptions, ModelDefined, Sequelize, Transaction } from "sequelize";
|
||||||
35
modules/core/src/web/api/axios/create-axios-data-source.ts
Normal file
35
modules/core/src/web/api/axios/create-axios-data-source.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { AxiosInstance } from "axios";
|
||||||
|
import { IDataSource } from "../datasource.interface";
|
||||||
|
|
||||||
|
export const createAxiosDataSource = (client: AxiosInstance): IDataSource => {
|
||||||
|
return {
|
||||||
|
getList: async <T>(resource: string, params?: Record<string, any>) => {
|
||||||
|
const res = await client.get<T[]>(resource, { params });
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getOne: async <T>(resource: string, id: string | number) => {
|
||||||
|
const res = await client.get<T>(`${resource}/${id}`);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getMany: async <T>(resource: string, ids: Array<string | number>) => {
|
||||||
|
const res = await client.get<T[]>(`${resource}`, { params: { ids } });
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createOne: async <T>(resource: string, data: Partial<T>) => {
|
||||||
|
const res = await client.post<T>(resource, data);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOne: async <T>(resource: string, id: string | number, data: Partial<T>) => {
|
||||||
|
const res = await client.put<T>(`${resource}/${id}`, data);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteOne: async <T>(resource: string, id: string | number) => {
|
||||||
|
await client.delete(`${resource}/${id}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
47
modules/core/src/web/api/axios/create-axios-instance.ts
Normal file
47
modules/core/src/web/api/axios/create-axios-instance.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import { setupInterceptors } from "./setup-interceptors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuración necesaria para crear una instancia de Axios personalizada.
|
||||||
|
*/
|
||||||
|
export interface AxiosFactoryConfig {
|
||||||
|
/** URL base del backend API. */
|
||||||
|
baseURL: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función que retorna el token JWT actual.
|
||||||
|
* Debe devolver `null` si no hay token disponible.
|
||||||
|
*/
|
||||||
|
getAccessToken: () => string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función opcional que se ejecuta cuando ocurre un error de autenticación (por ejemplo, 401).
|
||||||
|
*/
|
||||||
|
onAuthError?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea una instancia de Axios preconfigurada con interceptores.
|
||||||
|
*
|
||||||
|
* @param config - Configuración necesaria para inicializar Axios correctamente.
|
||||||
|
* @returns Instancia de Axios lista para usarse.
|
||||||
|
*/
|
||||||
|
export const createAxiosInstance = ({
|
||||||
|
baseURL,
|
||||||
|
getAccessToken,
|
||||||
|
onAuthError,
|
||||||
|
}: AxiosFactoryConfig): AxiosInstance => {
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Access-Control-Allow-Origin": "*", // Could work and fix the previous problem, but not in all APIs
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setupInterceptors(instance, getAccessToken, onAuthError);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
2
modules/core/src/web/api/axios/index.ts
Normal file
2
modules/core/src/web/api/axios/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./create-axios-data-source";
|
||||||
|
export * from "./create-axios-instance";
|
||||||
37
modules/core/src/web/api/axios/setup-interceptors.ts
Normal file
37
modules/core/src/web/api/axios/setup-interceptors.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { AxiosError, AxiosInstance, InternalAxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configura interceptores para una instancia de Axios.
|
||||||
|
*
|
||||||
|
* @param instance - Instancia de Axios que será modificada.
|
||||||
|
* @param getAccessToken - Función que devuelve el token JWT actual.
|
||||||
|
* @param onAuthError - Función opcional que se ejecuta ante errores de autenticación (status 401).
|
||||||
|
*/
|
||||||
|
export const setupInterceptors = (
|
||||||
|
instance: AxiosInstance,
|
||||||
|
getAccessToken: () => string | null,
|
||||||
|
onAuthError?: () => void
|
||||||
|
): void => {
|
||||||
|
instance.interceptors.request.use(
|
||||||
|
(config: InternalAxiosRequestConfig) => {
|
||||||
|
const token = getAccessToken();
|
||||||
|
if (token && config.headers) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error: unknown) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error: AxiosError) => {
|
||||||
|
if (error.response?.status === 401 && onAuthError) {
|
||||||
|
onAuthError();
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
20
modules/core/src/web/api/datasource-context.tsx
Normal file
20
modules/core/src/web/api/datasource-context.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { createContext, useContext } from "react";
|
||||||
|
import { IDataSource } from "./datasource.interface";
|
||||||
|
|
||||||
|
const DataSourceContext = createContext<IDataSource | null>(null);
|
||||||
|
|
||||||
|
export const DataSourceProvider = ({
|
||||||
|
dataSource,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
dataSource: IDataSource;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => <DataSourceContext.Provider value={dataSource}>{children}</DataSourceContext.Provider>;
|
||||||
|
|
||||||
|
export const useDataSource = (): IDataSource => {
|
||||||
|
const context = useContext(DataSourceContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useDataSource debe usarse dentro de <DataSourceProvider>");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
8
modules/core/src/web/api/datasource.interface.ts
Normal file
8
modules/core/src/web/api/datasource.interface.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface IDataSource {
|
||||||
|
getList<T>(resource: string, params?: Record<string, any>): Promise<T[]>;
|
||||||
|
getOne<T>(resource: string, id: string | number): Promise<T>;
|
||||||
|
getMany<T>(resource: string, ids: Array<string | number>): Promise<T[]>;
|
||||||
|
createOne<T>(resource: string, data: Partial<T>): Promise<T>;
|
||||||
|
updateOne<T>(resource: string, id: string | number, data: Partial<T>): Promise<T>;
|
||||||
|
deleteOne<T>(resource: string, id: string | number): Promise<void>;
|
||||||
|
}
|
||||||
3
modules/core/src/web/api/index.ts
Normal file
3
modules/core/src/web/api/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./axios";
|
||||||
|
export * from "./datasource-context";
|
||||||
|
export * from "./datasource.interface";
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import type { IListResponseDTO } from "@erp/core";
|
|
||||||
import { type AxiosHeaderValue, type ResponseType } from "axios";
|
|
||||||
|
|
||||||
export interface IPaginationDataProviderParam {
|
|
||||||
pageIndex: number;
|
|
||||||
pageSize: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISortItemDataProviderParam {
|
|
||||||
order: string;
|
|
||||||
field: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFilterItemDataProviderParam {
|
|
||||||
field: string;
|
|
||||||
operator?: string;
|
|
||||||
value?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetListDataProviderParams {
|
|
||||||
resource: string;
|
|
||||||
quickSearchTerm?: string[];
|
|
||||||
pagination?: IPaginationDataProviderParam;
|
|
||||||
sort?: ISortItemDataProviderParam[];
|
|
||||||
filters?: IFilterItemDataProviderParam[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetOneDataProviderParams {
|
|
||||||
resource: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISaveOneDataProviderParams<T> {
|
|
||||||
resource: string;
|
|
||||||
data: T;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICreateOneDataProviderParams<T> {
|
|
||||||
resource: string;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUpdateOneDataProviderParams<T> {
|
|
||||||
resource: string;
|
|
||||||
data: T;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IRemoveOneDataProviderParams {
|
|
||||||
resource: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDownloadPDFDataProviderParams {
|
|
||||||
url: string;
|
|
||||||
config?: {
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDownloadPDFDataProviderResponse {
|
|
||||||
filename: string;
|
|
||||||
filedata: Blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUploadFileDataProviderParam {
|
|
||||||
path: string;
|
|
||||||
//resource: string;
|
|
||||||
//id: string;
|
|
||||||
file: File;
|
|
||||||
key: string;
|
|
||||||
onUploadProgress?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICustomDataProviderParam {
|
|
||||||
url?: string;
|
|
||||||
path?: string;
|
|
||||||
method: "get" | "delete" | "head" | "options" | "post" | "put" | "patch";
|
|
||||||
signal?: AbortSignal;
|
|
||||||
responseType?: ResponseType;
|
|
||||||
headers?: {
|
|
||||||
[key: string]: AxiosHeaderValue;
|
|
||||||
};
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDataSource {
|
|
||||||
name: () => string;
|
|
||||||
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>;
|
|
||||||
updateOne: <P, R>(params: IUpdateOneDataProviderParams<P>) => Promise<R>;
|
|
||||||
removeOne: (params: IRemoveOneDataProviderParams) => Promise<void>;
|
|
||||||
downloadPDF: (
|
|
||||||
params: IDownloadPDFDataProviderParams
|
|
||||||
) => Promise<IDownloadPDFDataProviderResponse>;
|
|
||||||
uploadFile: <R>(params: IUploadFileDataProviderParam) => Promise<R>;
|
|
||||||
custom: <R>(params: ICustomDataProviderParam) => Promise<R>;
|
|
||||||
|
|
||||||
getApiUrl: () => string;
|
|
||||||
getApiAuthorization: () => string;
|
|
||||||
|
|
||||||
//create: () => any;
|
|
||||||
//createMany: () => any;
|
|
||||||
//removeMany: () => any;
|
|
||||||
//getMany: () => any;
|
|
||||||
//update: () => any;
|
|
||||||
//updateMany: () => any;
|
|
||||||
//upload: () => any;
|
|
||||||
//custom: () => any;
|
|
||||||
//getApiUrl: () => string;
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./datasource.interface";
|
|
||||||
export * from "./use-datasource";
|
|
||||||
export * from "./use-list";
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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;
|
|
||||||
};
|
|
||||||
@ -7,7 +7,7 @@ import {
|
|||||||
useQuery,
|
useQuery,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
import { isResponseAListDTO } from "@erp/core/dto";
|
import { isResponseAListDTO } from "@erp/core/common/dto";
|
||||||
import {
|
import {
|
||||||
UseLoadingOvertimeOptionsProps,
|
UseLoadingOvertimeOptionsProps,
|
||||||
UseLoadingOvertimeReturnType,
|
UseLoadingOvertimeReturnType,
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
|
export * from "./api";
|
||||||
|
export * from "./components";
|
||||||
export * from "./hooks";
|
export * from "./hooks";
|
||||||
|
|||||||
@ -13,25 +13,12 @@
|
|||||||
"./components/*": "./src/web/components/*.tsx",
|
"./components/*": "./src/web/components/*.tsx",
|
||||||
"./locales": "./src/common/locales/index.tsx"
|
"./locales": "./src/common/locales/index.tsx"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"ag-grid-community": "^33.3.0",
|
|
||||||
"ag-grid-react": "^33.3.0",
|
|
||||||
"express": "^4.18.2",
|
|
||||||
"i18next": "^25.1.1",
|
|
||||||
"lucide-react": "^0.503.0",
|
|
||||||
"react": "^18 || ^19",
|
|
||||||
"react-dom": "^18 || ^19",
|
|
||||||
"react-i18next": "^15.5.1",
|
|
||||||
"react-router-dom": "^6.26.0",
|
|
||||||
"sequelize": "^6.37.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.3",
|
"@types/react-dom": "^19.1.3",
|
||||||
"@types/react-i18next": "^8.1.0",
|
"@types/react-i18next": "^8.1.0",
|
||||||
"sequelize": "^6.37.5",
|
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -50,6 +37,7 @@
|
|||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
|
"sequelize": "^6.37.5",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"zod": "^3.24.4"
|
"zod": "^3.24.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ITransactionManager } from "@erp/core";
|
import { ITransactionManager } from "@erp/core";
|
||||||
import { Criteria } from "@repo/rdx-criteria";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { IInvoiceService, Invoice } from "../domain";
|
import { IInvoiceService, Invoice } from "../domain";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Criteria } from "@repo/rdx-criteria";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Invoice } from "../aggregates";
|
import { Invoice } from "../aggregates";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Criteria } from "@repo/rdx-criteria";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { IInvoiceProps, Invoice } from "../aggregates";
|
import { IInvoiceProps, Invoice } from "../aggregates";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Criteria } from "@repo/rdx-criteria";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { SequelizeRepository } from "@erp/core";
|
import { SequelizeRepository } from "@erp/core";
|
||||||
import { Criteria } from "@repo/rdx-criteria";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Sequelize, Transaction } from "sequelize";
|
import { Sequelize, Transaction } from "sequelize";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { IListResponseDTO } from "@erp/core";
|
import { IListResponseDTO } from "@erp/core";
|
||||||
import { Criteria } from "@repo/rdx-criteria";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { Collection } from "@repo/rdx-utils";
|
import { Collection } from "@repo/rdx-utils";
|
||||||
import { IListInvoicesResponseDTO } from "../../../../common/dto";
|
import { IListInvoicesResponseDTO } from "../../../../common/dto";
|
||||||
import { Invoice } from "../../../domain";
|
import { Invoice } from "../../../domain";
|
||||||
|
|||||||
@ -50,26 +50,32 @@ interface IRow {
|
|||||||
export const InvoicesGrid = () => {
|
export const InvoicesGrid = () => {
|
||||||
const { useList } = useInvoices();
|
const { useList } = useInvoices();
|
||||||
|
|
||||||
const { data, isPending, isError, error } = useList({});
|
const { data, isLoading, isPending, isError, error } = useList({});
|
||||||
|
|
||||||
// Column Definitions: Defines & controls grid columns.
|
// Column Definitions: Defines & controls grid columns.
|
||||||
const [colDefs] = useState<ColDef[]>([
|
const [colDefs] = useState<ColDef[]>([
|
||||||
|
{ field: "invoice_number" },
|
||||||
|
{ field: "invoice_series" },
|
||||||
{
|
{
|
||||||
field: "mission",
|
field: "status",
|
||||||
filter: true,
|
|
||||||
minWidth: 200,
|
|
||||||
},
|
},
|
||||||
{ field: "company", filter: false },
|
|
||||||
{ field: "location" },
|
{ field: "issue_date" },
|
||||||
{ field: "date" },
|
{ field: "operation_date" },
|
||||||
{
|
{
|
||||||
field: "price",
|
field: "subtotal",
|
||||||
valueFormatter: (params: ValueFormatterParams) => {
|
valueFormatter: (params: ValueFormatterParams) => {
|
||||||
return `£${params.value.toLocaleString()}`;
|
return "0 €";
|
||||||
|
//return `£${params.value.toLocaleString()}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "total",
|
||||||
|
valueFormatter: (params: ValueFormatterParams) => {
|
||||||
|
return "0 €";
|
||||||
|
//return `£${params.value.toLocaleString()}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ field: "successful" },
|
|
||||||
{ field: "rocket" },
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Apply settings across all columns
|
// Apply settings across all columns
|
||||||
@ -92,7 +98,7 @@ export const InvoicesGrid = () => {
|
|||||||
>
|
>
|
||||||
<AgGridReact
|
<AgGridReact
|
||||||
rowData={data}
|
rowData={data}
|
||||||
loading={loading}
|
loading={isLoading || isPending}
|
||||||
columnDefs={colDefs}
|
columnDefs={colDefs}
|
||||||
defaultColDef={defaultColDef}
|
defaultColDef={defaultColDef}
|
||||||
pagination={true}
|
pagination={true}
|
||||||
|
|||||||
@ -7,10 +7,8 @@
|
|||||||
"clean": "rm -rf node_modules"
|
"clean": "rm -rf node_modules"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/defaults.ts",
|
||||||
},
|
"./server": "./src/index.ts"
|
||||||
"peerDependencies": {
|
|
||||||
"sequelize": "^6.37.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/typescript-config": "workspace:*"
|
"@repo/typescript-config": "workspace:*"
|
||||||
|
|||||||
@ -14,11 +14,6 @@
|
|||||||
"./locales/*": "./src/locales/*",
|
"./locales/*": "./src/locales/*",
|
||||||
"./hooks/*": ["./src/hooks/*.tsx", "./src/hooks/*.ts"]
|
"./hooks/*": ["./src/hooks/*.tsx", "./src/hooks/*.ts"]
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^18 || ^19",
|
|
||||||
"react-dom": "^18 || ^19",
|
|
||||||
"react-router": "^6.26.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
|
|||||||
@ -16,8 +16,5 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"joi": "^17.13.3"
|
"joi": "^17.13.3"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"joi": "^17.13.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1271
pnpm-lock.yaml
1271
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -2,15 +2,12 @@ packages:
|
|||||||
- packages/*
|
- packages/*
|
||||||
- modules/*
|
- modules/*
|
||||||
- apps/*
|
- apps/*
|
||||||
- "!apps/web"
|
ignoredBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- '@biomejs/biome'
|
- '@biomejs/biome'
|
||||||
- '@parcel/watcher'
|
- '@parcel/watcher'
|
||||||
- '@tailwindcss/oxide'
|
- '@tailwindcss/oxide'
|
||||||
- bcrypt
|
- bcrypt
|
||||||
- core-js-pure
|
- core-js-pure
|
||||||
|
- sharp
|
||||||
ignoredBuiltDependencies:
|
|
||||||
- esbuild
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user