Parte cliente de Suppliers

This commit is contained in:
David Arranz 2026-04-03 22:03:48 +02:00
parent 91166766a3
commit 6e97e91173
28 changed files with 808 additions and 1 deletions

View File

@ -12,22 +12,45 @@
"exports": {
".": "./src/common/index.ts",
"./common": "./src/common/index.ts",
"./api": "./src/api/index.ts"
"./api": "./src/api/index.ts",
"./api/domain": "./src/api/domain/index.ts",
"./client": "./src/web/manifest.ts",
"./globals.css": "./src/web/globals.css",
"./components": "./src/web/components/index.ts"
},
"peerDependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/react": "^19.1.2",
"@types/react-router-dom": "^5.3.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"typescript": "^5.9.3"
},
"dependencies": {
"@erp/auth": "workspace:*",
"@erp/core": "workspace:*",
"@hookform/resolvers": "^5.0.1",
"@repo/i18next": "workspace:*",
"@repo/rdx-criteria": "workspace:*",
"@repo/rdx-ddd": "workspace:*",
"@repo/rdx-logger": "workspace:*",
"@repo/rdx-ui": "workspace:*",
"@repo/rdx-utils": "workspace:*",
"@repo/shadcn-ui": "workspace:*",
"@tanstack/react-query": "^5.90.6",
"@tanstack/react-table": "^8.21.3",
"express": "^4.18.2",
"lucide-react": "^0.577.0",
"react-data-table-component": "^7.7.0",
"react-hook-form": "^7.72.1",
"react-i18next": "^16.2.4",
"react-router-dom": "^6.26.0",
"sequelize": "^6.37.8",
"use-debounce": "^10.0.5",
"zod": "^4.1.11"
}
}

View File

@ -0,0 +1,38 @@
import type { GetSupplierByIdResponseDTO } from "../../../common";
import type { Supplier } from "../entities";
export const GetSupplierByIdAdapter = {
fromDTO(dto: GetSupplierByIdResponseDTO): Supplier {
return {
id: dto.id,
companyId: dto.company_id,
status: dto.status,
reference: dto.reference,
isCompany: dto.is_company === "1",
name: dto.name,
tradeName: dto.trade_name,
tin: dto.tin,
street: dto.street,
street2: dto.street2,
city: dto.city,
province: dto.province,
postalCode: dto.postal_code,
country: dto.country,
primaryEmail: dto.email_primary,
secondaryEmail: dto.email_secondary,
primaryPhone: dto.phone_primary,
secondaryPhone: dto.phone_secondary,
primaryMobile: dto.mobile_primary,
secondaryMobile: dto.mobile_secondary,
fax: dto.fax,
website: dto.website,
languageCode: dto.language_code,
currencyCode: dto.currency_code,
};
},
};

View File

@ -0,0 +1,3 @@
export * from "./get-supplier-by-id.adapter";
export * from "./list-suppliers.adapter";
export * from "./supplier-to-list-row-patch.adapter";

View File

@ -0,0 +1,52 @@
import type { ListSuppliersResponseDTO } from "../../../common";
import type { SupplierList, SupplierListRow } from "../entities";
type ListSuppliersItemDTO = ListSuppliersResponseDTO["items"][number];
export const ListSuppliersAdapter = {
fromDTO(pageDto: ListSuppliersResponseDTO): SupplierList {
return {
page: pageDto.page,
per_page: pageDto.per_page,
total_pages: pageDto.total_pages,
total_items: pageDto.total_items,
items: pageDto.items.map(ListSuppliersRowAdapter.fromDTO),
};
},
};
const ListSuppliersRowAdapter = {
fromDTO(rowDto: ListSuppliersItemDTO): SupplierListRow {
return {
id: rowDto.id,
companyId: rowDto.company_id,
status: rowDto.status,
reference: rowDto.reference,
isCompany: rowDto.is_company === "1",
name: rowDto.name,
tradeName: rowDto.trade_name,
tin: rowDto.tin,
street: rowDto.street,
street2: rowDto.street2,
city: rowDto.city,
province: rowDto.province,
postalCode: rowDto.postal_code,
country: rowDto.country,
primaryEmail: rowDto.email_primary,
secondaryEmail: rowDto.email_secondary,
primaryPhone: rowDto.phone_primary,
secondaryPhone: rowDto.phone_secondary,
primaryMobile: rowDto.mobile_primary,
secondaryMobile: rowDto.mobile_secondary,
fax: rowDto.fax,
website: rowDto.website,
languageCode: rowDto.language_code,
currencyCode: rowDto.currency_code,
};
},
};

View File

@ -0,0 +1,29 @@
import type { Supplier, SupplierListRow } from "../entities";
export type SupplierListRowPatch = Pick<SupplierListRow, "id"> & Partial<SupplierListRow>;
export const SupplierToListRowPatchAdapter = {
fromSupplier(supplier: Supplier): SupplierListRowPatch {
return {
id: supplier.id,
status: supplier.status,
reference: supplier.reference,
isCompany: supplier.isCompany,
name: supplier.name,
tradeName: supplier.tradeName,
tin: supplier.tin,
city: supplier.city,
province: supplier.province,
country: supplier.country,
primaryEmail: supplier.primaryEmail,
primaryPhone: supplier.primaryPhone,
primaryMobile: supplier.primaryMobile,
languageCode: supplier.languageCode,
currencyCode: supplier.currencyCode,
};
},
};

View File

@ -0,0 +1,24 @@
import type { IDataSource } from "@erp/core/client";
import type { CreateSupplierRequestDTO, SupplierCreationResponseDTO } from "../../../common";
export type SupplierCreateInput = CreateSupplierRequestDTO;
export type SupplierCreateOutput = SupplierCreationResponseDTO;
export async function createSupplier(
dataSource: IDataSource,
id: string,
data: SupplierCreateInput
) {
if (!id) throw new Error("supplierId is required");
const response = await dataSource.createOne<SupplierCreateInput, SupplierCreateOutput>(
"suppliers",
{
...data,
id,
}
);
return response;
}

View File

@ -0,0 +1,13 @@
import type { IDataSource } from "@erp/core/client";
import type { DeleteSupplierByIdRequestDTO } from "../../../common";
export type SupplierDeleteInput = DeleteSupplierByIdRequestDTO;
export async function deleteSupplierById(dataSource: IDataSource, id: string, signal: AbortSignal) {
const response = await dataSource.deleteOne<SupplierDeleteInput>("suppliers", id, {
signal,
});
return response;
}

View File

@ -0,0 +1,13 @@
import type { IDataSource } from "@erp/core/client";
import type { GetSupplierByIdResponseDTO } from "../../../common";
export type SupplierGetOutput = GetSupplierByIdResponseDTO;
export async function getSupplierById(dataSource: IDataSource, id: string, signal: AbortSignal) {
const response = dataSource.getOne<SupplierGetOutput>("suppliers", id, {
signal,
});
return response;
}

View File

@ -0,0 +1,5 @@
export * from "./create-supplier.api";
export * from "./delete-supplier-by-id.api";
export * from "./get-supplier-by-id.api";
export * from "./list-suppliers.api";
export * from "./update-supplier-by-id.api";

View File

@ -0,0 +1,19 @@
import type { CriteriaDTO } from "@erp/core";
import type { IDataSource } from "@erp/core/client";
import type { ListSuppliersResponseDTO } from "../../../common";
export type SupplierListOutput = ListSuppliersResponseDTO;
export async function getListSuppliers(
dataSource: IDataSource,
criteria: CriteriaDTO,
signal: AbortSignal
) {
const response = dataSource.getList<SupplierListOutput>("suppliers", {
signal,
...criteria,
});
return response;
}

View File

@ -0,0 +1,22 @@
import type { IDataSource } from "@erp/core/client";
import type { UpdateSupplierByIdRequestDTO, UpdateSupplierByIdResponseDTO } from "../../../common";
export type SupplierUpdateInput = UpdateSupplierByIdRequestDTO;
export type SupplierUpdateOutput = UpdateSupplierByIdResponseDTO;
export async function updateSupplierById(
dataSource: IDataSource,
id: string,
data: SupplierUpdateInput
) {
if (!id) throw new Error("supplierId is required");
const response = dataSource.updateOne<SupplierUpdateInput, SupplierUpdateOutput>(
"suppliers",
id,
data
);
return response;
}

View File

