浏览代码

Merge remote-tracking branch 'origin/paging-sorting-filtering' into djordje-dev

pull/7/head
Djordje Mitrovic 3 年前
父节点
当前提交
fcdbe753a9

+ 15
- 0
db/db.js 查看文件

@@ -0,0 +1,15 @@
const faker = require('faker');

module.exports = () => {
const items = [];
for (let id = 1; id <= 500; id++) {
items.push({
id: id,
name: `${faker.commerce.productAdjective()} ${faker.commerce.productMaterial()} ${faker.commerce.product()}`,
color: faker.commerce.color(),
price: `$${faker.commerce.price()}`,
company: faker.company.companyName(),
});
}
return { items };
};

+ 5
- 1
package.json 查看文件

@@ -7,6 +7,7 @@
"@emotion/styled": "^11.3.0",
"@mui/icons-material": "^5.0.5",
"@mui/material": "^5.0.6",
"@mui/x-data-grid": "^5.0.1",
"@reduxjs/toolkit": "^1.5.1",
"@testing-library/jest-dom": "^5.13.0",
"@testing-library/react": "^11.2.7",
@@ -15,8 +16,10 @@
"date-fns": "^2.22.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-security": "^1.4.0",
"faker": "^5.5.3",
"formik": "^2.2.9",
"i18next": "^20.3.1",
"json-server": "^0.17.0",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"lodash.isempty": "^4.4.0",
@@ -39,7 +42,8 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"json-serve": "json-server ./db/db.js --port=4000"
},
"eslintConfig": {
"extends": [

src/components/MUI/CustomBackdrop.js → src/components/MUI/BackdropComponent.js 查看文件

@@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
import { Backdrop, CircularProgress } from '@mui/material';
import { alpha } from '@mui/system';

const CustomBackdrop = ({ position = 'fixed', isLoading }) => (
const BackdropComponent = ({ position = 'fixed', isLoading }) => (
<Backdrop
sx={{
// 'fixed' takes whole page, 'fixed' takes whole space of the parent element which needs to have 'relative' position
// 'fixed' takes whole page, 'absolute' takes whole space of the parent element which needs to have 'relative' position
position,
backgroundColor: ({ palette }) =>
alpha(palette.background.default, palette.action.disabledOpacity),
@@ -18,9 +18,9 @@ const CustomBackdrop = ({ position = 'fixed', isLoading }) => (
</Backdrop>
);

CustomBackdrop.propTypes = {
BackdropComponent.propTypes = {
position: PropTypes.oneOf(['fixed', 'absolute']),
isLoading: PropTypes.bool.isRequired,
};

export default CustomBackdrop;
export default BackdropComponent;

src/components/MUI/CustomErrorMessage.js → src/components/MUI/ErrorMessageComponent.js 查看文件

@@ -2,14 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Typography } from '@mui/material';

const CustomErrorMessage = ({ error }) => (
const ErrorMessageComponent = ({ error }) => (
<Typography variant="body1" color="error" my={2}>
{error}
</Typography>
);

CustomErrorMessage.propTypes = {
ErrorMessageComponent.propTypes = {
error: PropTypes.string.isRequired,
};

export default CustomErrorMessage;
export default ErrorMessageComponent;

+ 29
- 0
src/components/MUI/Examples/DataGridExample.js 查看文件

@@ -0,0 +1,29 @@
import React from 'react';
import { Paper, Typography } from '@mui/material';
import { DataGrid } from '@mui/x-data-grid';

// Use these values from REDUX?
const rows = [
{ id: 1, col1: 'Example', col2: 'Row', col3: '1' },
{ id: 2, col1: 'Row', col2: 'Example', col3: '2' },
{ id: 3, col1: '3', col2: 'Row', col3: 'Example' },
];

const columns = [
{ field: 'col1', headerName: 'Column 1', flex: 1 },
{ field: 'col2', headerName: 'Column 2', flex: 1 },
{ field: 'col3', headerName: 'Column 2', flex: 1 },
];

const DataGridExample = () => {
return (
<Paper sx={{ p: 2 }} elevation={5}>
<Typography variant="h4" gutterBottom align="center">
DataGrid Example
</Typography>
<DataGrid autoHeight rows={rows} columns={columns} />
</Paper>
);
};

export default DataGridExample;

+ 64
- 0
src/components/MUI/Examples/ModalsExample.js 查看文件

@@ -0,0 +1,64 @@
import React, { useState } from 'react';
import { Button, Divider, Paper, Typography } from '@mui/material';
import DialogComponent from '../DialogComponent';
import DrawerComponent from '../DrawerComponent';
import PopoverComponent from '../PopoverComponent';

const Modals = () => {
const [dialogOpen, setDialogOpen] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
const [popoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);

return (
<Paper
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
}}
elevation={5}
>
<Typography variant="h4" gutterBottom align="center">
Modals Example
</Typography>
<Divider />
<Button onClick={() => setDialogOpen(true)}>Open Dialog</Button>
<Button onClick={() => setDrawerOpen(true)}>Open Drawer</Button>
<Button
onClick={(e) => {
setPopoverOpen(true);
setAnchorEl(e.currentTarget);
}}
>
Open Popover
</Button>
<DialogComponent
title="Dialog Title"
content={<Typography>Dialog Content</Typography>}
open={dialogOpen}
onClose={() => setDialogOpen(false)}
maxWidth="md"
fullWidth
responsive
/>
<DrawerComponent
anchor="left"
content={<Typography sx={{ p: 2 }}>Drawer Content</Typography>}
open={drawerOpen}
toggleOpen={() => setDrawerOpen(!drawerOpen)}
/>
<PopoverComponent
anchorEl={anchorEl}
open={popoverOpen}
onClose={() => {
setPopoverOpen(false);
setAnchorEl(null);
}}
content={<Typography sx={{ p: 2 }}>Popover Content</Typography>}
/>
</Paper>
);
};

export default Modals;

+ 183
- 0
src/components/MUI/Examples/PagingSortingFilteringExample.js 查看文件

@@ -0,0 +1,183 @@
import React, { useEffect, useState } from 'react';
import {
Paper,
Box,
Grid,
Typography,
Divider,
TablePagination,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
} from '@mui/material';
// import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector, batch } from 'react-redux';
import useDebounce from '../../../hooks/useDebounceHook';
import {
itemsSelector,
pageSelector,
itemsPerPageSelector,
countSelector,
sortSelector,
} from '../../../store/selectors/randomDataSelectors';
import {
loadData,
updatePage,
updateItemsPerPage,
updateFilter,
updateSort,
} from '../../../store/actions/randomData/randomDataActions';

const PagingSortingFilteringExample = () => {
const [filterText, setFilterText] = useState('');

const dispatch = useDispatch();
// const { t } = useTranslation();
const items = useSelector(itemsSelector);
const currentPage = useSelector(pageSelector);
const itemsPerPage = useSelector(itemsPerPageSelector);
const totalCount = useSelector(countSelector);
const sort = useSelector(sortSelector) || 'name-asc';

// Use debounce to prevent too many rerenders
const debouncedFilterText = useDebounce(filterText, 500);

useEffect(() => {
dispatch(loadData(30));
dispatch(updateSort(sort));
}, []);

useEffect(() => {
batch(() => {
dispatch(updateFilter(filterText));
currentPage > 0 && dispatch(updatePage(0));
});
}, [debouncedFilterText]);

const handleFilterTextChange = (event) => {
const filterText = event.target.value;
setFilterText(filterText);
};

const handleSortChange = (event) => {
const sort = event.target.value;
dispatch(updateSort(sort));
};

const handlePageChange = (event, newPage) => {
dispatch(updatePage(newPage));
};

const handleItemsPerPageChange = (event) => {
const itemsPerPage = parseInt(event.target.value);
batch(() => {
dispatch(updateItemsPerPage(itemsPerPage));
dispatch(updatePage(0));
});
};


return (
<Paper
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'start',
py: 2,
minHeight: 500,
}}
elevation={5}
>
<Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center">
Pagination, Filtering and Sorting Example Client Side
</Typography>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
flexWrap: 'wrap',
mx: 2,
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}}
>
{/* TODO Separate into SelectComponent */}
<FormControl sx={{ flexGrow: 1 }}>
<InputLabel id="sort-label">Sort</InputLabel>
<Select
label="Sort"
labelId="sort-label"
id="sort-select-helper"
value={sort}
onChange={handleSortChange}
>
<MenuItem value="name-asc">Name - A-Z</MenuItem>
<MenuItem value="name-desc">Name - Z-A</MenuItem>
<MenuItem value="price-asc">Price - Lowest to Highest</MenuItem>
<MenuItem value="price-desc">Price - Highest to Lowest</MenuItem>
</Select>
</FormControl>
<TextField
sx={{ flexGrow: 1 }}
variant="outlined"
label="Filter"
placeholder="Filter"
value={filterText}
onChange={handleFilterTextChange}
/>
</Box>
</Box>
<Grid container>
{items &&
items.length > 0 &&
items
.slice(
currentPage * itemsPerPage,
currentPage * itemsPerPage + itemsPerPage
)
.map((product, index) => (
// ! DON'T USE index for key, this is for example only
<Grid item sx={{ p: 2 }} xs={12} sm={6} md={4} lg={3} key={index}>
{/* TODO separate into component */}
<Paper sx={{ p: 3, height: '100%' }} elevation={3}>
<Typography sx={{ fontWeight: 600 }}>Name: </Typography>
<Typography display="inline"> {product.name}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Designer: </Typography>
<Typography display="inline"> {product.designer}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Type: </Typography>
<Typography display="inline"> {product.type}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Price: </Typography>
<Typography display="inline"> ${product.price}</Typography>
</Paper>
</Grid>
))}
</Grid>
<Box sx={{ width: '100%' }}>
<TablePagination
component="div"
count={totalCount}
page={currentPage}
onPageChange={handlePageChange}
rowsPerPage={itemsPerPage}
onRowsPerPageChange={handleItemsPerPageChange}
rowsPerPageOptions={[12, 24, 48, 96]}
labelRowsPerPage="Items per page"
showFirstButton
showLastButton
/>
</Box>
</Paper>
);
};

export default PagingSortingFilteringExample;

+ 159
- 0
src/components/MUI/Examples/PagingSortingFilteringExampleServerSide.js 查看文件

@@ -0,0 +1,159 @@
import React, { useEffect, useState } from 'react';
import {
Paper,
Box,
Grid,
Typography,
Divider,
TablePagination,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
} from '@mui/material';
// import { useTranslation } from 'react-i18next';
import Backdrop from '../BackdropComponent';
import useDebounce from '../../../hooks/useDebounceHook';
import { useRandomData } from '../../../context/RandomDataContext';

const PagingSortingFilteringExampleServerSide = () => {
const [filterText, setFilterText] = useState('');
const { state, data } = useRandomData();
const { items, loading, totalCount, currentPage, itemsPerPage, sort } = data;
const { setPage, setItemsPerPage, setSort, setFilter } = state;
// const { t } = useTranslation();

// Use debounce to prevent too many rerenders
const debouncedFilterText = useDebounce(filterText, 500);

useEffect(() => {
setFilter(filterText);
}, [debouncedFilterText]);

const handleFilterTextChange = (event) => {
const filterText = event.target.value;
setFilterText(filterText);
};

const handleSortChange = (event) => {
const sort = event.target.value;
setSort(sort);
};

const handlePageChange = (event, newPage) => {
setPage(newPage);
};

const handleItemsPerPageChange = (event) => {
const itemsPerPage = parseInt(event.target.value);
setItemsPerPage(itemsPerPage);
setPage(0);
};

return (
<Paper
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'start',
py: 2,
minHeight: 500,
position: 'relative',
}}
elevation={5}
>
{loading && <Backdrop isLoading position="absolute" />}
<Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center">
Pagination, Filtering and Sorting Example Server Side
</Typography>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
flexWrap: 'wrap',
mx: 2,
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}}
>
<FormControl sx={{ flexGrow: 1 }}>
<InputLabel id="sort-label">Sort</InputLabel>
<Select
label="Sort"
labelId="sort-label"
id="sort-select-helper"
value={sort || ''}
onChange={handleSortChange}
>
<MenuItem value="">None</MenuItem>
<MenuItem value="name-asc">Name - A-Z</MenuItem>
<MenuItem value="name-desc">Name - Z-A</MenuItem>
<MenuItem value="price-asc">Price - Lowest to Highest</MenuItem>
<MenuItem value="price-desc">Price - Highest to Lowest</MenuItem>
</Select>
</FormControl>
<TextField
sx={{ flexGrow: 1 }}
variant="outlined"
label="Filter"
placeholder="Filter"
value={filterText}
onChange={handleFilterTextChange}
/>
</Box>
<Grid container sx={{ position: 'relative' }}>
{items &&
items.length > 0 &&
items.map((item) => (
<Grid
item
sx={{ p: 2 }}
xs={12}
sm={6}
md={4}
lg={3}
key={item.id}
>
{/* TODO separate into component */}
<Paper sx={{ p: 3, height: '100%' }} elevation={3}>
<Typography sx={{ fontWeight: 600 }}>Name: </Typography>
<Typography display="inline"> {item.name}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Company: </Typography>
<Typography display="inline"> {item.company}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Color: </Typography>
<Typography display="inline"> {item.color}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Price: </Typography>
<Typography display="inline"> {item.price}</Typography>
</Paper>
</Grid>
))}
</Grid>
<Box sx={{ width: '100%' }}>
<TablePagination
component="div"
count={totalCount}
page={currentPage}
onPageChange={handlePageChange}
rowsPerPage={itemsPerPage}
onRowsPerPageChange={handleItemsPerPageChange}
rowsPerPageOptions={[12, 24, 48, 96]}
labelRowsPerPage="Items per page"
showFirstButton
showLastButton
/>
</Box>
</Box>
</Paper>
);
};

