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 { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { Suspense } from "react";
import { I18nextProvider } from "react-i18next";
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 "./App.css";
import { Routes } from "./routes";
import { AppRoutes } from "./app-routes";
import "./app.css";
function App() {
export const App = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@ -26,22 +24,16 @@ function App() {
<I18nextProvider i18n={i18n}>
<QueryClientProvider client={queryClient}>
<DataSourceProvider dataSource={createAxiosDataProvider(import.meta.env.VITE_API_URL)}>
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
<TooltipProvider delayDuration={0}>
<UnsavedWarnProvider>
<Suspense fallback={<LoadingOverlay />}>
<Routes />
</Suspense>
<AppRoutes />
</UnsavedWarnProvider>
</TooltipProvider>
<Toaster />
<TailwindIndicator />
{import.meta.env.MODE === "development" && <ReactQueryDevtools initialIsOpen={false} />}
</ThemeProvider>
</DataSourceProvider>
</QueryClientProvider>
</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 { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { App } from "./app.tsx";
import "./global.css";
@ -8,7 +10,10 @@ const rootElement = document.getElementById("factuges");
if (rootElement) {
createRoot(rootElement).render(
<StrictMode>
<ThemeProvider defaultTheme='light' storageKey='vite-ui-theme'>
<App />
<TailwindIndicator />
</ThemeProvider>
</StrictMode>
);
} 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 { RouteObject } from "react-router-dom";
import React, { JSX, ReactNode } from "react";
import { ModuleMetadata } from "./types";
export interface ModuleClientMetadata extends ModuleMetadata {
@ -9,7 +8,7 @@ export interface ModuleClientMetadata extends ModuleMetadata {
export interface IModuleClient {
metadata: ModuleClientMetadata;
routes?: () => RouteObject[];
routes?: () => JSX.Element;
component?: React.FC;
setup?(): void;
}

View File

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

View File

@ -57,8 +57,9 @@ export const InvoicesGrid = () => {
{
field: "mission",
filter: true,
minWidth: 200,
},
{ field: "company" },
{ field: "company", filter: false },
{ field: "location" },
{ field: "date" },
{
@ -75,12 +76,17 @@ export const InvoicesGrid = () => {
const defaultColDef = useMemo<ColDef>(() => {
return {
filter: true,
sortable: false,
resizable: false,
};
}, []);
// Container: Defines the grid's theme & dimensions.
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
rowData={data}
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 { SectionCards } from "@repo/rdx-ui/components/layout/section-cards";
@ -11,29 +7,9 @@ export const InvoicesLayout = ({ children }: PropsWithChildren) => {
const { t } = useTranslation("invoices");
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("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 { LoadingOverlay } from "@repo/rdx-ui/components";
import { Suspense, lazy } from "react";
import { Outlet, RouteObject } from "react-router-dom";
import { InvoicesGrid } from "./components";
import { JSX, lazy } from "react";
import { Route } from "react-router-dom";
// Lazy load components
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 InvoicesList = lazy(() => import("./app").then((m) => ({ default: m.InvoicesList })));*/
export const InvoiceRoutes = (): RouteObject[] => {
// Define routes accessible only to authenticated users
const privateRoutes: RouteObject[] = [
{
path: "/invoices",
element: (
<Suspense fallback={<LoadingOverlay />}>
<InvoicesLayout>
<Outlet />
</InvoicesLayout>
</Suspense>
),
children: [
{
index: true,
element: <InvoicesGrid />,
},
],
},
];
export const InvoiceRoutes = (): JSX.Element => {
return (
<Route path='/invoices' element={<InvoicesLayout />}>
<Route index element={<InvoicesList />} />
<Route path='list' element={<InvoicesList />} />
// Define routes accessible only to non-authenticated users
const publicRoutes = [{}];
{/*<Route path='create' element={<InvoiceCreate />} />
<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;
// Provide the router configuration using RouterProvider
//return <RouterProvider router={router} />;
<Route path='*' element={<InvoicesList />} />
</Route>
);
};

View File

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

View File

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

View File

@ -1,5 +1,7 @@
export * from "./tailwind-indicator.tsx";
export * from "./loading-overlay/index.tsx";
export * from "./buttons/index.tsx";
export * from "./custom-dialog.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 "./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";
export * from "./app-layout.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))
ts-jest:
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:
specifier: ^4.2.0
version: 4.2.0
@ -507,6 +507,9 @@ importers:
react-i18next:
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)
react-router:
specifier: ^6.26.0
version: 6.30.0(react@19.1.0)
react-router-dom:
specifier: ^6.26.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-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:
bs-logger: 0.2.6
ejs: 3.1.10
@ -11396,6 +11399,7 @@ snapshots:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
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):
dependencies: