Просмотр исходного кода

Add DataGrid and Pagination components refactor code, add examples

paging-sorting-filtering
mladen.dubovac 4 лет назад
Родитель
Сommit
db33d77f1a

+ 1
- 0
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",

src/components/MUI/CustomBackdrop.js → src/components/MUI/BackdropComponent.js Просмотреть файл

@@ -3,7 +3,7 @@ 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
@@ -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;

src/components/MUI/Examples/Modals.js → src/components/MUI/Examples/ModalsExample.js Просмотреть файл

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Box, Button, Divider, Paper, Typography } from '@mui/material';
import { Button, Divider, Paper, Typography } from '@mui/material';
import DialogComponent from '../DialogComponent';
import DrawerComponent from '../DrawerComponent';
import PopoverComponent from '../PopoverComponent';
@@ -11,36 +11,28 @@ const Modals = () => {
const [anchorEl, setAnchorEl] = useState(null);

return (
<Box
<Paper
sx={{
mt: 4,
ml: 4,
p: 2,
display: 'flex',
flexGrow: 1,
flexDirection: 'column',
}}
elevation={5}
>
<Paper
sx={{
p: 4,
display: 'flex',
flexDirection: 'column',
<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);
}}
>
<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>
Open Popover
</Button>
<DialogComponent
title="Dialog Title"
content={<Typography>Dialog Content</Typography>}
@@ -65,7 +57,7 @@ const Modals = () => {
}}
content={<Typography sx={{ p: 2 }}>Popover Content</Typography>}
/>
</Box>
</Paper>
);
};


+ 109
- 0
src/components/MUI/Examples/PagingSortingFilteringExample.js Просмотреть файл

@@ -0,0 +1,109 @@
/*eslint-disable*/
import React, { useEffect } from 'react';
import {
Paper,
Box,
Grid,
Typography,
Divider,
TablePagination,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector, batch } from 'react-redux';
import {
itemsSelector,
pageSelector,
itemsPerPageSelector,
countSelector,
} from '../../../store/selectors/randomDataSelectors';
import {
loadData,
updatePage,
updateItemsPerPage,
} from '../../../store/actions/randomData/randomDataActions';

const PagingSortingFilteringExample = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const items = useSelector(itemsSelector);
const currentPage = useSelector(pageSelector);
const itemsPerPage = useSelector(itemsPerPageSelector);
const totalCount = useSelector(countSelector);

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

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: 'center',
}}
elevation={5}
>
<Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center">
Pagination, Filtering and Sorting Example
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Box>
<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>
<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>
</Paper>
);
};

export default PagingSortingFilteringExample;

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 Просмотреть файл

@@ -18,11 +18,11 @@ import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined';
import ShoppingBasketIcon from '@mui/icons-material/ShoppingBasket';
import Brightness4Icon from '@mui/icons-material/Brightness4';
import Brightness7Icon from '@mui/icons-material/Brightness7';
import MenuList from './MenuList';
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'));
@@ -157,4 +157,4 @@ const Navbar = () => {
);
};

export default Navbar;
export default NavbarComponent;

+ 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',

+ 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';


+ 18
- 5
src/pages/HomePage/HomePageMUI.js Просмотреть файл

@@ -1,14 +1,27 @@
import React from 'react';
import Navbar from '../../components/MUI/Navbar';
import Modals from '../../components/MUI/Examples/Modals';
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';

const HomePage = () => {


return (
<>
<Navbar />
<Modals />
<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>
</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';


+ 3
- 0
src/store/actions/randomData/randomDataActionConstants.js Просмотреть файл

@@ -0,0 +1,3 @@
export const LOAD_DATA = 'LOAD_DATA';
export const UPDATE_PAGE = 'UPDATE_PAGE';
export const UPDATE_ITEMS_PER_PAGE = 'UPDATE_ITEMS_PER_PAGE';

+ 20
- 0
src/store/actions/randomData/randomDataActions.js Просмотреть файл

@@ -0,0 +1,20 @@
import {
LOAD_DATA,
UPDATE_PAGE,
UPDATE_ITEMS_PER_PAGE,
} 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,
});

+ 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
});

+ 52
- 0
src/store/reducers/randomData/randomDataReducer.js Просмотреть файл

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

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

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

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

return {
...state,
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,
};
}

+ 23
- 0
src/store/selectors/randomDataSelectors.js Просмотреть файл

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

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

export const itemsSelector = createSelector(
randomDataSelector,
(state) => 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
);

+ 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;

+ 33
- 0
yarn.lock Просмотреть файл

@@ -1172,6 +1172,13 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.16.3":
version "7.16.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
@@ -1691,6 +1698,27 @@
prop-types "^15.7.2"
react-is "^17.0.2"

"@mui/utils@^5.1.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.1.1.tgz#3cb2c049731dacb3830336bc8011141e84434aad"
integrity sha512-rqakHf0IMaasDo1EcYqkx13VTxeoQoGf/3RxQuazQFKzF7d2uylFwNyb6bnUJGNe2/akiIMk/qiub58sYrwxVQ==
dependencies:
"@babel/runtime" "^7.16.3"
"@types/prop-types" "^15.7.4"
"@types/react-is" "^16.7.1 || ^17.0.0"
prop-types "^15.7.2"
react-is "^17.0.2"

"@mui/x-data-grid@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-5.0.1.tgz#bace714fca342b6401bebb8861c4a0b09a4f1604"
integrity sha512-F1IhkxNiI7A/VoRT0vTs7PP4E8E/K6e5WLrjXFzxeWmvOBaAfmwvf3Wq8o/8ouQ9MFy92M/lAgviOMp1gduOtw==
dependencies:
"@mui/utils" "^5.1.0"
clsx "^1.1.1"
prop-types "^15.7.2"
reselect "^4.1.1"

"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -10080,6 +10108,11 @@ reselect@^4.0.0:
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==

reselect@^4.1.1:
version "4.1.4"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.4.tgz#66df0aff41b6ee0f51e2cc17cfaf2c1995916f32"
integrity sha512-i1LgXw8DKSU5qz1EV0ZIKz4yIUHJ7L3bODh+Da6HmVSm9vdL/hG7IpbgzQ3k2XSirzf8/eI7OMEs81gb1VV2fQ==

resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"

Загрузка…
Отмена
Сохранить