diff --git a/package-lock.json b/package-lock.json
index bcac0ac..b523520 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"axios": "^1.6.7",
"dayjs": "^1.11.10",
"firebase": "^10.8.0",
+ "framer-motion": "^11.3.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-photoswipe-gallery": "^3.0.1",
@@ -3565,6 +3566,30 @@
"node": ">= 6"
}
},
+ "node_modules/framer-motion": {
+ "version": "11.3.12",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.12.tgz",
+ "integrity": "sha512-ulc8EHFZpKIj+NAyJv+alLUEUIXZKOQnE+JHkGjfoIcxbZwV+CSvfOoACaOpAW4nVznFMF2y3r+ViUtPtP4qiw==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
diff --git a/repopack-refadiazfrontend-output.txt b/repopack-refadiazfrontend-output.txt
index b5fde1f..e737629 100644
--- a/repopack-refadiazfrontend-output.txt
+++ b/repopack-refadiazfrontend-output.txt
@@ -2,7 +2,7 @@
REPOPACK OUTPUT FILE
================================================================
-This file was generated by Repopack on: 2024-07-23T03:31:19.211Z
+This file was generated by Repopack on: 2024-07-24T03:00:28.974Z
Purpose:
--------
@@ -198,6 +198,7 @@ File: package.json
"axios": "^1.6.7",
"dayjs": "^1.11.10",
"firebase": "^10.8.0",
+ "framer-motion": "^11.3.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-photoswipe-gallery": "^3.0.1",
@@ -957,6 +958,78 @@ export const MobileProvider = ({ children }) => {
);
};
+================
+File: src/components/NavigationManager.jsx
+================
+import React, { useState, useCallback } from 'react';
+import { Breadcrumbs, Link, Typography } from '@mui/material';
+const NavigationManager = ({ initialComponent, initialTitle }) => {
+ const [navigationStack, setNavigationStack] = useState([
+ { component: initialComponent, title: initialTitle, onBack: null }
+ ]);
+ const navigate = useCallback((component, title, onBack = null) => {
+ console.log('Navigating to:', onBack);
+ setNavigationStack(prevStack => [...prevStack, { component, title, onBack }]);
+ }, []);
+ const navigateBack = useCallback(() => {
+ setNavigationStack(prevStack => {
+ if (prevStack.length > 1) {
+ const currentView = prevStack[prevStack.length - 1];
+ if (currentView.onBack) {
+ currentView.onBack();
+ }
+ return prevStack.slice(0, -1);
+ }
+ return prevStack;
+ });
+ }, []);
+ const resetNavigation = useCallback(() => {
+ setNavigationStack(prevStack => {
+ prevStack.slice(1).forEach(view => {
+ if (view.onBack) {
+ view.onBack();
+ }
+ });
+ return [prevStack[0]];
+ });
+ }, []);
+ const currentView = navigationStack[navigationStack.length - 1];
+ const renderBreadcrumbs = () => (
+
+ {navigationStack.map((item, index) => {
+ const isLast = index === navigationStack.length - 1;
+ return isLast ? (
+ {item.title}
+ ) : (
+ {
+ e.preventDefault();
+ navigationStack.slice(index + 1).forEach(view => {
+ if (view.onBack) {
+ view.onBack();
+ }
+ });
+ setNavigationStack(prevStack => prevStack.slice(0, index + 1));
+ }}
+ >
+ {item.title}
+
+ );
+ })}
+
+ );
+ return (
+
+ {renderBreadcrumbs()}
+ {React.cloneElement(currentView.component, { navigate, navigateBack, resetNavigation })}
+
+ );
+};
+export default NavigationManager;
+
================
File: src/components/ProtectedComponent.jsx
================
@@ -1131,11 +1204,11 @@ import UserPage from '../../pages/Users/UserPage';
import ProductsPage from '../../pages/Products/ProductsPage';
import ProviderPage from '../../pages/Providers/ProviderPage';
const mainMenus = [
- { text: 'Productos', icon: , roles: [ROLES.ADMIN, ROLES.EMPLOYEE], component: },
- { text: 'Proveedores', icon: , roles: [ROLES.ADMIN, ROLES.EMPLOYEE], component: },
+ { text: 'Productos', icon: , roles: [ROLES.ADMIN, ROLES.EMPLOYEE], component: , title: 'Productos' },
+ { text: 'Proveedores', icon: , roles: [ROLES.ADMIN, ROLES.EMPLOYEE], component: , title: 'Proveedores' },
];
const adminMenus = [
- { text: 'Usuarios', icon: , roles: [ROLES.ADMIN], component: }
+ { text: 'Usuarios', icon: , roles: [ROLES.ADMIN], component: , title: 'Gestión de Usuarios' }
];
export { mainMenus, adminMenus }
@@ -1633,12 +1706,15 @@ import '../../../styles/brandContainer.css';
import { useSnackbar } from '../../../components/SnackbarContext';
import { useProductsContext } from '../ProductsContext';
import { Screens } from '../ProductsConstants';
-const BrandContainer = () => {
+import CarModelListContainer from '../ModelViewer/CarModelContainer';
+import ListContainer from '../ListContainer';
+const BrandContainer = ({navigate}) => {
const [brands, setBrands] = useState([]);
const { openSnackbar } = useSnackbar();
- const { handleItemSelect, searchTerm, setLoading } = useProductsContext();
+ const { handleItemSelect, searchTerm, setLoading, navigateBack} = useProductsContext();
const onBrandSelect = (e, brand) => {
handleItemSelect(brand, Screens.BRANDS);
+ navigate(, 'Modelos', navigateBack);
}
useEffect(() => {
setLoading(true);
@@ -1667,17 +1743,31 @@ const BrandContainer = () => {
fetchBrands();
}, [setLoading]);
return (
- 0}
- timeout={300}
- classNames="fade"
- unmountOnExit
- >
-
- brand.brandTypeId === 1 && brand.name.toLowerCase().includes(searchTerm.toLowerCase()))} onBrandSelect={onBrandSelect} />
- brand.brandTypeId === 2 && brand.name.toLowerCase().includes(searchTerm.toLowerCase()))} onBrandSelect={onBrandSelect} />
-
-
+
+ 0}
+ timeout={300}
+ classNames="fade"
+ unmountOnExit
+ >
+
+
+ brand.brandTypeId === 1 && brand.name.toLowerCase().includes(searchTerm.toLowerCase())
+ )}
+ onBrandSelect={onBrandSelect}
+ />
+
+ brand.brandTypeId === 2 && brand.name.toLowerCase().includes(searchTerm.toLowerCase())
+ )}
+ onBrandSelect={onBrandSelect}
+ />
+
+
+
);
};
export default BrandContainer;
@@ -1764,6 +1854,42 @@ const RadiatorFormContainer = ({ setIsFormValid }) => {
};
export { RadiatorFormContainer as default, RadiatorFormDisplay}
+================
+File: src/pages/Products/ListContainer.jsx
+================
+import { Box, CircularProgress, Fab } from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+import CustomSearchBar from '../../components/CustomSearchBar';
+import { useProductsContext } from './ProductsContext';
+import ProductDialog from './ProductDialog/ProductDialog';
+const ListContainer = ({ children }) => {
+ const { loading, handleOpenDialog } = useProductsContext();
+ return (
+ *:not(style)': { mb: 3 } }}>
+
+
+ {loading ? (
+
+
+
+ ) : (
+ {children}
+ )}
+
+
+
+
+
+
+ );
+};
+export default ListContainer;
+
================
File: src/pages/Products/ModelViewer/CarModelContainer.jsx
================
@@ -1774,12 +1900,15 @@ import CarModelList from './CarModelList';
import { deleteCarModel, getCarModels } from '../../../services/CarModelService';
import { useProductsContext } from '../ProductsContext';
import { Screens } from '../ProductsConstants';
-const CarModelListContainer = () => {
+import ProductContainer from '../ProductViewer/ProductContainer';
+import ListContainer from '../ListContainer';
+const CarModelListContainer = ({navigate}) => {
const [carModels, setCarModels] = useState([]);
const { openSnackbar } = useSnackbar();
- const { selectedBrand, handleItemSelect, setLoading, searchTerm } = useProductsContext();
+ const { selectedBrand, handleItemSelect, setLoading, searchTerm, navigateBack} = useProductsContext();
const onCarModelSelect = (e, carModel) => {
handleItemSelect(carModel, Screens.MODELS);
+ navigate(, 'Productos', navigateBack);
}
const handleOnDelete = async (carModel) => {
try {
@@ -1815,7 +1944,9 @@ const CarModelListContainer = () => {
fetchCarModels();
}, [selectedBrand, searchTerm]);
return (
-
+
+
+
);
};
export default CarModelListContainer;
@@ -2955,23 +3086,37 @@ import { useProductsContext } from './ProductsContext';
import { Screens } from './ProductsConstants';
import ProductContainer from './ProductViewer/ProductContainer';
import CarModelListContainer from './ModelViewer/CarModelContainer';
+import ProductTypeSelector from './ProductTypeSelector';
const ProductSelector = () => {
- const { currentScreen, loading } = useProductsContext();
+ const { currentScreen, loading, productType } = useProductsContext();
+ if (!productType) {
+ return ;
+ }
+ const renderContent = () => {
+ switch (currentScreen) {
+ case Screens.BRANDS:
+ return ;
+ case Screens.MODELS:
+ return ;
+ case Screens.PRODUCTS:
+ return ;
+ default:
+ return null;
+ }
+ };
return (
- <>
+
- {currentScreen === Screens.BRANDS && }
- {currentScreen === Screens.MODELS && }
- {currentScreen === Screens.PRODUCTS && }
+ {renderContent()}
{loading && (
-
+
-
+
)}
- >
+
);
};
export default ProductSelector;
@@ -3080,31 +3225,67 @@ const ProductSummary = ({ productType, product }) => {
export default ProductSummary;
================
-File: src/pages/Products/ProductTypeTabs.jsx
+File: src/pages/Products/ProductTypeSelector.jsx
================
-import { Tab, Tabs } from "@mui/material"
-import { useProductsContext } from "./ProductsContext";
-import { useMobile } from "../../components/MobileProvider";
-import { ProductTypes } from "./ProductsConstants";
-export const ProductTypeTabs = () => {
- const { productType, handleChangeProductType } = useProductsContext();
- const responsive = useMobile();
- const handleChange = (event, newValue) => {
- handleChangeProductType(newValue);
- };
- return
-
-
-
-
-}
+import { Grid, Card, CardContent, CardMedia, Typography, CardActionArea } from '@mui/material';
+import { ProductTypes } from './ProductsConstants';
+import { useProductsContext } from './ProductsContext';
+import BrandContainer from './BrandViewer/BrandContainer';
+const productTypeData = [
+ {
+ type: ProductTypes.RADIATOR,
+ title: "Radiadores",
+ description: "Para todo tipo de vehículos",
+ image: "/src/assets/radiator.jpeg"
+ },
+ {
+ type: ProductTypes.CAP,
+ title: "Tapas",
+ description: "De radiador y depósito",
+ image: "/src/assets/caps.jpeg"
+ },
+ {
+ type: ProductTypes.FAN,
+ title: "Abanicos",
+ description: "Sistemas de enfriamiento",
+ image: "/src/assets/fans.jpeg"
+ }
+];
+const ProductTypeSelector = ({navigate}) => {
+ const { handleChangeProductType } = useProductsContext();
+ const handleOnProductTypeClick = (type) => {
+ handleChangeProductType(type);
+ navigate(, 'Marcas');
+ }
+ return (
+
+ {productTypeData.map((item) => (
+
+
+ handleOnProductTypeClick(item.type)} sx={{ flexGrow: 1 }}>
+
+
+
+ {item.title}
+
+
+ {item.description}
+
+
+
+
+
+ ))}
+
+ );
+};
+export default ProductTypeSelector;
================
File: src/pages/Products/ProductViewer/ProductContainer.jsx
@@ -3120,6 +3301,7 @@ import { Screens } from '../ProductsConstants';
import ProductList from './ProductList';
import { ProductCarModel } from '../../../models/ProductCarModel';
import { deleteProduct } from '../../../services/ProductService';
+import ListContainer from '../ListContainer';
const ProductContainer = () => {
const [productCarModels, setProductCarModels] = useState([]);
const { openSnackbar } = useSnackbar();
@@ -3181,7 +3363,8 @@ const ProductContainer = () => {
fetchProducts();
}, [searchTerm, setLoading]);
return (
-
+ 0}
timeout={300}
classNames="fade"
@@ -3191,6 +3374,7 @@ const ProductContainer = () => {
+
);
};
export default ProductContainer;
@@ -3249,7 +3433,7 @@ export const FileTypes = {
File: src/pages/Products/ProductsContext.jsx
================
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
-import { ProductTypes, Screens, SearchOptions } from './ProductsConstants';
+import { Screens, SearchOptions } from './ProductsConstants';
const ProductsContext = createContext();
export const useProductsContext = () => {
const context = useContext(ProductsContext);
@@ -3259,7 +3443,7 @@ export const useProductsContext = () => {
return context;
};
export const ProductsProvider = ({ children }) => {
- const [productType, setProductType] = useState(ProductTypes.RADIATOR);
+ const [productType, setProductType] = useState(null);
const [currentScreen, setCurrentScreen] = useState(Screens.BRANDS);
const [openDialog, setOpenDialog] = useState(false);
const [selectedProduct, setSelectedProduct] = useState(null);
@@ -3278,9 +3462,11 @@ export const ProductsProvider = ({ children }) => {
case Screens.PRODUCTS:
setCurrentScreen(Screens.MODELS);
setSelectedCarModel(null);
+ setSearchOption(SearchOptions.MODELS);
break;
case Screens.MODELS:
setCurrentScreen(Screens.BRANDS);
+ setSearchOption(SearchOptions.BRANDS);
setSelectedBrand(null);
break;
case Screens.BRANDS:
@@ -3387,34 +3573,13 @@ export const ProductsProvider = ({ children }) => {
================
File: src/pages/Products/ProductsPage.jsx
================
-import { Box, Fab } from "@mui/material";
-import ProductSelector from "./ProductSelector";
-import ProductDialog from "./ProductDialog/ProductDialog";
-import AddIcon from "@mui/icons-material/Add";
-import { ProductsProvider, useProductsContext } from "./ProductsContext";
-import { ProductTypeTabs } from "./ProductTypeTabs";
-function ProductsPresentation() {
- const { handleOpenDialog } = useProductsContext();
- return (
- *:not(style)': { mb: 3 } }}>
-
-
-
-
-
-
-
- );
-}
+import { ProductsProvider } from "./ProductsContext";
+import NavigationManager from "../../components/NavigationManager";
+import ProductTypeSelector from "./ProductTypeSelector";
export default function ProductsPage() {
return (
-
+ } initialTitle="Productos" />
)
}
diff --git a/src/assets/caps.jpeg b/src/assets/caps.jpeg
new file mode 100644
index 0000000..1e30eca
Binary files /dev/null and b/src/assets/caps.jpeg differ
diff --git a/src/assets/fans.jpeg b/src/assets/fans.jpeg
new file mode 100644
index 0000000..a5e2706
Binary files /dev/null and b/src/assets/fans.jpeg differ
diff --git a/src/assets/radiator.jpeg b/src/assets/radiator.jpeg
new file mode 100644
index 0000000..abacd38
Binary files /dev/null and b/src/assets/radiator.jpeg differ
diff --git a/src/components/CustomSearchBar.jsx b/src/components/CustomSearchBar.jsx
index c034988..b4c2ec0 100644
--- a/src/components/CustomSearchBar.jsx
+++ b/src/components/CustomSearchBar.jsx
@@ -1,24 +1,18 @@
import { Select, MenuItem } from '@mui/material';
import CustomInput from "./CustomInput";
import { useProductsContext } from '../pages/Products/ProductsContext';
-import { ProductTypes, SearchOptions } from '../pages/Products/ProductsConstants';
+import { SearchOptions } from '../pages/Products/ProductsConstants';
+import BrandContainer from '../pages/Products/BrandViewer/BrandContainer';
+import CarModelListContainer from '../pages/Products/ModelViewer/CarModelContainer';
+import ProductContainer from '../pages/Products/ProductViewer/ProductContainer';
+import { getProductVerbiage } from '../util/generalUtils';
-const CustomSearchBar = () => {
+const CustomSearchBar = ({ navigate }) => {
const { searchOption, searchTerm, handleSearchOptionChange, setSearchTerm, productType } = useProductsContext();
- const handleSearchChange = (e) => {
- const newSearchTerm = e.target.value;
- setSearchTerm(newSearchTerm);
- }
let placeholder = '';
- let productVerbiage = 'Radiadores';
-
- if (productType === ProductTypes.CAP) {
- productVerbiage = 'Tapas';
- } else if (productType === ProductTypes.FAN) {
- productVerbiage = 'Abanicos';
- }
+ let productVerbiage = getProductVerbiage(productType);
if (searchOption === SearchOptions.BRANDS) {
placeholder = 'Buscar marcas...';
@@ -28,12 +22,34 @@ const CustomSearchBar = () => {
placeholder = `Buscar ${productVerbiage}...`;
}
+ const handleSearchChange = (e) => {
+ const newSearchTerm = e.target.value;
+ setSearchTerm(newSearchTerm);
+ }
+
+ const handleOptionChange = (option) => {
+ handleSearchOptionChange(option);
+
+ switch (option) {
+ case SearchOptions.BRANDS:
+ navigate(, 'Marcas');
+ break;
+ case SearchOptions.MODELS:
+ navigate(, 'Modelos');
+ break;
+ case SearchOptions.PRODUCTS:
+ navigate(, productVerbiage);
+ break;
+ default:
+ break;
+ }
+ }
return (
+
);
};
diff --git a/src/pages/Products/ProductsContext.jsx b/src/pages/Products/ProductsContext.jsx
index 82bead3..1d0f1a5 100644
--- a/src/pages/Products/ProductsContext.jsx
+++ b/src/pages/Products/ProductsContext.jsx
@@ -1,5 +1,5 @@
-import { createContext, useContext, useState, useEffect, useCallback } from 'react';
-import { ProductTypes, Screens, SearchOptions } from './ProductsConstants';
+import { createContext, useContext, useState, useCallback } from 'react';
+import { Screens, SearchOptions } from './ProductsConstants';
const ProductsContext = createContext();
@@ -12,7 +12,7 @@ export const useProductsContext = () => {
};
export const ProductsProvider = ({ children }) => {
- const [productType, setProductType] = useState(ProductTypes.RADIATOR);
+ const [productType, setProductType] = useState(null);
const [currentScreen, setCurrentScreen] = useState(Screens.BRANDS);
const [openDialog, setOpenDialog] = useState(false);
const [selectedProduct, setSelectedProduct] = useState(null);
@@ -28,46 +28,34 @@ export const ProductsProvider = ({ children }) => {
setOpenDialog(false);
return;
}
-
switch (currentScreen) {
case Screens.PRODUCTS:
setCurrentScreen(Screens.MODELS);
setSelectedCarModel(null);
+ setSearchOption(SearchOptions.MODELS);
break;
case Screens.MODELS:
setCurrentScreen(Screens.BRANDS);
+ setSearchOption(SearchOptions.BRANDS);
setSelectedBrand(null);
break;
- case Screens.BRANDS:
- // Comportamiento predeterminado: salir de la aplicación
- if (window.history.length > 1) {
- window.history.back();
- }
- break;
default:
break;
}
}, [currentScreen, openDialog]);
- useEffect(() => {
- const handlePopState = (event) => {
- event.preventDefault();
- navigateBack();
- };
-
- window.addEventListener('popstate', handlePopState);
-
- return () => {
- window.removeEventListener('popstate', handlePopState);
- };
- }, [navigateBack]);
-
- const pushState = useCallback(() => {
- window.history.pushState(null, '', window.location.pathname);
- }, []);
+ const resetState = () => {
+ setCurrentScreen(Screens.BRANDS);
+ setOpenDialog(false);
+ setSelectedBrand(null);
+ setSelectedCarModel(null);
+ setSearchTerm('');
+ setSearchOption(SearchOptions.BRANDS);
+ setLoading(false);
+ setScrollPosition(0);
+ }
const handleItemSelect = (item, type) => {
- pushState();
switch (type) {
case Screens.BRANDS:
setSelectedBrand(item);
@@ -89,12 +77,11 @@ export const ProductsProvider = ({ children }) => {
};
const handleOpenDialog = () => {
- pushState();
setOpenDialog(true);
};
const handleCloseDialog = () => {
- navigateBack();
+ setOpenDialog(false);
};
const handleChangeProductType = (newValue) => {
@@ -104,7 +91,7 @@ export const ProductsProvider = ({ children }) => {
const handleSearchOptionChange = (value) => {
setSearchTerm('');
setSearchOption(value);
-
+ resetState();
if (value === SearchOptions.BRANDS) {
setCurrentScreen(Screens.BRANDS);
} else if (value === SearchOptions.MODELS) {
@@ -140,8 +127,7 @@ export const ProductsProvider = ({ children }) => {
handleCloseDialog,
handleChangeProductType,
handleSearchOptionChange,
- navigateBack,
- pushState
+ navigateBack
};
return (
diff --git a/src/pages/Products/ProductsPage.jsx b/src/pages/Products/ProductsPage.jsx
index a33f094..cf9ae00 100644
--- a/src/pages/Products/ProductsPage.jsx
+++ b/src/pages/Products/ProductsPage.jsx
@@ -1,35 +1,12 @@
-import { Box, Fab } from "@mui/material";
-import ProductSelector from "./ProductSelector";
-import ProductDialog from "./ProductDialog/ProductDialog";
-import AddIcon from "@mui/icons-material/Add";
-import { ProductsProvider, useProductsContext } from "./ProductsContext";
-import { ProductTypeTabs } from "./ProductTypeTabs";
-function ProductsPresentation() {
- const { handleOpenDialog } = useProductsContext();
-
- return (
- *:not(style)': { mb: 3 } }}>
-
-
-
-
-
-
-
-
- );
-}
+import { ProductsProvider } from "./ProductsContext";
+import NavigationManager from "../../components/NavigationManager";
+import ProductTypeSelector from "./ProductTypeSelector";
export default function ProductsPage() {
return (
-
+ } initialTitle="Productos" />
)
}
diff --git a/src/util/generalUtils.js b/src/util/generalUtils.js
index aa9ee20..f1d0631 100644
--- a/src/util/generalUtils.js
+++ b/src/util/generalUtils.js
@@ -1,3 +1,5 @@
+import { ProductTypes } from "../pages/Products/ProductsConstants";
+
export function modifyAndClone(obj, path, value) {
// Realiza una clonación profunda del objeto original
const clone = JSON.parse(JSON.stringify(obj));
@@ -62,3 +64,13 @@ export const base64ToBlob = (base64) => {
const blob = new Blob(byteArrays, { type: mimeType });
return blob;
}
+
+export const getProductVerbiage = (productType) => {
+ let productVerbiage = 'Radiadores';
+ if (productType === ProductTypes.CAP) {
+ productVerbiage = 'Tapas';
+ } else if (productType === ProductTypes.FAN) {
+ productVerbiage = 'Abanicos';
+ }
+ return productVerbiage;
+}
\ No newline at end of file