export default PagingSortingFilteringExampleServerSide;

src/components/MUI/MenuList.js → src/components/MUI/MenuListComponent.js 查看文件

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { Button, Menu, MenuItem } from '@mui/material';

const MenuList = () => {
const MenuListComponent = () => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
@@ -23,4 +23,4 @@ const MenuList = () => {
);
};

export default MenuList;
export default MenuListComponent;

src/components/MUI/Navbar.js → src/components/MUI/NavbarComponent.js 查看文件

@@ -1,4 +1,4 @@
import React, { useState, useMemo } from 'react';
import React, { useState, useMemo, useContext } from 'react';
import {
AppBar,
Badge,
@@ -16,35 +16,52 @@ import {
import { useTheme } from '@mui/system';
import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined';
import ShoppingBasketIcon from '@mui/icons-material/ShoppingBasket';
import MenuList from './MenuList';
import Brightness4Icon from '@mui/icons-material/Brightness4';
import Brightness7Icon from '@mui/icons-material/Brightness7';
import MenuList from './MenuListComponent';
import Drawer from './DrawerComponent';
import { ColorModeContext } from '../../context/ColorModeContext';

const Navbar = () => {
const NavbarComponent = () => {
const [openDrawer, setOpenDrawer] = useState(false);
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down('sm'));
const toggleColorMode = useContext(ColorModeContext);

const handleToggleDrawer = () => {
setOpenDrawer(!openDrawer);
};

const drawerContent = useMemo(() => (
<List>
<ListItemButton divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 1</ListItemText>
</ListItemIcon>
</ListItemButton>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 2</ListItemText>
</ListItemIcon>
</ListItem>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemText>Link 3</ListItemText>
</ListItem>
</List>
), [handleToggleDrawer]);
const drawerContent = useMemo(
() => (
<List>
<ListItemButton divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 1</ListItemText>
</ListItemIcon>
</ListItemButton>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 2</ListItemText>
</ListItemIcon>
</ListItem>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemText>Link 3</ListItemText>
</ListItem>
<ListItem divider>
<IconButton onClick={toggleColorMode}>
<ListItemText>Toggle {theme.palette.mode} mode</ListItemText>
{theme.palette.mode === 'dark' ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
</ListItem>
</List>
),
[handleToggleDrawer]
);

return (
<AppBar
@@ -118,11 +135,20 @@ const Navbar = () => {
</IconButton>
</Box>
) : (
<IconButton>
<Badge badgeContent={3} color="primary">
<ShoppingBasketIcon color="action" />
</Badge>
</IconButton>
<Box>
<IconButton>
<Badge badgeContent={3} color="primary">
<ShoppingBasketIcon color="action" />
</Badge>
</IconButton>
<IconButton sx={{ ml: 1 }} onClick={toggleColorMode}>
{theme.palette.mode === 'dark' ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
</Box>
)}
</Box>
</Box>
@@ -131,4 +157,4 @@ const Navbar = () => {
);
};

export default Navbar;
export default NavbarComponent;

+ 21
- 0
src/context/ColorModeContext.js 查看文件

@@ -0,0 +1,21 @@
import React, { createContext } from 'react';
import PropTypes from 'prop-types';
import { ThemeProvider } from '@mui/material/styles';
import useToggleColorMode from '../hooks/useToggleColorMode';

export const ColorModeContext = createContext();

const ColorModeProvider = ({ children }) => {
const [toggleColorMode, theme] = useToggleColorMode();
return (
<ColorModeContext.Provider value={toggleColorMode}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</ColorModeContext.Provider>
);
};

ColorModeProvider.propTypes = {
children: PropTypes.node,
};

export default ColorModeProvider;

+ 63
- 0
src/context/RandomDataContext.js 查看文件

@@ -0,0 +1,63 @@
import React, { createContext, useContext, useState } from 'react';
import PropTypes from 'prop-types';
import usePagingHook from '../hooks/usePagingHook';
import { getRequest } from '../request/jsonServerRequest';

const apiCall = (page, itemsPerPage, sort, sortDirection, filter) =>
getRequest('/items', {
_page: page,
_limit: itemsPerPage,
// Conditionally add to params object if keys exist
...(sort && { _sort: sort }),
...(sortDirection && { _order: sortDirection }),
...(filter && { q: filter }),
});

const Context = createContext();
export const useRandomData = () => useContext(Context);

const RandomDataProvider = ({ children }) => {
const setPage = (page) => {
setState({ ...state, page });
};

const setItemsPerPage = (itemsPerPage) => {
setState({ ...state, itemsPerPage });
};

const setSort = (sort) => {
setState({ ...state, sort });
};

const setFilter = (filter) => {
setState({ ...state, filter });
};

const [state, setState] = useState({
page: 0,
setPage,
itemsPerPage: 12,
setItemsPerPage,
sort: '',
setSort,
filter: '',
setFilter,
});

const data = usePagingHook(
state.page,
state.itemsPerPage,
state.sort,
state.filter,
apiCall
);
return (
<Context.Provider value={{ state, data }}>{children}</Context.Provider>
);
};

RandomDataProvider.propTypes = {
children: PropTypes.node,
};

export default RandomDataProvider;

+ 17
- 0
src/hooks/useDebounceHook.js 查看文件

@@ -0,0 +1,17 @@
import { useEffect, useState } from 'react';

const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay || 500);

return () => {
clearTimeout(timer);
};
}, [value, delay]);

return debouncedValue;
};

export default useDebounce;

+ 66
- 0
src/hooks/usePagingHook.js 查看文件

@@ -0,0 +1,66 @@
import { useState, useCallback, useEffect } from 'react';
import { unstable_batchedUpdates } from 'react-dom';

const usePagingHook = (page, itemsPerPage, sort, filter, apiCallback) => {
const [items, setItems] = useState([]);
const [totalPages, setTotalPages] = useState(0);
const [currentPage, setCurrentPage] = useState(0);
const [loading, setLoading] = useState(false);
const [totalCount, setTotalCount] = useState(0);

const reload = useCallback(async () => {
setLoading(true);
try {
const [sortColumn, sortDirection] = sort.split('-');
const response = await apiCallback(
page,
itemsPerPage,
sortColumn,
sortDirection,
filter
);
if (response.status === 200) {
// Prevents multiple rerenders
unstable_batchedUpdates(() => {
setItems(response.data);
setTotalCount(parseInt(response.headers['x-total-count']));
setTotalPages(
Math.ceil(response.headers['x-total-count'] / itemsPerPage)
);
setCurrentPage(page);
});
}
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
}, [
setItems,
setLoading,
setTotalPages,
setCurrentPage,
apiCallback,
page,
itemsPerPage,
sort,
filter,
]);

useEffect(() => {
reload();
}, [reload]);

return {
items,
loading,
reload,
totalCount,
totalPages,
currentPage,
itemsPerPage,
sort,
};
};

export default usePagingHook;

+ 31
- 0
src/hooks/useToggleColorMode.js 查看文件

@@ -0,0 +1,31 @@
import { useState, useMemo } from 'react';
import { createTheme } from '@mui/material/styles';
import {
authScopeSetHelper,
authScopeStringGetHelper,
} from '../util/helpers/authScopeHelpers';

const useToggleColorMode = () => {
const currentColorMode = authScopeStringGetHelper('colorMode') || 'light';
const [mode, setMode] = useState(currentColorMode);

const toggleColorMode = () => {
const nextMode = mode === 'light' ? 'dark' : 'light';
setMode(nextMode);
authScopeSetHelper('colorMode', nextMode);
};

const theme = useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode]
);

return [toggleColorMode, theme];
};

export default useToggleColorMode;

+ 2
- 0
src/i18n/resources/en.js 查看文件

@@ -16,6 +16,8 @@ export default {
labelUsername: 'Username',
labelPassword: 'Password',
next: 'Next',
nextPage: 'Next page',
previousPage: 'Previous page',
back: 'Back',
goBack: 'Go Back',
ok: 'Ok',

+ 5
- 2
src/index.js 查看文件

@@ -8,14 +8,17 @@ import App from './App';
import store from './store';

import './i18n';
import ColorModeProvider from './context/ColorModeContext';

ReactDOM.render(
<HelmetProvider>
<React.StrictMode>
<Provider store={store}>
<App />
<ColorModeProvider>
<App />
</ColorModeProvider>
</Provider>
</React.StrictMode>
</HelmetProvider>,
document.getElementById('root'),
);
);

+ 1
- 1
src/pages/ForgotPasswordPage/ForgotPasswordPageMUI.js 查看文件

@@ -12,7 +12,7 @@ import {
Link,
Grid,
} from '@mui/material';
import Backdrop from '../../components/MUI/CustomBackdrop';
import Backdrop from '../../components/MUI/BackdropComponent';
import { LOGIN_PAGE } from '../../constants/pages';
import { NavLink } from 'react-router-dom';


+ 26
- 63
src/pages/HomePage/HomePageMUI.js 查看文件

@@ -1,71 +1,34 @@
import React, { useState } from 'react';
import { Box, Button, Divider, Paper, Typography } from '@mui/material';
import DialogComponent from '../../components/MUI/DialogComponent';
import DrawerComponent from '../../components/MUI/DrawerComponent';
import Navbar from '../../components/MUI/Navbar';
import PopoverComponent from '../../components/MUI/PopoverComponent';
import React from 'react';
import { Box, Grid } from '@mui/material';
import Navbar from '../../components/MUI/NavbarComponent';
import Modals from '../../components/MUI/Examples/ModalsExample';
import DataGrid from '../../components/MUI/Examples/DataGridExample';
import PagingSortingFiltering from '../../components/MUI/Examples/PagingSortingFilteringExample';
import PagingSortingFilteringServerSide from '../../components/MUI/Examples/PagingSortingFilteringExampleServerSide';
import RandomDataProvider from '../../context/RandomDataContext';

const HomePage = () => {
const [dialogOpen, setDialogOpen] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
const [popoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);

return (
<>
<Navbar />
<Box
sx={{
mt: 4,
ml: 4,
display: 'flex',
flexGrow: 1,
}}
>
<Paper
sx={{
p: 4,
display: 'flex',
flexDirection: 'column',
}}
>
<Typography variant="h4" gutterBottom align="center">Modals</Typography>
<Divider />
<Button onClick={() => setDialogOpen(true)}>Open Dialog</Button>
<Button onClick={() => setDrawerOpen(true)}>Open Drawer</Button>
<Button
onClick={(e) => {
setPopoverOpen(true);
setAnchorEl(e.currentTarget);
}}
>
Open Popover
</Button>
</Paper>
<DialogComponent
title="Dialog Title"
content={<Typography>Dialog Content</Typography>}
open={dialogOpen}
onClose={() => setDialogOpen(false)}
maxWidth="md"
fullWidth
responsive
/>
<DrawerComponent
anchor="left"
content={<Typography sx={{ p: 2 }}>Drawer Content</Typography>}
open={drawerOpen}
toggleOpen={() => setDrawerOpen(!drawerOpen)}
/>
<PopoverComponent
anchorEl={anchorEl}
open={popoverOpen}
onClose={() => {
setPopoverOpen(false);
setAnchorEl(null);
}}
content={<Typography sx={{ p: 2 }}>Popover Content</Typography>}
/>
<Box sx={{ mt: 4, mx: 4 }}>
<Grid container spacing={2} justifyContent="center">
<Grid item xs={12} md={3}>
<Modals />
</Grid>
<Grid item xs={12} md={6}>
<DataGrid />
</Grid>
<Grid item xs={12} md={9}>
<PagingSortingFiltering />
</Grid>
<Grid item xs={12} md={9}>
{/* Move to higher components? */}
<RandomDataProvider>
<PagingSortingFilteringServerSide />
</RandomDataProvider>
</Grid>
</Grid>
</Box>
</>
);

+ 2
- 2
src/pages/LoginPage/LoginPageMUI.js 查看文件

@@ -25,8 +25,8 @@ import {
Typography,
} from '@mui/material';
import { Visibility, VisibilityOff } from '@mui/icons-material';
import Backdrop from '../../components/MUI/CustomBackdrop';
import ErrorMessage from '../../components/MUI/CustomErrorMessage';
import Backdrop from '../../components/MUI/BackdropComponent';
import ErrorMessage from '../../components/MUI/ErrorMessageComponent';
import { selectIsLoadingByActionType } from '../../store/selectors/loadingSelectors';
import { LOGIN_USER_LOADING } from '../../store/actions/login/loginActionConstants';


+ 13
- 0
src/request/jsonServerRequest.js 查看文件

@@ -0,0 +1,13 @@
import axios from 'axios';

const JSON_SERVER_ENDPOINT = 'http://localhost:4000';

const request = axios.create({
baseURL: JSON_SERVER_ENDPOINT,
headers: {
'Content-Type': 'application/json',
},
});

export const getRequest = (url, params = null, options = null) =>
request.get(url, { params, ...options });

+ 5
- 0
src/store/actions/randomData/randomDataActionConstants.js 查看文件

@@ -0,0 +1,5 @@
export const LOAD_DATA = 'LOAD_DATA';
export const UPDATE_PAGE = 'UPDATE_PAGE';
export const UPDATE_ITEMS_PER_PAGE = 'UPDATE_ITEMS_PER_PAGE';
export const UPDATE_FILTER = 'UPDATE_FILTER';
export const UPDATE_SORT = 'UPDATE_SORT';

+ 32
- 0
src/store/actions/randomData/randomDataActions.js 查看文件

@@ -0,0 +1,32 @@
import {
LOAD_DATA,
UPDATE_PAGE,
UPDATE_ITEMS_PER_PAGE,
UPDATE_FILTER,
UPDATE_SORT
} from './randomDataActionConstants';

export const loadData = (payload) => ({
type: LOAD_DATA,
payload,
});

export const updatePage = (payload) => ({
type: UPDATE_PAGE,
payload,
});

export const updateItemsPerPage = (payload) => ({
type: UPDATE_ITEMS_PER_PAGE,
payload,
});

export const updateFilter = (payload) => ({
type: UPDATE_FILTER,
payload,
})

export const updateSort = (payload) => ({
type: UPDATE_SORT,
payload,
})

+ 3
- 1
src/store/reducers/index.js 查看文件

@@ -2,9 +2,11 @@ import { combineReducers } from 'redux';
import loginReducer from './login/loginReducer';
import loadingReducer from './loading/loadingReducer';
import userReducer from './user/userReducer';
import randomDataReducer from './randomData/randomDataReducer';

export default combineReducers({
login: loginReducer,
user: userReducer,
loading:loadingReducer
loading:loadingReducer,
randomData: randomDataReducer
});

+ 103
- 0
src/store/reducers/randomData/randomDataReducer.js 查看文件

@@ -0,0 +1,103 @@
import createReducer from '../../utils/createReducer';
import {
LOAD_DATA,
UPDATE_PAGE,
UPDATE_ITEMS_PER_PAGE,
UPDATE_FILTER,
UPDATE_SORT,
} from '../../actions/randomData/randomDataActionConstants.js';
import generate from '../../../util/helpers/randomData';

const initialState = {
items: [],
filteredItems: [],
count: 0,
page: 0,
itemsPerPage: 12,
filter: '',
sort: '',
};

export default createReducer(
{
[LOAD_DATA]: loadRandomData,
[UPDATE_PAGE]: updatePage,
[UPDATE_ITEMS_PER_PAGE]: updateItemsPerPage,
[UPDATE_FILTER]: updateFilter,
[UPDATE_SORT]: updateSort,
},
initialState
);

function loadRandomData(state, action) {
const count = action.payload;
const items = generate(count);

return {
...state,
items,
filteredItems: items,
count: items.length,
};
}

function updatePage(state, action) {
const page = action.payload;

return {
...state,
page,
};
}

function updateItemsPerPage(state, action) {
const itemsPerPage = action.payload;

return {
...state,
itemsPerPage,
};
}

function updateFilter(state, action) {
const filter = action.payload;
const filteredItems = filter
? state.items.filter((item) => item.name.toLowerCase().includes(filter.toLowerCase())) : state.items;

return {
...state,
filter,
filteredItems,
count: filteredItems.length,
};
}

function updateSort(state, action) {
const sort = action.payload;
const [field, direction] = sort.split('-');

const sortDirection = direction === 'asc' ? 1 : -1;
const dataItems = state.filteredItems.length
? state.filteredItems
: state.items;

const sorted = dataItems.sort((a, b) => {
if (a[field] > b[field]) {
return sortDirection;
}
if (b[field] > a[field]) {
return sortDirection * -1;
}
return 0;
});

const filteredItems = state.filteredItems.length
? sorted
: state.filteredItems;

return {
...state,
sort,
filteredItems,
};
}

+ 32
- 0
src/store/selectors/randomDataSelectors.js 查看文件

@@ -0,0 +1,32 @@
import { createSelector } from 'reselect';

const randomDataSelector = (state) => state.randomData;

export const itemsSelector = createSelector(randomDataSelector, (state) =>
(state.filter) ? state.filteredItems : state.items
);

export const pageSelector = createSelector(
randomDataSelector,
(state) => state.page
);

export const itemsPerPageSelector = createSelector(
randomDataSelector,
(state) => state.itemsPerPage
);

export const countSelector = createSelector(
randomDataSelector,
(state) => state.count
);

export const filterSelector = createSelector(
randomDataSelector,
(state) => state.filter
);

export const sortSelector = createSelector(
randomDataSelector,
(state) => state.sort
);

+ 65
- 0
src/util/helpers/randomData.js 查看文件

@@ -0,0 +1,65 @@
const random = (arr) => {
return arr[Math.floor(Math.random() * arr.length)];
};

const size = () => {
return random(['Extra Small', 'Small', 'Medium', 'Large', 'Extra Large']);
};

const color = () => {
return random(['Red', 'Green', 'Blue', 'Orange', 'Yellow']);
};

const designer = () => {
return random([
'Ralph Lauren',
'Alexander Wang',
'Grayse',
'Marc NY Performance',
'Scrapbook',
'J Brand Ready to Wear',
'Vintage Havana',
'Neiman Marcus Cashmere Collection',
'Derek Lam 10 Crosby',
'Jordan',
]);
};

const type = () => {
return random([
'Cashmere',
'Cardigans',
'Crew and Scoop',
'V-Neck',
'Shoes',
'Cowl & Turtleneck',
]);
};

const price = () => {
return (Math.random() * 100).toFixed(2);
};

function generate(count) {
const data = [];
for (let i = 0; i < count; i++) {
const currentColor = color();
const currentSize = size();
const currentType = type();
const currentDesigner = designer();
const currentPrice = price();

data.push({
name: `${currentDesigner} ${currentType} ${currentColor} ${currentSize}`,
color: currentColor,
size: currentSize,
designer: currentDesigner,
type: currentType,
price: currentPrice,
salesPrice: currentPrice
});
}
return data;
}

export default generate;

+ 617
- 11
yarn.lock
文件差异内容过多而无法显示
查看文件


正在加载...
取消
保存