@ -0,0 +1,28 @@
export const COUNTRY_OPTIONS = [
{ value: "es", label: "España" },
{ value: "fr", label: "Francia" },
{ value: "de", label: "Alemania" },
{ value: "it", label: "Italia" },
{ value: "pt", label: "Portugal" },
{ value: "us", label: "Estados Unidos" },
{ value: "mx", label: "México" },
{ value: "ar", label: "Argentina" },
] as const;
export const LANGUAGE_OPTIONS = [
{ value: "es", label: "Español" },
{ value: "en", label: "Inglés" },
{ value: "fr", label: "Francés" },
{ value: "de", label: "Alemán" },
{ value: "it", label: "Italiano" },
{ value: "pt", label: "Portugués" },
] as const;
export const CURRENCY_OPTIONS = [
{ value: "EUR", label: "Euro" },
{ value: "USD", label: "Dólar estadounidense" },
{ value: "GBP", label: "Libra esterlina" },
{ value: "ARS", label: "Peso argentino" },
{ value: "MXN", label: "Peso mexicano" },
{ value: "JPY", label: "Yen japonés" },
] as const;

View File

@ -0,0 +1,3 @@
export * from "./supplier.entity";
export * from "./supplier-list.entity";
export * from "./supplier-list-row.entity";

View File

@ -0,0 +1,31 @@
export interface SupplierListRow {
id: string;
companyId: string;
status: string;
reference: string;
isCompany: boolean;
name: string;
tradeName: string;
tin: string;
street: string;
street2: string;
city: string;
province: string;
postalCode: string;
country: string;
primaryEmail: string;
secondaryEmail: string;
primaryPhone: string;
secondaryPhone: string;
primaryMobile: string;
secondaryMobile: string;
fax: string;
website: string;
languageCode: string;
currencyCode: string;
}

View File

@ -0,0 +1,9 @@
import type { SupplierListRow } from "./supplier-list-row.entity";
export interface SupplierList {
items: SupplierListRow[];
total_pages: number;
total_items: number;
page: number;
per_page: number;
}

View File

@ -0,0 +1,31 @@
export interface Supplier {
id: string;
companyId: string;
status: string;
reference: string;
isCompany: boolean;
name: string;
tradeName: string;
tin: string;
street: string;
street2: string;
city: string;
province: string;
postalCode: string;
country: string;
primaryEmail: string;
secondaryEmail: string;
primaryPhone: string;
secondaryPhone: string;
primaryMobile: string;
secondaryMobile: string;
fax: string;
website: string;
languageCode: string;
currencyCode: string;
}

View File

@ -0,0 +1,8 @@
export * from "./keys";
export * from "./supplier-cache-strategy";
export * from "./to-validation-errors";
export * from "./use-list-suppliers-query";
export * from "./use-supplier-create-mutation";
export * from "./use-supplier-delete-mutation";
export * from "./use-supplier-get-query";
export * from "./use-supplier-update-mutation";

View File

@ -0,0 +1,29 @@
import type { CriteriaDTO } from "@erp/core";
import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria";
import type { QueryKey } from "@tanstack/react-query";
export const LIST_SUPPLIERS_QUERY_KEY_PREFIX = ["suppliers"] as const;
export const LIST_SUPPLIERS_QUERY_KEY = (criteria?: CriteriaDTO): QueryKey =>
[
...LIST_SUPPLIERS_QUERY_KEY_PREFIX,
{
pageNumber: criteria?.pageNumber ?? INITIAL_PAGE_INDEX,
pageSize: criteria?.pageSize ?? INITIAL_PAGE_SIZE,
q: criteria?.q ?? "",
filters: criteria?.filters ?? [],
orderBy: criteria?.orderBy ?? "",
order: criteria?.order ?? "",
},
] as const;
export const SUPPLIER_QUERY_KEY = (supplierId?: string): QueryKey => [
"suppliers:detail",
{
supplierId,
},
];
export const SUPPLIER_CREATE_KEY = ["suppliers", "create"] as const;
export const SUPPLIER_UPDATE_KEY = ["suppliers", "update"] as const;
export const SUPPLIER_DELETE_KEY = ["suppliers", "delete"] as const;

View File

@ -0,0 +1,149 @@
import type { QueryClient, QueryKey } from "@tanstack/react-query";
import { SupplierToListRowPatchAdapter } from "../adapters";
import type { Supplier, SupplierList, SupplierListRow } from "../entities";
import { LIST_SUPPLIERS_QUERY_KEY_PREFIX, SUPPLIER_QUERY_KEY } from "./keys";
export interface SupplierListCacheSnapshot {
key: QueryKey;
page?: SupplierList;
}
export interface DeleteSupplierCacheContext {
snapshots: SupplierListCacheSnapshot[];
}
export function cancelSupplierListQueries(queryClient: QueryClient) {
return queryClient.cancelQueries({
queryKey: LIST_SUPPLIERS_QUERY_KEY_PREFIX,
});
}
export function invalidateSupplierListQueries(queryClient: QueryClient) {
return queryClient.invalidateQueries({
queryKey: LIST_SUPPLIERS_QUERY_KEY_PREFIX,
});
}
export function invalidateSupplierDetailQuery(queryClient: QueryClient, supplierId: string) {
return queryClient.invalidateQueries({
queryKey: SUPPLIER_QUERY_KEY(supplierId),
exact: true,
});
}
export function setSupplierDetailCache(
queryClient: QueryClient,
supplierId: string,
supplier: Supplier
) {
queryClient.setQueryData<Supplier>(SUPPLIER_QUERY_KEY(supplierId), supplier);
}
export function removeSupplierDetailCache(queryClient: QueryClient, supplierId: string) {
queryClient.removeQueries({
queryKey: SUPPLIER_QUERY_KEY(supplierId),
exact: true,
});
}
export function getAllSupplierListQueryKeys(queryClient: QueryClient): QueryKey[] {
const entries = queryClient.getQueriesData<SupplierList>({
queryKey: LIST_SUPPLIERS_QUERY_KEY_PREFIX,
});
return entries.map(([key]) => key);
}
export function upsertSupplierInListCaches(
queryClient: QueryClient,
supplier: Pick<SupplierListRow, "id"> & Partial<SupplierListRow>
) {
const keys = getAllSupplierListQueryKeys(queryClient);
for (const key of keys) {
const page = queryClient.getQueryData<SupplierList>(key);
if (!page) continue;
const index = page.items.findIndex((row) => row.id === supplier.id);
if (index === -1) continue;
const nextItems = page.items.slice();
nextItems[index] = {
...page.items[index],
...supplier,
};
queryClient.setQueryData<SupplierList>(key, {
...page,
items: nextItems,
});
}
}
export function removeSupplierFromListCaches(
queryClient: QueryClient,
supplierId: string
): SupplierListCacheSnapshot[] {
const snapshots = getAllSupplierListQueryKeys(queryClient).map((key) => ({
key,
page: queryClient.getQueryData<SupplierList>(key),
}));
for (const { key, page } of snapshots) {
if (!page) continue;
queryClient.setQueryData<SupplierList>(key, {
...page,
items: page.items.filter((row) => row.id !== supplierId),
total_items: Math.max(0, page.total_items - 1),
});
}
return snapshots;
}
export function restoreSupplierListCaches(
queryClient: QueryClient,
snapshots: SupplierListCacheSnapshot[]
) {
for (const snapshot of snapshots) {
queryClient.setQueryData(snapshot.key, snapshot.page);
}
}
export function syncCreatedSupplierCaches(queryClient: QueryClient, supplier: Supplier) {
setSupplierDetailCache(queryClient, supplier.id, supplier);
return invalidateSupplierListQueries(queryClient);
}
export function syncUpdatedSupplierCaches(queryClient: QueryClient, supplier: Supplier) {
setSupplierDetailCache(queryClient, supplier.id, supplier);
upsertSupplierInListCaches(queryClient, SupplierToListRowPatchAdapter.fromSupplier(supplier));
return invalidateSupplierListQueries(queryClient);
}
export async function prepareDeleteSupplierOptimisticUpdate(
queryClient: QueryClient,
supplierId: string
): Promise<DeleteSupplierCacheContext> {
await cancelSupplierListQueries(queryClient);
const snapshots = removeSupplierFromListCaches(queryClient, supplierId);
return { snapshots };
}
export function rollbackDeleteSupplierOptimisticUpdate(
queryClient: QueryClient,
context?: DeleteSupplierCacheContext
) {
if (!context) return;
restoreSupplierListCaches(queryClient, context.snapshots);
}
export function finalizeDeletedSupplierCaches(queryClient: QueryClient, supplierId: string) {
removeSupplierDetailCache(queryClient, supplierId);
return invalidateSupplierListQueries(queryClient);
}

View File

@ -0,0 +1,10 @@
import type { ZodError } from "zod";
// Helpers de validación a errores de dominio
export function toValidationErrors(error: ZodError<unknown>) {
return error.issues.map((err) => ({
field: err.path.join("."),
message: err.message,
}));
}

View File

