├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App
│ ├── App.css
│ ├── App.js
│ └── App.test.js
├── components
│ ├── Header.js
│ ├── PageHeader.js
│ ├── SideMenu.js
│ ├── controls
│ │ ├── Button.js
│ │ ├── Checkbox.js
│ │ ├── Controls.js
│ │ ├── DatePicker.js
│ │ ├── Input.js
│ │ ├── RadioGroup.js
│ │ └── Select.js
│ ├── useForm.js
│ └── useTable.js
├── index.css
├── index.js
├── logo.svg
├── pages
│ └── Employees
│ │ ├── EmployeeForm.js
│ │ └── Employees.js
├── serviceWorker.js
├── services
│ └── employeeService.js
└── setupTests.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Complete React Material UI Table - Paging, Sorting and Filtering
2 |
3 | These is the demo project used to explaining Material UI table with paging sorting and filtering.
4 | - Show array of records in mui table.
5 | - Implement Paging Sorting and Filtering
6 |
7 | Complete videos in this series of React Materil UI : https://bit.ly/3k2tUSD
8 |
9 | ## Get the Code
10 |
11 | ```
12 | $ git clone https://github.com/CodAffection/React-Material-UI-Table-Paging-Sorting-and-Filtering..git
13 | ```
14 |
15 | ## How it works ?
16 |
17 | :tv: Video tutorial on this same topic
18 | Url : https://bit.ly/3fPcxBe
19 |
20 |
23 |
24 |
25 | | :bar_chart: | List of Tutorials | | :moneybag: | Support Us |
26 | |--------------------------:|:---------------------|---|---------------------:|:-------------------------------------|
27 | | Angular |http://bit.ly/2KQN9xF | |Paypal | https://goo.gl/bPcyXW |
28 | | Asp.Net Core |http://bit.ly/30fPDMg | |Amazon Affiliate | https://geni.us/JDzpE |
29 | | React |http://bit.ly/325temF | |
30 | | Python |http://bit.ly/2ws4utg | | :point_right: | Follow Us |
31 | | Node.js |https://goo.gl/viJcFs | |Website |http://www.codaffection.com |
32 | | Asp.Net MVC |https://goo.gl/gvjUJ7 | |YouTube |https://www.youtube.com/codaffection |
33 | | Flutter |https://bit.ly/3ggmmJz| |Facebook |https://www.facebook.com/codaffection |
34 | | Web API |https://goo.gl/itVayJ | |Twitter |https://twitter.com/CodAffection |
35 | | MEAN Stack |https://goo.gl/YJPPAH | |
36 | | C# Tutorial |https://goo.gl/s1zJxo | |
37 | | Asp.Net WebForm |https://goo.gl/GXC2aJ | |
38 | | C# WinForm |https://goo.gl/vHS9Hd | |
39 | | MS SQL |https://goo.gl/MLYS9e | |
40 | | Crystal Report |https://goo.gl/5Vou7t | |
41 | | CG Exercises in C Program |https://goo.gl/qEWJCs | |
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "complete_mui_system",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@date-io/date-fns": "^1.3.13",
7 | "@material-ui/core": "^4.10.2",
8 | "@material-ui/icons": "^4.9.1",
9 | "@material-ui/pickers": "^3.2.10",
10 | "@testing-library/jest-dom": "^4.2.4",
11 | "@testing-library/react": "^9.3.2",
12 | "@testing-library/user-event": "^7.1.2",
13 | "date-fns": "^2.14.0",
14 | "react": "^16.13.1",
15 | "react-dom": "^16.13.1",
16 | "react-scripts": "3.4.1"
17 | },
18 | "scripts": {
19 | "start": "set PORT=3003 && react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodAffection/React-Material-UI-Table-Paging-Sorting-and-Filtering./ab564c560a9ef62b7be584016ed6ebe5eb766c76/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
33 |
34 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodAffection/React-Material-UI-Table-Paging-Sorting-and-Filtering./ab564c560a9ef62b7be584016ed6ebe5eb766c76/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodAffection/React-Material-UI-Table-Paging-Sorting-and-Filtering./ab564c560a9ef62b7be584016ed6ebe5eb766c76/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodAffection/React-Material-UI-Table-Paging-Sorting-and-Filtering./ab564c560a9ef62b7be584016ed6ebe5eb766c76/src/App/App.css
--------------------------------------------------------------------------------
/src/App/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import SideMenu from "../components/SideMenu";
4 | import { makeStyles, CssBaseline, createMuiTheme, ThemeProvider } from '@material-ui/core';
5 | import Header from "../components/Header";
6 | import PageHeader from '../components/PageHeader';
7 |
8 | import Employees from "../pages/Employees/Employees";
9 |
10 | const theme = createMuiTheme({
11 | palette: {
12 | primary: {
13 | main: "#333996",
14 | light: '#3c44b126'
15 | },
16 | secondary: {
17 | main: "#f83245",
18 | light: '#f8324526'
19 | },
20 | background: {
21 | default: "#f4f5fd"
22 | },
23 | },
24 | overrides:{
25 | MuiAppBar:{
26 | root:{
27 | transform:'translateZ(0)'
28 | }
29 | }
30 | },
31 | props:{
32 | MuiIconButton:{
33 | disableRipple:true
34 | }
35 | }
36 | })
37 |
38 |
39 | const useStyles = makeStyles({
40 | appMain: {
41 | paddingLeft: '320px',
42 | width: '100%'
43 | }
44 | })
45 |
46 | function App() {
47 | const classes = useStyles();
48 |
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | }
61 |
62 | export default App;
63 |
--------------------------------------------------------------------------------
/src/App/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { AppBar, Toolbar, Grid, InputBase, IconButton, Badge, makeStyles } from '@material-ui/core'
3 | import NotificationsNoneIcon from '@material-ui/icons/NotificationsNone';
4 | import ChatBubbleOutlineIcon from '@material-ui/icons/ChatBubbleOutline';
5 | import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew';
6 | import SearchIcon from '@material-ui/icons/Search';
7 |
8 |
9 | const useStyles = makeStyles(theme => ({
10 | root: {
11 | backgroundColor: '#fff',
12 |
13 | },
14 | searchInput: {
15 | opacity: '0.6',
16 | padding: `0px ${theme.spacing(1)}px`,
17 | fontSize: '0.8rem',
18 | '&:hover': {
19 | backgroundColor: '#f2f2f2'
20 | },
21 | '& .MuiSvgIcon-root': {
22 | marginRight: theme.spacing(1)
23 | }
24 | }
25 | }))
26 |
27 | export default function Header() {
28 |
29 | const classes = useStyles();
30 |
31 | return (
32 |
33 |
34 |
36 |
37 | }
41 | />
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/PageHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Paper, Card, Typography, makeStyles, Button } from '@material-ui/core'
3 |
4 | const useStyles = makeStyles(theme => ({
5 | root: {
6 | backgroundColor: '#fdfdff'
7 | },
8 | pageHeader:{
9 | padding:theme.spacing(4),
10 | display:'flex',
11 | marginBottom:theme.spacing(2)
12 | },
13 | pageIcon:{
14 | display:'inline-block',
15 | padding:theme.spacing(2),
16 | color:'#3c44b1'
17 | },
18 | pageTitle:{
19 | paddingLeft:theme.spacing(4),
20 | '& .MuiTypography-subtitle2':{
21 | opacity:'0.6'
22 | }
23 | }
24 | }))
25 |
26 | export default function PageHeader(props) {
27 |
28 | const classes = useStyles();
29 | const { title, subTitle, icon } = props;
30 | return (
31 |
32 |
33 |
34 | {icon}
35 |
36 |
37 |
40 | {title}
41 |
44 | {subTitle}
45 |
46 |
47 |
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/SideMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, withStyles } from "@material-ui/core";
3 |
4 | // withStyles & makeStyles
5 |
6 | const style = {
7 | sideMenu: {
8 | display: 'flex',
9 | flexDirection: 'column',
10 | position: 'absolute',
11 | left: '0px',
12 | width: '320px',
13 | height: '100%',
14 | backgroundColor: '#253053'
15 | }
16 | }
17 |
18 | const SideMenu = (props) => {
19 | const { classes } = props;
20 | return (
21 |
22 |
23 |
24 | )
25 | }
26 |
27 | export default withStyles(style)(SideMenu);
28 |
--------------------------------------------------------------------------------
/src/components/controls/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Button as MuiButton, makeStyles } from "@material-ui/core";
3 |
4 |
5 | const useStyles = makeStyles(theme => ({
6 | root: {
7 | margin: theme.spacing(0.5)
8 | },
9 | label: {
10 | textTransform: 'none'
11 | }
12 | }))
13 |
14 | export default function Button(props) {
15 |
16 | const { text, size, color, variant, onClick, ...other } = props
17 | const classes = useStyles();
18 |
19 | return (
20 |
27 | {text}
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/controls/Checkbox.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormControl, FormControlLabel, Checkbox as MuiCheckbox } from '@material-ui/core';
3 |
4 | export default function Checkbox(props) {
5 |
6 | const { name, label, value, onChange } = props;
7 |
8 |
9 | const convertToDefEventPara = (name, value) => ({
10 | target: {
11 | name, value
12 | }
13 | })
14 |
15 | return (
16 |
17 | onChange(convertToDefEventPara(name, e.target.checked))}
23 | />}
24 | label={label}
25 | />
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/controls/Controls.js:
--------------------------------------------------------------------------------
1 | import Input from "./Input";
2 | import RadioGroup from "./RadioGroup";
3 | import Select from "./Select";
4 | import Checkbox from "./Checkbox";
5 | import DatePicker from "./DatePicker";
6 | import Button from "./Button";
7 |
8 | const Controls = {
9 | Input,
10 | RadioGroup,
11 | Select,
12 | Checkbox,
13 | DatePicker,
14 | Button
15 |
16 | }
17 |
18 | export default Controls;
--------------------------------------------------------------------------------
/src/components/controls/DatePicker.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { MuiPickersUtilsProvider, KeyboardDatePicker } from "@material-ui/pickers";
3 | import DateFnsUtils from "@date-io/date-fns";
4 |
5 | export default function DatePicker(props) {
6 |
7 | const { name, label, value, onChange } = props
8 |
9 |
10 | const convertToDefEventPara = (name, value) => ({
11 | target: {
12 | name, value
13 | }
14 | })
15 |
16 | return (
17 |
18 | onChange(convertToDefEventPara(name,date))}
24 |
25 | />
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/controls/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { TextField } from '@material-ui/core';
3 |
4 | export default function Input(props) {
5 |
6 | const { name, label, value,error=null, onChange, ...other } = props;
7 | return (
8 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/controls/RadioGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormControl, FormLabel, RadioGroup as MuiRadioGroup, FormControlLabel, Radio } from '@material-ui/core';
3 |
4 | export default function RadioGroup(props) {
5 |
6 | const { name, label, value, onChange, items } = props;
7 |
8 | return (
9 |
10 | {label}
11 |
15 | {
16 | items.map(
17 | item => (
18 | } label={item.title} />
19 | )
20 | )
21 | }
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/controls/Select.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FormControl, InputLabel, Select as MuiSelect, MenuItem, FormHelperText } from '@material-ui/core';
3 |
4 | export default function Select(props) {
5 |
6 | const { name, label, value,error=null, onChange, options } = props;
7 |
8 | return (
9 |
11 | {label}
12 |
17 |
18 | {
19 | options.map(
20 | item => ()
21 | )
22 | }
23 |
24 | {error && {error}}
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/useForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { makeStyles } from "@material-ui/core";
3 |
4 | export function useForm(initialFValues, validateOnChange = false, validate) {
5 |
6 |
7 | const [values, setValues] = useState(initialFValues);
8 | const [errors, setErrors] = useState({});
9 |
10 | const handleInputChange = e => {
11 | const { name, value } = e.target
12 | setValues({
13 | ...values,
14 | [name]: value
15 | })
16 | if (validateOnChange)
17 | validate({ [name]: value })
18 | }
19 |
20 | const resetForm = () => {
21 | setValues(initialFValues);
22 | setErrors({})
23 | }
24 |
25 |
26 | return {
27 | values,
28 | setValues,
29 | errors,
30 | setErrors,
31 | handleInputChange,
32 | resetForm
33 |
34 | }
35 | }
36 |
37 |
38 | const useStyles = makeStyles(theme => ({
39 | root: {
40 | '& .MuiFormControl-root': {
41 | width: '80%',
42 | margin: theme.spacing(1)
43 | }
44 | }
45 | }))
46 |
47 | export function Form(props) {
48 |
49 | const classes = useStyles();
50 | const { children, ...other } = props;
51 | return (
52 |
55 | )
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/src/components/useTable.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Table, TableHead, TableRow, TableCell, makeStyles, TablePagination, TableSortLabel } from '@material-ui/core'
3 |
4 | const useStyles = makeStyles(theme => ({
5 | table: {
6 | marginTop: theme.spacing(3),
7 | '& thead th': {
8 | fontWeight: '600',
9 | color: theme.palette.primary.main,
10 | backgroundColor: theme.palette.primary.light,
11 | },
12 | '& tbody td': {
13 | fontWeight: '300',
14 | },
15 | '& tbody tr:hover': {
16 | backgroundColor: '#fffbf2',
17 | cursor: 'pointer',
18 | },
19 | },
20 | }))
21 |
22 | export default function useTable(records, headCells,filterFn) {
23 |
24 | const classes = useStyles();
25 |
26 | const pages = [5, 10, 25]
27 | const [page, setPage] = useState(0)
28 | const [rowsPerPage, setRowsPerPage] = useState(pages[page])
29 | const [order, setOrder] = useState()
30 | const [orderBy, setOrderBy] = useState()
31 |
32 | const TblContainer = props => (
33 |
34 | {props.children}
35 |
36 | )
37 |
38 | const TblHead = props => {
39 |
40 | const handleSortRequest = cellId => {
41 | const isAsc = orderBy === cellId && order === "asc";
42 | setOrder(isAsc ? 'desc' : 'asc');
43 | setOrderBy(cellId)
44 | }
45 |
46 | return (
47 |
48 | {
49 | headCells.map(headCell => (
50 |
52 | {headCell.disableSorting ? headCell.label :
53 | { handleSortRequest(headCell.id) }}>
57 | {headCell.label}
58 |
59 | }
60 | ))
61 | }
62 |
63 | )
64 | }
65 |
66 | const handleChangePage = (event, newPage) => {
67 | setPage(newPage);
68 | }
69 |
70 | const handleChangeRowsPerPage = event => {
71 | setRowsPerPage(parseInt(event.target.value, 10))
72 | setPage(0);
73 | }
74 |
75 | const TblPagination = () => ()
84 |
85 | function stableSort(array, comparator) {
86 | const stabilizedThis = array.map((el, index) => [el, index]);
87 | stabilizedThis.sort((a, b) => {
88 | const order = comparator(a[0], b[0]);
89 | if (order !== 0) return order;
90 | return a[1] - b[1];
91 | });
92 | return stabilizedThis.map((el) => el[0]);
93 | }
94 |
95 | function getComparator(order, orderBy) {
96 | return order === 'desc'
97 | ? (a, b) => descendingComparator(a, b, orderBy)
98 | : (a, b) => -descendingComparator(a, b, orderBy);
99 | }
100 |
101 | function descendingComparator(a, b, orderBy) {
102 | if (b[orderBy] < a[orderBy]) {
103 | return -1;
104 | }
105 | if (b[orderBy] > a[orderBy]) {
106 | return 1;
107 | }
108 | return 0;
109 | }
110 |
111 | const recordsAfterPagingAndSorting = () => {
112 | return stableSort(filterFn.fn(records), getComparator(order, orderBy))
113 | .slice(page * rowsPerPage, (page + 1) * rowsPerPage)
114 | }
115 |
116 | return {
117 | TblContainer,
118 | TblHead,
119 | TblPagination,
120 | recordsAfterPagingAndSorting
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App/App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 | ,
9 | document.getElementById('root')
10 | );
11 |
12 | // If you want your app to work offline and load faster, you can change
13 | // unregister() to register() below. Note this comes with some pitfalls.
14 | // Learn more about service workers: https://bit.ly/CRA-PWA
15 | serviceWorker.unregister();
16 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/pages/Employees/EmployeeForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { Grid, } from '@material-ui/core';
3 | import Controls from "../../components/controls/Controls";
4 | import { useForm, Form } from '../../components/useForm';
5 | import * as employeeService from "../../services/employeeService";
6 |
7 |
8 | const genderItems = [
9 | { id: 'male', title: 'Male' },
10 | { id: 'female', title: 'Female' },
11 | { id: 'other', title: 'Other' },
12 | ]
13 |
14 | const initialFValues = {
15 | id: 0,
16 | fullName: '',
17 | email: '',
18 | mobile: '',
19 | city: '',
20 | gender: 'male',
21 | departmentId: '',
22 | hireDate: new Date(),
23 | isPermanent: false,
24 | }
25 |
26 | export default function EmployeeForm() {
27 |
28 | const validate = (fieldValues = values) => {
29 | let temp = { ...errors }
30 | if ('fullName' in fieldValues)
31 | temp.fullName = fieldValues.fullName ? "" : "This field is required."
32 | if ('email' in fieldValues)
33 | temp.email = (/$^|.+@.+..+/).test(fieldValues.email) ? "" : "Email is not valid."
34 | if ('mobile' in fieldValues)
35 | temp.mobile = fieldValues.mobile.length > 9 ? "" : "Minimum 10 numbers required."
36 | if ('departmentId' in fieldValues)
37 | temp.departmentId = fieldValues.departmentId.length != 0 ? "" : "This field is required."
38 | setErrors({
39 | ...temp
40 | })
41 |
42 | if (fieldValues == values)
43 | return Object.values(temp).every(x => x == "")
44 | }
45 |
46 | const {
47 | values,
48 | setValues,
49 | errors,
50 | setErrors,
51 | handleInputChange,
52 | resetForm
53 | } = useForm(initialFValues, true, validate);
54 |
55 | const handleSubmit = e => {
56 | e.preventDefault()
57 | if (validate()){
58 | employeeService.insertEmployee(values)
59 | resetForm()
60 | }
61 | }
62 |
63 | return (
64 |
137 | )
138 | }
139 |
--------------------------------------------------------------------------------
/src/pages/Employees/Employees.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import EmployeeForm from "./EmployeeForm";
3 | import PageHeader from "../../components/PageHeader";
4 | import PeopleOutlineTwoToneIcon from '@material-ui/icons/PeopleOutlineTwoTone';
5 | import { Paper, makeStyles, TableBody, TableRow, TableCell, Toolbar, InputAdornment } from '@material-ui/core';
6 | import useTable from "../../components/useTable";
7 | import * as employeeService from "../../services/employeeService";
8 | import Controls from "../../components/controls/Controls";
9 | import { Search } from "@material-ui/icons";
10 |
11 | const useStyles = makeStyles(theme => ({
12 | pageContent: {
13 | margin: theme.spacing(5),
14 | padding: theme.spacing(3)
15 | },
16 | searchInput: {
17 | width: '75%'
18 | }
19 | }))
20 |
21 |
22 | const headCells = [
23 | { id: 'fullName', label: 'Employee Name' },
24 | { id: 'email', label: 'Email Address (Personal)' },
25 | { id: 'mobile', label: 'Mobile Number' },
26 | { id: 'department', label: 'Department', disableSorting: true },
27 | ]
28 |
29 | export default function Employees() {
30 |
31 | const classes = useStyles();
32 | const [records, setRecords] = useState(employeeService.getAllEmployees())
33 | const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
34 |
35 | const {
36 | TblContainer,
37 | TblHead,
38 | TblPagination,
39 | recordsAfterPagingAndSorting
40 | } = useTable(records, headCells, filterFn);
41 |
42 | const handleSearch = e => {
43 | let target = e.target;
44 | setFilterFn({
45 | fn: items => {
46 | if (target.value == "")
47 | return items;
48 | else
49 | return items.filter(x => x.fullName.toLowerCase().includes(target.value))
50 | }
51 | })
52 | }
53 |
54 | return (
55 | <>
56 | }
60 | />
61 |
62 | {/* */}
63 |
64 |
69 |
70 | )
71 | }}
72 | onChange={handleSearch}
73 | />
74 |
75 |
76 |
77 |
78 | {
79 | recordsAfterPagingAndSorting().map(item =>
80 | (
81 | {item.fullName}
82 | {item.email}
83 | {item.mobile}
84 | {item.department}
85 | )
86 | )
87 | }
88 |
89 |
90 |
91 |
92 | >
93 | )
94 | }
95 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/services/employeeService.js:
--------------------------------------------------------------------------------
1 | const KEYS = {
2 | employees: 'employees',
3 | employeeId: 'employeeId'
4 | }
5 |
6 | export const getDepartmentCollection = () => ([
7 | { id: '1', title: 'Development' },
8 | { id: '2', title: 'Marketing' },
9 | { id: '3', title: 'Accounting' },
10 | { id: '4', title: 'HR' },
11 | ])
12 |
13 | export function insertEmployee(data) {
14 | let employees = getAllEmployees();
15 | data['id'] = generateEmployeeId()
16 | employees.push(data)
17 | localStorage.setItem(KEYS.employees, JSON.stringify(employees))
18 | }
19 |
20 | export function generateEmployeeId() {
21 | if (localStorage.getItem(KEYS.employeeId) == null)
22 | localStorage.setItem(KEYS.employeeId, '0')
23 | var id = parseInt(localStorage.getItem(KEYS.employeeId))
24 | localStorage.setItem(KEYS.employeeId, (++id).toString())
25 | return id;
26 | }
27 |
28 | export function getAllEmployees() {
29 | if (localStorage.getItem(KEYS.employees) == null)
30 | localStorage.setItem(KEYS.employees, JSON.stringify([]))
31 | let employees = JSON.parse(localStorage.getItem(KEYS.employees));
32 | //map departmentID to department title
33 | let departments = getDepartmentCollection();
34 | return employees.map(x => ({
35 | ...x,
36 | department: departments[x.departmentId - 1].title
37 | }))
38 | }
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------