This commit is contained in:
David Arranz 2025-05-18 13:53:00 +02:00
parent ff6a546d8a
commit 87eb51a44b
17 changed files with 183 additions and 300 deletions

View File

@ -0,0 +1,64 @@
import { AppLayout, LoadingOverlay, ScrollToTop } from "@repo/rdx-ui/components";
import { JSX, Suspense } from "react";
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
import { ErrorPage } from "./pages";
import { modules } from "./register-modules"; // Aquí ca
// Lazy load components
//const ErrorPage = lazy(() => import("./pages").then((m) => ({ default: m.ErrorPage })));
//const LogoutPage = lazy(() => import("./app").then((m) => ({ default: m.LogoutPage })));
/*const DealerLayout = lazy(() => import("./app").then((m) => ({ default: m.DealerLayout })));
const DealersList = lazy(() => import("./app").then((m) => ({ default: m.DealersList })));
const LoginPageWithLanguageSelector = lazy(() =>
import("./app").then((m) => ({ default: m.LoginPageWithLanguageSelector }))
);
const QuoteCreate = lazy(() => import("./app").then((m) => ({ default: m.QuoteCreate })));
const QuoteEdit = lazy(() => import("./app").then((m) => ({ default: m.QuoteEdit })));
const SettingsEditor = lazy(() => import("./app").then((m) => ({ default: m.SettingsEditor })));
const SettingsLayout = lazy(() => import("./app").then((m) => ({ default: m.SettingsLayout })));
const CatalogLayout = lazy(() => import("./app").then((m) => ({ default: m.CatalogLayout })));
const CatalogList = lazy(() => import("./app").then((m) => ({ default: m.CatalogList })));
const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.DashboardPage })));
const QuotesLayout = lazy(() => import("./app").then((m) => ({ default: m.QuotesLayout })));
const QuotesList = lazy(() => import("./app").then((m) => ({ default: m.QuotesList })));*/
export const AppRoutes = (): JSX.Element => {
return (
<Router>
<ScrollToTop />
<Suspense fallback={<LoadingOverlay />}>
<Routes>
<Route element={<AppLayout />}>
{/* Main Layout */}
<Route index element={<ErrorPage />} />
<Route path='/dashboard' element={<ErrorPage />} />
<Route path='/settings' element={<ErrorPage />} />
<Route path='/catalog' element={<ErrorPage />} />
<Route path='/quotes' element={<ErrorPage />} />
{/* Dynamic Module Routes */}
{modules.map((module) => {
if (module.routes) {
return module.routes();
}
return null;
})}
</Route>
{/* Auth Layout */}
{/*<Route path="/signin" element={<SignIn />} />
<Route path="/signup" element={<SignUp />} />*/}
{/* Fallback Route */}
<Route path='*' element={<ErrorPage />} />
</Routes>
</Suspense>
</Router>
);
};

View File

@ -1,18 +1,16 @@
import { LoadingOverlay, TailwindIndicator } from "@repo/rdx-ui/components";
import { Toaster, TooltipProvider } from "@repo/shadcn-ui/components"; import { Toaster, TooltipProvider } from "@repo/shadcn-ui/components";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { Suspense } from "react";
import { I18nextProvider } from "react-i18next"; import { I18nextProvider } from "react-i18next";
import { createAxiosDataProvider } from "@/lib/axios/create-axios-data-provider"; import { createAxiosDataProvider } from "@/lib/axios/create-axios-data-provider";
import { DataSourceProvider, ThemeProvider, UnsavedWarnProvider } from "@/lib/hooks"; import { DataSourceProvider, UnsavedWarnProvider } from "@/lib/hooks";
import { i18n } from "@/locales"; import { i18n } from "@/locales";
import "./App.css"; import { AppRoutes } from "./app-routes";
import { Routes } from "./routes"; import "./app.css";
function App() { export const App = () => {
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
@ -26,22 +24,16 @@ function App() {
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<DataSourceProvider dataSource={createAxiosDataProvider(import.meta.env.VITE_API_URL)}> <DataSourceProvider dataSource={createAxiosDataProvider(import.meta.env.VITE_API_URL)}>
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'> <TooltipProvider delayDuration={0}>
<TooltipProvider delayDuration={0}> <UnsavedWarnProvider>
<UnsavedWarnProvider> <AppRoutes />
<Suspense fallback={<LoadingOverlay />}> </UnsavedWarnProvider>
<Routes /> </TooltipProvider>
</Suspense> <Toaster />
</UnsavedWarnProvider>
</TooltipProvider> {import.meta.env.MODE === "development" && <ReactQueryDevtools initialIsOpen={false} />}
<Toaster />
<TailwindIndicator />
{import.meta.env.MODE === "development" && <ReactQueryDevtools initialIsOpen={false} />}
</ThemeProvider>
</DataSourceProvider> </DataSourceProvider>
</QueryClientProvider> </QueryClientProvider>
</I18nextProvider> </I18nextProvider>
); );
} };
export default App;