@ -0,0 +1,32 @@
import type { CriteriaDTO } from "@erp/core";
import { useDataSource } from "@erp/core/hooks";
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
import { ListSuppliersAdapter } from "../adapters";
import { getListSuppliers } from "../api";
import type { SupplierList } from "../entities";
import { LIST_SUPPLIERS_QUERY_KEY } from "./keys";
type ListSuppliersQueryOptions = {
enabled?: boolean;
criteria?: Partial<CriteriaDTO>;
};
export const useListSuppliersQuery = (
options?: ListSuppliersQueryOptions
): UseQueryResult<SupplierList, DefaultError> => {
const dataSource = useDataSource();
const enabled = options?.enabled ?? true;
const criteria = options?.criteria ?? {};
return useQuery<SupplierList, DefaultError>({
queryKey: LIST_SUPPLIERS_QUERY_KEY(criteria as CriteriaDTO),
queryFn: async ({ signal }) => {
const dto = await getListSuppliers(dataSource, criteria as CriteriaDTO, signal);
return ListSuppliersAdapter.fromDTO(dto);
},
enabled,
placeholderData: (previousData) => previousData,
});
};

View File

@ -0,0 +1,51 @@
import { useDataSource } from "@erp/core/hooks";
import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd";
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
import { CreateSupplierRequestSchema } from "../../../common";
import { GetSupplierByIdAdapter } from "../adapters";
import { type SupplierCreateInput, createSupplier } from "../api";
import type { Supplier } from "../entities";
import { SUPPLIER_CREATE_KEY } from "./keys";
import {
invalidateSupplierListQueries,
syncCreatedSupplierCaches,
} from "./supplier-cache-strategy";
import { toValidationErrors } from "./to-validation-errors";
type CreateSupplierContext = {};
type CreateSupplierPayload = {
data: SupplierCreateInput;
};
export const useSupplierCreateMutation = () => {
const queryClient = useQueryClient();
const dataSource = useDataSource();
const schema = CreateSupplierRequestSchema;
return useMutation<Supplier, DefaultError, CreateSupplierPayload, CreateSupplierContext>({
mutationKey: SUPPLIER_CREATE_KEY,
mutationFn: async ({ data }) => {
const id = UniqueID.generateNewID().toString();
const result = schema.safeParse(data);
if (!result.success) {
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
}
const dto = await createSupplier(dataSource, id, data);
return GetSupplierByIdAdapter.fromDTO(dto);
},
onSuccess: (createdSupplier) => {
syncCreatedSupplierCaches(queryClient, createdSupplier);
},
onSettled: async () => {
await invalidateSupplierListQueries(queryClient);
},
});
};

View File

@ -0,0 +1,53 @@
import { useDataSource } from "@erp/core/hooks";
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteSupplierById } from "../api";
import { SUPPLIER_DELETE_KEY } from "./keys";
import {
type DeleteSupplierCacheContext,
finalizeDeletedSupplierCaches,
invalidateSupplierListQueries,
prepareDeleteSupplierOptimisticUpdate,
rollbackDeleteSupplierOptimisticUpdate,
} from "./supplier-cache-strategy";
export interface DeleteSupplierPayload {
id: string;
}
interface DeleteSupplierContext extends DeleteSupplierCacheContext {}
export const useSupplierDeleteMutation = () => {
const queryClient = useQueryClient();
const dataSource = useDataSource();
return useMutation<{ id: string }, DefaultError, DeleteSupplierPayload, DeleteSupplierContext>({
mutationKey: SUPPLIER_DELETE_KEY,
mutationFn: async ({ id }) => {
if (!id) {
throw new Error("supplierId is required");
}
await deleteSupplierById(dataSource, id, new AbortController().signal);
return { id };
},
onMutate: async ({ id }) => {
return prepareDeleteSupplierOptimisticUpdate(queryClient, id);
},
onError: (_error, _variables, context) => {
rollbackDeleteSupplierOptimisticUpdate(queryClient, context);
},
onSuccess: ({ id }) => {
finalizeDeletedSupplierCaches(queryClient, id);
},
onSettled: async () => {
await invalidateSupplierListQueries(queryClient);
},
});
};

View File

@ -0,0 +1,29 @@
import { useDataSource } from "@erp/core/hooks";
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
import { GetSupplierByIdAdapter } from "../adapters";
import { getSupplierById } from "../api";
import type { Supplier } from "../entities";
import { SUPPLIER_QUERY_KEY } from "./keys";
type SupplierGetQueryOptions = {
enabled?: boolean;
};
export const useSupplierGetQuery = (
supplierId?: string,
options?: SupplierGetQueryOptions
): UseQueryResult<Supplier, DefaultError> => {
const dataSource = useDataSource();
const enabled = options?.enabled ?? Boolean(supplierId);
return useQuery<Supplier, DefaultError>({
queryKey: SUPPLIER_QUERY_KEY(supplierId),
queryFn: async ({ signal }) => {
const dto = await getSupplierById(dataSource, String(supplierId), signal);
return GetSupplierByIdAdapter.fromDTO(dto);
},
enabled,
});
};

View File

@ -0,0 +1,54 @@
import { useDataSource } from "@erp/core/hooks";
import { ValidationErrorCollection } from "@repo/rdx-ddd";
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
import { UpdateSupplierByIdRequestSchema } from "../../../common";
import { GetSupplierByIdAdapter } from "../adapters";
import { type SupplierUpdateInput, updateSupplierById } from "../api";
import type { Supplier } from "../entities";
import { SUPPLIER_UPDATE_KEY } from "./keys";
import {
invalidateSupplierListQueries,
syncUpdatedSupplierCaches,
} from "./supplier-cache-strategy";
import { toValidationErrors } from "./to-validation-errors";
type UpdateSupplierContext = {};
type UpdateSupplierPayload = {
id: string;
data: SupplierUpdateInput;
};
export const useSupplierUpdateMutation = () => {
const queryClient = useQueryClient();
const dataSource = useDataSource();
const schema = UpdateSupplierByIdRequestSchema;
return useMutation<Supplier, DefaultError, UpdateSupplierPayload, UpdateSupplierContext>({
mutationKey: SUPPLIER_UPDATE_KEY,
mutationFn: async ({ id, data }) => {
if (!id) {
throw new Error("supplierId is required");
}
const result = schema.safeParse(data);
if (!result.success) {
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
}
const dto = await updateSupplierById(dataSource, id, data);
return GetSupplierByIdAdapter.fromDTO(dto);
},
onSuccess: (updatedSupplier) => {
syncUpdatedSupplierCaches(queryClient, updatedSupplier);
},
onSettled: async () => {
await invalidateSupplierListQueries(queryClient);
},
});
};

View File

@ -0,0 +1,4 @@
export * from "./api";
export * from "./constants";
export * from "./entities";
export * from "./hooks";

View File

@ -702,6 +702,9 @@ importers:
'@erp/core':
specifier: workspace:*
version: link:../core
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.2.2(react-hook-form@7.72.1(react@19.2.0))
'@repo/i18next':
specifier: workspace:*
version: link:../../packages/i18n
@ -714,15 +717,45 @@ importers:
'@repo/rdx-logger':
specifier: workspace:*
version: link:../../packages/rdx-logger
'@repo/rdx-ui':
specifier: workspace:*
version: link:../../packages/rdx-ui
'@repo/rdx-utils':
specifier: workspace:*
version: link:../../packages/rdx-utils
'@repo/shadcn-ui':
specifier: workspace:*
version: link:../../packages/shadcn-ui
'@tanstack/react-query':
specifier: ^5.90.6
version: 5.90.6(react@19.2.0)
'@tanstack/react-table':
specifier: ^8.21.3
version: 8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
express:
specifier: ^4.18.2
version: 4.21.2
lucide-react:
specifier: ^0.577.0
version: 0.577.0(react@19.2.0)
react-data-table-component:
specifier: ^7.7.0
version: 7.7.0(react@19.2.0)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0))
react-hook-form:
specifier: ^7.72.1
version: 7.72.1(react@19.2.0)
react-i18next:
specifier: ^16.2.4
version: 16.2.4(i18next@25.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
react-router-dom:
specifier: ^6.26.0
version: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
sequelize:
specifier: ^6.37.8
version: 6.37.8(mysql2@3.15.3)(pg-hstore@2.3.4)
use-debounce:
specifier: ^10.0.5
version: 10.0.6(react@19.2.0)
zod:
specifier: ^4.1.11
version: 4.1.12
@ -730,6 +763,18 @@ importers:
'@types/express':
specifier: ^4.17.21
version: 4.17.25
'@types/react':
specifier: ^19.1.2
version: 19.2.2
'@types/react-router-dom':
specifier: ^5.3.3
version: 5.3.3
react:
specifier: ^19.1.0
version: 19.2.0
react-dom:
specifier: ^19.1.0
version: 19.2.0(react@19.2.0)
typescript:
specifier: ^5.9.3
version: 5.9.3