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 (