View File

@ -1,6 +1,8 @@
import { ThemeProvider } from "@/lib/hooks";
import { TailwindIndicator } from "@repo/rdx-ui/components";
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import App from "./App.tsx"; import { App } from "./app.tsx";
import "./global.css"; import "./global.css";
@ -8,7 +10,10 @@ const rootElement = document.getElementById("factuges");
if (rootElement) { if (rootElement) {
createRoot(rootElement).render( createRoot(rootElement).render(
<StrictMode> <StrictMode>
<App /> <ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
<App />
<TailwindIndicator />
</ThemeProvider>
</StrictMode> </StrictMode>
); );
} else { } else {

View File

@ -1,197 +0,0 @@
import { LoadingOverlay } from "@repo/rdx-ui/components";
import { Suspense, lazy } from "react";
import { RouteObject, RouterProvider, createBrowserRouter } from "react-router-dom";
import { modules } from "./register-modules"; // Aquí ca
// Lazy load components
const ErrorPage = lazy(() => import("./pages").then((m) => ({ default: m.ErrorPage })));
//const LogoutPage = lazy(() => import("./app").then((m) => ({ default: m.LogoutPage })));
/*const DealerLayout = lazy(() => import("./app").then((m) => ({ default: m.DealerLayout })));
const DealersList = lazy(() => import("./app").then((m) => ({ default: m.DealersList })));
const LoginPageWithLanguageSelector = lazy(() =>
import("./app").then((m) => ({ default: m.LoginPageWithLanguageSelector }))
);
const QuoteCreate = lazy(() => import("./app").then((m) => ({ default: m.QuoteCreate })));
const QuoteEdit = lazy(() => import("./app").then((m) => ({ default: m.QuoteEdit })));
const SettingsEditor = lazy(() => import("./app").then((m) => ({ default: m.SettingsEditor })));
const SettingsLayout = lazy(() => import("./app").then((m) => ({ default: m.SettingsLayout })));
const CatalogLayout = lazy(() => import("./app").then((m) => ({ default: m.CatalogLayout })));
const CatalogList = lazy(() => import("./app").then((m) => ({ default: m.CatalogList })));
const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.DashboardPage })));
const QuotesLayout = lazy(() => import("./app").then((m) => ({ default: m.QuotesLayout })));
const QuotesList = lazy(() => import("./app").then((m) => ({ default: m.QuotesList })));*/
export const Routes = () => {
const routesForErrors = [
{
path: "*",
Element: (
<Suspense fallback={<LoadingOverlay />}>
<ErrorPage />
</Suspense>
),
},
];
const routesForModules: RouteObject[] = [];
modules.map((module) => {
if (module.routes) {
routesForModules.push(...module.routes());
}
});
// Define routes accessible only to authenticated users
/*const routesForAuthenticatedOnly = [
{
path: "/",
element: (
<ProtectedRoute>
<Navigate to='/quotes' replace={true} />
</ProtectedRoute>
),
},
{
path: "/home",
element: (
<ProtectedRoute>
<Suspense fallback={<LoadingOverlay />}>
<DashboardPage />
</Suspense>
</ProtectedRoute>
),
},
{
path: "/catalog",
element: (
<Suspense fallback={<LoadingOverlay />}>
<CatalogLayout>
<Outlet />
</CatalogLayout>
</Suspense>
),
children: [
{
index: true,
element: (
<Suspense fallback={<LoadingOverlay />}>
<CatalogList />
</Suspense>
),
},
],
},
{
path: "/dealers",
element: (
<Suspense fallback={<LoadingOverlay />}>
<DealerLayout>
<Outlet />
</DealerLayout>
</Suspense>
),
children: [
{
index: true,
element: (
<Suspense fallback={<LoadingOverlay />}>
<DealersList />
</Suspense>
),
},
],
},
{
path: "/quotes",
element: (
<Suspense fallback={<LoadingOverlay />}>
<QuotesLayout>
<Outlet />
</QuotesLayout>
</Suspense>
),
children: [
{
index: true,
element: (
<Suspense fallback={<LoadingOverlay />}>
<QuotesList />
</Suspense>
),
},
{
path: "add",
element: (
<Suspense fallback={<LoadingOverlay />}>
<QuoteCreate />
</Suspense>
),
},
{
path: "edit/:id",
element: (
<Suspense fallback={<LoadingOverlay />}>
<QuoteEdit />
</Suspense>
),
},
],
},
{
path: "/settings",
element: (
<Suspense fallback={<LoadingOverlay />}>
<SettingsLayout>
<Outlet />
</SettingsLayout>
</Suspense>
),
children: [
{
index: true,
element: (
<Suspense fallback={<LoadingOverlay />}>
<SettingsEditor />
</Suspense>
),
},
],
},
{
path: "/logout",
element: <LogoutPage />,
},
];
// Define routes accessible only to non-authenticated users
const routesForNotAuthenticatedOnly = [
{
path: "/login",
element: (
<Suspense fallback={<LoadingOverlay />}>
<LoginPageWithLanguageSelector />
</Suspense>
),
},
];*/
// Combine and conditionally include routes based on authentication status
const router = createBrowserRouter(
[
//...routesForAuthenticatedOnly,
//...routesForNotAuthenticatedOnly,
...routesForErrors,
...routesForModules,
],
{
//basename: "/app",
}
);
// Provide the router configuration using RouterProvider
return <RouterProvider router={router} />;
};

View File

@ -1,5 +1,4 @@
import React, { ReactNode } from "react"; import React, { JSX, ReactNode } from "react";
import { RouteObject } from "react-router-dom";
import { ModuleMetadata } from "./types"; import { ModuleMetadata } from "./types";
export interface ModuleClientMetadata extends ModuleMetadata { export interface ModuleClientMetadata extends ModuleMetadata {
@ -9,7 +8,7 @@ export interface ModuleClientMetadata extends ModuleMetadata {
export interface IModuleClient { export interface IModuleClient {
metadata: ModuleClientMetadata; metadata: ModuleClientMetadata;
routes?: () => RouteObject[]; routes?: () => JSX.Element;
component?: React.FC; component?: React.FC;
setup?(): void; setup?(): void;
} }

View File

@ -18,7 +18,8 @@
"ag-grid-community": "^33.3.0", "ag-grid-community": "^33.3.0",
"i18next": "^25.1.1", "i18next": "^25.1.1",
"react": "^18 || ^19", "react": "^18 || ^19",
"react-dom": "^18 || ^19" "react-dom": "^18 || ^19",
"react-router-dom": "^6.26.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",

View File

@ -57,8 +57,9 @@ export const InvoicesGrid = () => {
{ {
field: "mission", field: "mission",
filter: true, filter: true,
minWidth: 200,
}, },
{ field: "company" }, { field: "company", filter: false },
{ field: "location" }, { field: "location" },
{ field: "date" }, { field: "date" },
{ {
@ -75,12 +76,17 @@ export const InvoicesGrid = () => {
const defaultColDef = useMemo<ColDef>(() => { const defaultColDef = useMemo<ColDef>(() => {
return { return {
filter: true, filter: true,
sortable: false,
resizable: false,
}; };
}, []); }, []);
// Container: Defines the grid's theme & dimensions. // Container: Defines the grid's theme & dimensions.
return ( return (
<div style={{ height: 1500 }}> <div
style={{ height: 1500 }}
className='*:data-[slot=card]:shadow-xs @xl/main:grid-cols-2 @5xl/main:grid-cols-4 grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card lg:px-6'
>
<AgGridReact <AgGridReact
rowData={data} rowData={data}
loading={loading} loading={loading}

View File

@ -1,7 +1,3 @@
import { AppSidebar } from "@repo/rdx-ui/components/layout/app-sidebar";
import { ChartAreaInteractive } from "@repo/rdx-ui/components/layout/chart-area-interactive";
import { SiteHeader } from "@repo/rdx-ui/components/layout/site-header";
import { SidebarInset, SidebarProvider } from "@repo/shadcn-ui/components";
import { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
import { SectionCards } from "@repo/rdx-ui/components/layout/section-cards"; import { SectionCards } from "@repo/rdx-ui/components/layout/section-cards";
@ -11,29 +7,9 @@ export const InvoicesLayout = ({ children }: PropsWithChildren) => {
const { t } = useTranslation("invoices"); const { t } = useTranslation("invoices");
return ( return (
<SidebarProvider <>
style={ <SectionCards />
{ {children}
"--sidebar-width": "calc(var(--spacing) * 72)", </>
"--header-height": "calc(var(--spacing) * 12)",
} as React.CSSProperties
}
>
<AppSidebar variant='inset' />
<SidebarInset>
<SiteHeader title={t("invoices.title")} />
<div className='flex flex-1 flex-col'>
<div className='@container/main flex flex-1 flex-col gap-2'>
<div className='flex flex-col gap-4 py-4 md:gap-6 md:py-6'>
<SectionCards />
<div className='px-4 lg:px-6'>
<ChartAreaInteractive />
</div>
{children}
</div>
</div>
</div>
</SidebarInset>
</SidebarProvider>
); );
}; };

View File

@ -1,8 +1,6 @@
//import { ProtectedRoute } from "@erp/auth/components"; //import { ProtectedRoute } from "@erp/auth/components";
import { LoadingOverlay } from "@repo/rdx-ui/components"; import { JSX, lazy } from "react";
import { Suspense, lazy } from "react"; import { Route } from "react-router-dom";
import { Outlet, RouteObject } from "react-router-dom";
import { InvoicesGrid } from "./components";
// Lazy load components // Lazy load components
const InvoicesLayout = lazy(() => const InvoicesLayout = lazy(() =>
@ -29,32 +27,24 @@ const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.Dashb
const InvoicesLayout = lazy(() => import("./app").then((m) => ({ default: m.InvoicesLayout }))); const InvoicesLayout = lazy(() => import("./app").then((m) => ({ default: m.InvoicesLayout })));
const InvoicesList = lazy(() => import("./app").then((m) => ({ default: m.InvoicesList })));*/ const InvoicesList = lazy(() => import("./app").then((m) => ({ default: m.InvoicesList })));*/
export const InvoiceRoutes = (): RouteObject[] => { export const InvoiceRoutes = (): JSX.Element => {
// Define routes accessible only to authenticated users return (
const privateRoutes: RouteObject[] = [ <Route path='/invoices' element={<InvoicesLayout />}>
{ <Route index element={<InvoicesList />} />
path: "/invoices", <Route path='list' element={<InvoicesList />} />
element: (
<Suspense fallback={<LoadingOverlay />}>
<InvoicesLayout>
<Outlet />
</InvoicesLayout>
</Suspense>
),
children: [
{
index: true,
element: <InvoicesGrid />,
},
],
},
];
// Define routes accessible only to non-authenticated users {/*<Route path='create' element={<InvoiceCreate />} />
const publicRoutes = [{}]; <Route path=':id' element={<InvoicesList />} />
<Route path=':id/edit' element={<InvoicesList />} />
<Route path=':id/delete' element={<InvoicesList />} />
<Route path=':id/view' element={<InvoicesList />} />
<Route path=':id/print' element={<InvoicesList />} />
<Route path=':id/email' element={<InvoicesList />} />
<Route path=':id/download' element={<InvoicesList />} />
<Route path=':id/duplicate' element={<InvoicesList />} />
<Route path=':id/preview' element={<InvoicesList />} />*/}
return privateRoutes; <Route path='*' element={<InvoicesList />} />
</Route>
// Provide the router configuration using RouterProvider );
//return <RouterProvider router={router} />;
}; };

View File

@ -2,7 +2,7 @@ import { IModuleClient } from "@erp/core";
import i18next from "i18next"; import i18next from "i18next";
import enResources from "../common/locales/en.json"; import enResources from "../common/locales/en.json";
import esResources from "../common/locales/es.json"; import esResources from "../common/locales/es.json";
import { InvoiceRoutes } from "./routes"; import { InvoiceRoutes } from "./invoice-routes";
const MODULE_NAME = "invoices"; const MODULE_NAME = "invoices";
const MODULE_VERSION = "1.0.0"; const MODULE_VERSION = "1.0.0";

View File

@ -16,7 +16,8 @@
}, },
"peerDependencies": { "peerDependencies": {
"react": "^18 || ^19", "react": "^18 || ^19",
"react-dom": "^18 || ^19" "react-dom": "^18 || ^19",
"react-router": "^6.26.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
@ -49,6 +50,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",
"react-router": "^6.26.0",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"sonner": "^2.0.3", "sonner": "^2.0.3",
"zod": "^3.24.4" "zod": "^3.24.4"

View File

@ -1,5 +1,7 @@
export * from "./tailwind-indicator.tsx"; export * from "./buttons/index.tsx";
export * from "./loading-overlay/index.tsx";
export * from "./custom-dialog.tsx"; export * from "./custom-dialog.tsx";
export * from "./error-overlay.tsx"; export * from "./error-overlay.tsx";
export * from "./buttons/index.tsx"; export * from "./layout/index.tsx";
export * from "./loading-overlay/index.tsx";
export * from "./scroll-to-top.tsx";
export * from "./tailwind-indicator.tsx";

View File

@ -0,0 +1,30 @@
import { SidebarInset, SidebarProvider } from "@repo/shadcn-ui/components";
import { Outlet } from "react-router";
import { AppSidebar } from "./app-sidebar.tsx";
import { SiteHeader } from "./site-header.tsx";
export const AppLayout: React.FC = () => {
return (
<SidebarProvider
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
"--header-height": "calc(var(--spacing) * 12)",
} as React.CSSProperties
}
>
<AppSidebar variant='inset' />
<SidebarInset>
<SiteHeader title={"Título"} />
<div className='flex flex-1 flex-col'>
<div className='@container/main flex flex-1 flex-col gap-2'>
<div className='flex flex-col gap-4 py-4 md:gap-6 md:py-6'>
<Outlet />
</div>
</div>
</div>
</SidebarInset>
</SidebarProvider>
);
};

View File

@ -1,9 +1 @@
export * from "./app-sidebar.tsx"; export * from "./app-layout.tsx";
export * from "./chart-area-interactive.tsx";
export * from "./data-table.tsx";
export * from "./nav-documents.tsx";
export * from "./nav-main.tsx";
export * from "./nav-secondary.tsx";
export * from "./nav-user.tsx";
export * from "./section-cards.tsx";
export * from "./site-header.tsx";

View File

@ -0,0 +1,17 @@
import { useEffect } from "react";
import { useLocation } from "react-router";
export function ScrollToTop() {
const { pathname } = useLocation();
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
window.scrollTo({
top: 0,
left: 0,
behavior: "smooth",
});
}, [pathname]);
return null;
}

View File

@ -170,7 +170,7 @@ importers:
version: 29.7.0(@types/node@22.15.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3)) version: 29.7.0(@types/node@22.15.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3))
ts-jest: ts-jest:
specifier: ^29.2.5 specifier: ^29.2.5
version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@22.15.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3)))(typescript@5.8.3) version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(esbuild@0.25.4)(jest@29.7.0(@types/node@22.15.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3)))(typescript@5.8.3)
tsconfig-paths: tsconfig-paths:
specifier: ^4.2.0 specifier: ^4.2.0
version: 4.2.0 version: 4.2.0
@ -507,6 +507,9 @@ importers:
react-i18next: react-i18next:
specifier: ^15.5.1 specifier: ^15.5.1
version: 15.5.1(i18next@25.1.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) version: 15.5.1(i18next@25.1.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
react-router:
specifier: ^6.26.0
version: 6.30.0(react@19.1.0)
react-router-dom: react-router-dom:
specifier: ^6.26.0 specifier: ^6.26.0
version: 6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -11377,7 +11380,7 @@ snapshots:
ts-interface-checker@0.1.13: {} ts-interface-checker@0.1.13: {}
ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@22.15.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3)))(typescript@5.8.3): ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(esbuild@0.25.4)(jest@29.7.0(@types/node@22.15.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3)))(typescript@5.8.3):
dependencies: dependencies:
bs-logger: 0.2.6 bs-logger: 0.2.6
ejs: 3.1.10 ejs: 3.1.10
@ -11396,6 +11399,7 @@ snapshots:
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.27.1) babel-jest: 29.7.0(@babel/core@7.27.1)
esbuild: 0.25.4
ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3): ts-node@10.9.2(@types/node@22.15.12)(typescript@5.8.3):
dependencies: dependencies: