├── next.config.js
├── .babelrc
├── .gitignore
├── public
└── assets
│ └── footerAdornment.svg
├── README.md
├── package.json
├── src
└── ui
│ ├── Footer.js
│ ├── Link.js
│ ├── Header.js
│ ├── Theme.js
│ └── EnhancedTable.js
└── pages
├── _app.js
├── _document.js
└── index.js
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # misc
7 | .DS_Store
8 | .env.local
9 | .env.development.local
10 | .env.test.local
11 | .env.production.local
12 |
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # Next.js
18 | /.next
19 |
--------------------------------------------------------------------------------
/public/assets/footerAdornment.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js example
2 |
3 | ## How to use
4 |
5 | Download the example [or clone the repo](https://github.com/mui-org/material-ui):
6 |
7 | ```sh
8 | curl https://codeload.github.com/mui-org/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/nextjs
9 | cd nextjs
10 | ```
11 |
12 | Install it and run:
13 |
14 | ```sh
15 | npm install
16 | npm run dev
17 | ```
18 |
19 | ## The idea behind the example
20 |
21 | [Next.js](https://github.com/zeit/next.js) is a framework for server-rendered React apps.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs",
3 | "version": "4.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "@date-io/date-fns": "^1.3.13",
7 | "@material-ui/core": "latest",
8 | "@material-ui/icons": "^4.5.1",
9 | "@material-ui/pickers": "^3.2.8",
10 | "@material-ui/styles": "^4.7.1",
11 | "clsx": "latest",
12 | "date-fns": "^2.0.0-beta.5",
13 | "next": "latest",
14 | "prop-types": "latest",
15 | "react": "latest",
16 | "react-dom": "latest"
17 | },
18 | "scripts": {
19 | "dev": "next",
20 | "build": "next build",
21 | "start": "next start"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/ui/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { makeStyles } from "@material-ui/core/styles";
3 |
4 | const useStyles = makeStyles(theme => ({
5 | footer: {
6 | backgroundColor: theme.palette.common.blue,
7 | width: "100%",
8 | zIndex: 1302,
9 | position: "relative"
10 | },
11 | adornment: {
12 | width: "25em",
13 | verticalAlign: "bottom",
14 | [theme.breakpoints.down("md")]: {
15 | width: "21em"
16 | },
17 | [theme.breakpoints.down("xs")]: {
18 | width: "15em"
19 | }
20 | }
21 | }));
22 |
23 | export default function Footer(props) {
24 | const classes = useStyles();
25 |
26 | return (
27 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import App from "next/app";
3 | import Head from "next/head";
4 | import { ThemeProvider } from "@material-ui/core/styles";
5 | import CssBaseline from "@material-ui/core/CssBaseline";
6 | import theme from "../src/ui/theme";
7 | import Header from "../src/ui/Header";
8 | import Footer from "../src/ui/Footer";
9 |
10 | export default class MyApp extends App {
11 | componentDidMount() {
12 | // Remove the server-side injected CSS.
13 | const jssStyles = document.querySelector("#jss-server-side");
14 | if (jssStyles) {
15 | jssStyles.parentElement.removeChild(jssStyles);
16 | }
17 | }
18 |
19 | render() {
20 | const { Component, pageProps } = this.props;
21 |
22 | return (
23 |
24 |
25 | My page
26 |
27 |
28 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/ui/Link.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/anchor-has-content */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import clsx from 'clsx';
5 | import { useRouter } from 'next/router';
6 | import NextLink from 'next/link';
7 | import MuiLink from '@material-ui/core/Link';
8 |
9 | const NextComposed = React.forwardRef(function NextComposed(props, ref) {
10 | const { as, href, prefetch, ...other } = props;
11 |
12 | return (
13 |
14 |
15 |
16 | );
17 | });
18 |
19 | NextComposed.propTypes = {
20 | as: PropTypes.string,
21 | href: PropTypes.string,
22 | prefetch: PropTypes.bool,
23 | };
24 |
25 | // A styled version of the Next.js Link component:
26 | // https://nextjs.org/docs/#with-link
27 | function Link(props) {
28 | const {
29 | activeClassName = 'active',
30 | className: classNameProps,
31 | innerRef,
32 | naked,
33 | ...other
34 | } = props;
35 | const router = useRouter();
36 |
37 | const className = clsx(classNameProps, {
38 | [activeClassName]: router.pathname === props.href && activeClassName,
39 | });
40 |
41 | if (naked) {
42 | return ;
43 | }
44 |
45 | return ;
46 | }
47 |
48 | Link.propTypes = {
49 | activeClassName: PropTypes.string,
50 | as: PropTypes.string,
51 | className: PropTypes.string,
52 | href: PropTypes.string,
53 | innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
54 | naked: PropTypes.bool,
55 | onClick: PropTypes.func,
56 | prefetch: PropTypes.bool,
57 | };
58 |
59 | export default React.forwardRef((props, ref) => );
60 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Document, { Head, Main, NextScript } from "next/document";
3 | import { ServerStyleSheets } from "@material-ui/core/styles";
4 | import theme from "../src/ui/theme";
5 |
6 | export default class MyDocument extends Document {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
16 | {/* PWA primary color */}
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
32 | MyDocument.getInitialProps = async ctx => {
33 | // Resolution order
34 | //
35 | // On the server:
36 | // 1. app.getInitialProps
37 | // 2. page.getInitialProps
38 | // 3. document.getInitialProps
39 | // 4. app.render
40 | // 5. page.render
41 | // 6. document.render
42 | //
43 | // On the server with error:
44 | // 1. document.getInitialProps
45 | // 2. app.render
46 | // 3. page.render
47 | // 4. document.render
48 | //
49 | // On the client
50 | // 1. app.getInitialProps
51 | // 2. page.getInitialProps
52 | // 3. app.render
53 | // 4. page.render
54 |
55 | // Render app and page and get the context of the page with collected side effects.
56 | const sheets = new ServerStyleSheets();
57 | const originalRenderPage = ctx.renderPage;
58 |
59 | ctx.renderPage = () =>
60 | originalRenderPage({
61 | enhanceApp: App => props => sheets.collect()
62 | });
63 |
64 | const initialProps = await Document.getInitialProps(ctx);
65 |
66 | return {
67 | ...initialProps,
68 | // Styles fragment is rendered after the app and page rendering finish.
69 | styles: [
70 | ...React.Children.toArray(initialProps.styles),
71 | sheets.getStyleElement()
72 | ]
73 | };
74 | };
75 |
--------------------------------------------------------------------------------
/src/ui/Header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import AppBar from "@material-ui/core/AppBar";
3 | import Toolbar from "@material-ui/core/Toolbar";
4 | import { makeStyles, useTheme } from "@material-ui/core/styles";
5 |
6 | const useStyles = makeStyles(theme => ({
7 | toolbarMargin: {
8 | ...theme.mixins.toolbar,
9 | marginBottom: "3em",
10 | [theme.breakpoints.down("md")]: {
11 | marginBottom: "2em"
12 | },
13 | [theme.breakpoints.down("xs")]: {
14 | marginBottom: "1.25em"
15 | }
16 | },
17 | logo: {
18 | height: "8em",
19 | textTransform: "none",
20 | [theme.breakpoints.down("md")]: {
21 | height: "7em"
22 | },
23 | [theme.breakpoints.down("xs")]: {
24 | height: "5.5em"
25 | }
26 | },
27 | appbar: {
28 | zIndex: theme.zIndex.modal + 1
29 | }
30 | }));
31 |
32 | export default function Header(props) {
33 | const classes = useStyles();
34 | const theme = useTheme();
35 |
36 | return (
37 |
38 |
39 |
40 |
45 |
93 |
94 |
95 |
96 |
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/src/ui/Theme.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from "@material-ui/core/styles";
2 |
3 | const arcBlue = "#0B72B9";
4 | const arcOrange = "#FFBA60";
5 | const arcGrey = "#868686";
6 |
7 | export default createMuiTheme({
8 | palette: {
9 | common: {
10 | blue: arcBlue,
11 | orange: arcOrange
12 | },
13 | primary: {
14 | main: arcBlue
15 | },
16 | secondary: {
17 | main: arcOrange
18 | }
19 | },
20 | typography: {
21 | tab: {
22 | fontFamily: "Raleway",
23 | textTransform: "none",
24 | fontWeight: 700,
25 | color: "white",
26 | fontSize: "1rem"
27 | },
28 | estimate: {
29 | fontFamily: "Pacifico",
30 | fontSize: "1rem",
31 | textTransform: "none",
32 | color: "white"
33 | },
34 | h1: {
35 | fontFamily: "Raleway",
36 | fontWeight: 700,
37 | fontSize: "2.5rem",
38 | color: arcBlue,
39 | lineHeight: 1.5
40 | },
41 | h3: {
42 | fontFamily: "Pacifico",
43 | fontSize: "2.5rem",
44 | color: arcBlue
45 | },
46 | h4: {
47 | fontFamily: "Raleway",
48 | fontSize: "1.75rem",
49 | color: arcBlue,
50 | fontWeight: 700
51 | },
52 | h6: {
53 | fontWeight: 500,
54 | fontFamily: "Raleway",
55 | color: arcBlue
56 | },
57 | subtitle1: {
58 | fontSize: "1.25rem",
59 | fontWeight: 300,
60 | color: arcGrey
61 | },
62 | subtitle2: {
63 | color: "white",
64 | fontWeight: 300,
65 | fontSize: "1.25rem"
66 | },
67 | body1: {
68 | fontSize: "1.25rem",
69 | color: arcGrey,
70 | fontWeight: 300
71 | },
72 | caption: {
73 | fontSize: "1rem",
74 | fontWeight: 300,
75 | color: arcGrey
76 | },
77 | learnButton: {
78 | borderColor: arcBlue,
79 | borderWidth: 2,
80 | textTransform: "none",
81 | color: arcBlue,
82 | borderRadius: 50,
83 | fontFamily: "Roboto",
84 | fontWeight: "bold"
85 | }
86 | },
87 | overrides: {
88 | MuiTableCell: {
89 | head: {
90 | fontSize: "1rem",
91 | fontWeight: 700,
92 | color: arcBlue,
93 | borderColor: arcBlue,
94 | borderWidth: 2
95 | },
96 | body: {
97 | color: arcGrey,
98 | borderColor: arcBlue,
99 | borderWidth: 2
100 | }
101 | },
102 | MuiTableSortLabel: {
103 | root: {
104 | "&:hover": {
105 | color: arcOrange
106 | },
107 | "&.MuiTableSortLabel-active": {
108 | color: arcOrange
109 | }
110 | },
111 | icon: {
112 | fill: arcOrange
113 | }
114 | },
115 | MuiSvgIcon: {
116 | root: {
117 | "&.MuiSelect-icon": {
118 | fill: arcOrange
119 | }
120 | }
121 | },
122 | MuiFormControlLabel: {
123 | label: {
124 | color: arcBlue,
125 | fontWeight: 700
126 | },
127 | labelPlacementStart: {
128 | marginLeft: 0
129 | }
130 | },
131 | MuiInputLabel: {
132 | root: {
133 | color: arcBlue,
134 | fontSize: "1rem"
135 | }
136 | },
137 | MuiInput: {
138 | root: {
139 | color: arcGrey,
140 | fontWeight: 300
141 | },
142 | underline: {
143 | "&:before": {
144 | borderBottom: `2px solid ${arcBlue}`
145 | },
146 | "&:hover:not($disabled):not($focused):not($error):before": {
147 | borderBottom: `2px solid ${arcBlue}`
148 | }
149 | }
150 | }
151 | }
152 | });
153 |
--------------------------------------------------------------------------------
/src/ui/EnhancedTable.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import clsx from "clsx";
4 | import { lighten, makeStyles } from "@material-ui/core/styles";
5 | import Table from "@material-ui/core/Table";
6 | import TableBody from "@material-ui/core/TableBody";
7 | import TableCell from "@material-ui/core/TableCell";
8 | import TableContainer from "@material-ui/core/TableContainer";
9 | import TableHead from "@material-ui/core/TableHead";
10 | import TablePagination from "@material-ui/core/TablePagination";
11 | import TableRow from "@material-ui/core/TableRow";
12 | import TableSortLabel from "@material-ui/core/TableSortLabel";
13 | import Toolbar from "@material-ui/core/Toolbar";
14 | import Typography from "@material-ui/core/Typography";
15 | import Paper from "@material-ui/core/Paper";
16 | import Checkbox from "@material-ui/core/Checkbox";
17 | import IconButton from "@material-ui/core/IconButton";
18 | import Tooltip from "@material-ui/core/Tooltip";
19 | import FormControlLabel from "@material-ui/core/FormControlLabel";
20 | import Switch from "@material-ui/core/Switch";
21 | import DeleteIcon from "@material-ui/icons/Delete";
22 | import FilterListIcon from "@material-ui/icons/FilterList";
23 | import Snackbar from "@material-ui/core/Snackbar";
24 | import Button from "@material-ui/core/Button";
25 | import Menu from "@material-ui/core/Menu";
26 | import MenuItem from "@material-ui/core/MenuItem";
27 | import TextField from "@material-ui/core/TextField";
28 | import InputAdornment from "@material-ui/core/InputAdornment";
29 | import Chip from "@material-ui/core/Chip";
30 | import Grid from "@material-ui/core/Grid";
31 |
32 | function desc(a, b, orderBy) {
33 | if (b[orderBy] < a[orderBy]) {
34 | return -1;
35 | }
36 | if (b[orderBy] > a[orderBy]) {
37 | return 1;
38 | }
39 | return 0;
40 | }
41 |
42 | function stableSort(array, cmp) {
43 | const stabilizedThis = array.map((el, index) => [el, index]);
44 | stabilizedThis.sort((a, b) => {
45 | const order = cmp(a[0], b[0]);
46 | if (order !== 0) return order;
47 | return a[1] - b[1];
48 | });
49 | return stabilizedThis.map(el => el[0]);
50 | }
51 |
52 | function getSorting(order, orderBy) {
53 | return order === "desc"
54 | ? (a, b) => desc(a, b, orderBy)
55 | : (a, b) => -desc(a, b, orderBy);
56 | }
57 |
58 | const headCells = [
59 | { id: "name", label: "Name" },
60 | { id: "date", label: "Date" },
61 | { id: "service", label: "Service" },
62 | { id: "features", label: "Features" },
63 | { id: "complexity", label: "Complexity" },
64 | { id: "platforms", label: "Platforms" },
65 | { id: "users", label: "Users" },
66 | { id: "total", label: "Total" }
67 | ];
68 |
69 | function EnhancedTableHead(props) {
70 | const {
71 | classes,
72 | onSelectAllClick,
73 | order,
74 | orderBy,
75 | numSelected,
76 | rowCount,
77 | onRequestSort
78 | } = props;
79 | const createSortHandler = property => event => {
80 | onRequestSort(event, property);
81 | };
82 |
83 | return (
84 |
85 |
86 |
87 | 0 && numSelected < rowCount}
89 | checked={numSelected === rowCount}
90 | onChange={onSelectAllClick}
91 | inputProps={{ "aria-label": "select all desserts" }}
92 | />
93 |
94 | {headCells.map(headCell => (
95 |
100 |
106 | {headCell.label}
107 | {orderBy === headCell.id ? (
108 |
109 | {order === "desc" ? "sorted descending" : "sorted ascending"}
110 |
111 | ) : null}
112 |
113 |
114 | ))}
115 |
116 |
117 | );
118 | }
119 |
120 | EnhancedTableHead.propTypes = {
121 | classes: PropTypes.object.isRequired,
122 | numSelected: PropTypes.number.isRequired,
123 | onRequestSort: PropTypes.func.isRequired,
124 | onSelectAllClick: PropTypes.func.isRequired,
125 | order: PropTypes.oneOf(["asc", "desc"]).isRequired,
126 | orderBy: PropTypes.string.isRequired,
127 | rowCount: PropTypes.number.isRequired
128 | };
129 |
130 | const useToolbarStyles = makeStyles(theme => ({
131 | root: {
132 | paddingLeft: theme.spacing(2),
133 | paddingRight: theme.spacing(1)
134 | },
135 | highlight:
136 | theme.palette.type === "light"
137 | ? {
138 | color: theme.palette.secondary.main,
139 | backgroundColor: lighten(theme.palette.secondary.light, 0.85)
140 | }
141 | : {
142 | color: theme.palette.text.primary,
143 | backgroundColor: theme.palette.secondary.dark
144 | },
145 | title: {
146 | flex: "1 1 100%"
147 | },
148 | menu: {
149 | "&:hover": {
150 | backgroundColor: "#fff"
151 | },
152 | "&.Mui-focusVisible": {
153 | backgroundColor: "#fff"
154 | }
155 | },
156 | totalFilter: {
157 | fontSize: "2rem",
158 | color: theme.palette.common.orange
159 | },
160 | dollarSign: {
161 | fontSize: "1.5rem",
162 | color: theme.palette.common.orange
163 | }
164 | }));
165 |
166 | const EnhancedTableToolbar = props => {
167 | const classes = useToolbarStyles();
168 | const { numSelected } = props;
169 | const [undo, setUndo] = React.useState([]);
170 | const [anchorEl, setAnchorEl] = React.useState(null);
171 | const [openMenu, setOpenMenu] = React.useState(false);
172 |
173 | const [alert, setAlert] = React.useState({
174 | open: false,
175 | color: "#FF3232",
176 | message: "Row deleted!"
177 | });
178 |
179 | const handleClick = e => {
180 | setAnchorEl(e.currentTarget);
181 | setOpenMenu(true);
182 | };
183 |
184 | const handleClose = e => {
185 | setAnchorEl(null);
186 | setOpenMenu(false);
187 | };
188 |
189 | const onDelete = () => {
190 | const newRows = [...props.rows];
191 | const selectedRows = newRows.filter(row =>
192 | props.selected.includes(row.name)
193 | );
194 | selectedRows.map(row => (row.search = false));
195 | props.setRows(newRows);
196 |
197 | setUndo(selectedRows);
198 | props.setSelected([]);
199 | setAlert({ ...alert, open: true });
200 | };
201 |
202 | const onUndo = () => {
203 | setAlert({ ...alert, open: false });
204 | const newRows = [...props.rows];
205 | const redo = [...undo];
206 | redo.map(row => (row.search = true));
207 | Array.prototype.push.apply(newRows, ...redo);
208 | props.setRows(newRows);
209 | };
210 |
211 | const handleTotalFilter = event => {
212 | props.setFilterPrice(event.target.value);
213 |
214 | if (event.target.value !== "") {
215 | const newRows = [...props.rows];
216 | newRows.map(row =>
217 | eval(
218 | `${event.target.value} ${
219 | props.totalFilter === "=" ? "===" : props.totalFilter
220 | } ${row.total.slice(1, row.total.length)}`
221 | )
222 | ? (row.search = true)
223 | : (row.search = false)
224 | );
225 | props.setRows(newRows);
226 | } else {
227 | const newRows = [...props.rows];
228 | newRows.map(row => (row.search = true));
229 | props.setRows(newRows);
230 | }
231 | };
232 |
233 | const filterChange = operator => {
234 | if (props.filterPrice !== "") {
235 | const newRows = [...props.rows];
236 | newRows.map(row =>
237 | eval(
238 | `${props.filterPrice} ${
239 | operator === "=" ? "===" : operator
240 | } ${row.total.slice(1, row.total.length)}`
241 | )
242 | ? (row.search = true)
243 | : (row.search = false)
244 | );
245 | props.setRows(newRows);
246 | }
247 | };
248 |
249 | return (
250 | 0
253 | })}
254 | >
255 | {numSelected > 0 ? (
256 |
261 | {numSelected} selected
262 |
263 | ) : (
264 |
269 | {null}
270 |
271 | )}
272 |
273 | {numSelected > 0 ? (
274 |
275 |
276 |
277 |
278 |
279 | ) : (
280 |
281 |
282 |
283 |
284 |
285 | )}
286 | {
296 | if (reason === "clickaway") {
297 | setAlert({ ...alert, open: false });
298 | const newRows = [...props.rows];
299 | const names = [...undo.map(row => row.name)];
300 | props.setRows(newRows.filter(row => !names.includes(row.name)));
301 | }
302 | }}
303 | action={
304 |
307 | }
308 | />
309 |
360 |
361 | );
362 | };
363 |
364 | EnhancedTableToolbar.propTypes = {
365 | numSelected: PropTypes.number.isRequired
366 | };
367 |
368 | const useStyles = makeStyles(theme => ({
369 | root: {
370 | width: "100%"
371 | },
372 | paper: {
373 | width: "100%",
374 | marginBottom: theme.spacing(2)
375 | },
376 | table: {
377 | minWidth: 750
378 | },
379 | visuallyHidden: {
380 | border: 0,
381 | clip: "rect(0 0 0 0)",
382 | height: 1,
383 | margin: -1,
384 | overflow: "hidden",
385 | padding: 0,
386 | position: "absolute",
387 | top: 20,
388 | width: 1
389 | },
390 | chip: {
391 | marginRight: "2em",
392 | backgroundColor: theme.palette.common.blue,
393 | color: "#fff"
394 | }
395 | }));
396 |
397 | export default function EnhancedTable(props) {
398 | const classes = useStyles();
399 | const [order, setOrder] = React.useState("asc");
400 | const [orderBy, setOrderBy] = React.useState("name");
401 | const [selected, setSelected] = React.useState([]);
402 | const [rowsPerPage, setRowsPerPage] = React.useState(5);
403 | const [filterPrice, setFilterPrice] = React.useState("");
404 | const [totalFilter, setTotalFilter] = React.useState(">");
405 |
406 | const handleRequestSort = (event, property) => {
407 | const isDesc = orderBy === property && order === "desc";
408 | setOrder(isDesc ? "asc" : "desc");
409 | setOrderBy(property);
410 | };
411 |
412 | const handleSelectAllClick = event => {
413 | if (event.target.checked) {
414 | const newSelecteds = props.rows.map(n => n.name);
415 | setSelected(newSelecteds);
416 | return;
417 | }
418 | setSelected([]);
419 | };
420 |
421 | const handleClick = (event, name) => {
422 | const selectedIndex = selected.indexOf(name);
423 | let newSelected = [];
424 |
425 | if (selectedIndex === -1) {
426 | newSelected = newSelected.concat(selected, name);
427 | } else if (selectedIndex === 0) {
428 | newSelected = newSelected.concat(selected.slice(1));
429 | } else if (selectedIndex === selected.length - 1) {
430 | newSelected = newSelected.concat(selected.slice(0, -1));
431 | } else if (selectedIndex > 0) {
432 | newSelected = newSelected.concat(
433 | selected.slice(0, selectedIndex),
434 | selected.slice(selectedIndex + 1)
435 | );
436 | }
437 |
438 | setSelected(newSelected);
439 | };
440 |
441 | const handleChangePage = (event, newPage) => {
442 | props.setPage(newPage);
443 | };
444 |
445 | const handleChangeRowsPerPage = event => {
446 | setRowsPerPage(parseInt(event.target.value, 10));
447 | props.setPage(0);
448 | };
449 |
450 | const isSelected = name => selected.indexOf(name) !== -1;
451 |
452 | const switchFilters = () => {
453 | const {
454 | websiteChecked,
455 | iOSChecked,
456 | androidChecked,
457 | softwareChecked
458 | } = props;
459 |
460 | const websites = props.rows.filter(row =>
461 | websiteChecked ? row.service === "Website" : null
462 | );
463 |
464 | const iOSApps = props.rows.filter(row =>
465 | iOSChecked ? row.platforms.includes("iOS") : null
466 | );
467 |
468 | const androidApps = props.rows.filter(row =>
469 | androidChecked ? row.platforms.includes("Android") : null
470 | );
471 |
472 | const softwareApps = props.rows.filter(row =>
473 | softwareChecked ? row.service === "Custom Software" : null
474 | );
475 |
476 | if (!websiteChecked && !iOSChecked && !androidChecked && !softwareChecked) {
477 | return props.rows;
478 | } else {
479 | let newRows = websites.concat(
480 | iOSApps.filter(item => websites.indexOf(item) < 0)
481 | );
482 |
483 | let newRows2 = newRows.concat(
484 | androidApps.filter(item => newRows.indexOf(item) < 0)
485 | );
486 |
487 | let newRows3 = newRows2.concat(
488 | softwareApps.filter(item => newRows2.indexOf(item) < 0)
489 | );
490 |
491 | return newRows3;
492 | }
493 | };
494 |
495 | const priceFilters = switchRows => {
496 | if (filterPrice !== "") {
497 | const newRows = [...switchRows];
498 | newRows.map(row =>
499 | eval(
500 | `${filterPrice} ${
501 | totalFilter === "=" ? "===" : totalFilter
502 | } ${row.total.slice(1, row.total.length)}`
503 | )
504 | ? row.search === false
505 | ? null
506 | : (row.search = true)
507 | : (row.search = false)
508 | );
509 | return newRows;
510 | } else {
511 | return switchRows;
512 | }
513 | };
514 |
515 | return (
516 |
517 |
518 |
529 |
530 |
536 |
545 |
546 | {stableSort(
547 | priceFilters(switchFilters()).filter(row => row.search),
548 | getSorting(order, orderBy)
549 | )
550 | .slice(
551 | props.page * rowsPerPage,
552 | props.page * rowsPerPage + rowsPerPage
553 | )
554 | .map((row, index) => {
555 | const isItemSelected = isSelected(row.name);
556 | const labelId = `enhanced-table-checkbox-${index}`;
557 |
558 | return (
559 | handleClick(event, row.name)}
562 | role="checkbox"
563 | aria-checked={isItemSelected}
564 | tabIndex={-1}
565 | key={row.name}
566 | selected={isItemSelected}
567 | >
568 |
569 |
573 |
574 |
581 | {row.name}
582 |
583 | {row.date}
584 | {row.service}
585 |
586 | {row.features}
587 |
588 | {row.complexity}
589 | {row.platforms}
590 | {row.users}
591 | {row.total}
592 |
593 | );
594 | })}
595 |
596 |
597 |
598 | row.search).length}
602 | rowsPerPage={rowsPerPage}
603 | page={props.page}
604 | onChangePage={handleChangePage}
605 | onChangeRowsPerPage={handleChangeRowsPerPage}
606 | />
607 |
608 |
609 | {filterPrice !== "" ? (
610 | {
612 | setFilterPrice("");
613 | const newRows = [...props.rows];
614 | newRows.map(row => (row.search = true));
615 | props.setRows(newRows);
616 | }}
617 | className={classes.chip}
618 | label={
619 | totalFilter === ">"
620 | ? `Less than $${filterPrice}`
621 | : totalFilter === "<"
622 | ? `Greater than $${filterPrice}`
623 | : `Equal to $${filterPrice}`
624 | }
625 | />
626 | ) : null}
627 |
628 |
629 |
630 |
631 | );
632 | }
633 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Grid from "@material-ui/core/Grid";
3 | import Typography from "@material-ui/core/Typography";
4 | import { makeStyles, useTheme } from "@material-ui/core/styles";
5 | import TextField from "@material-ui/core/TextField";
6 | import InputAdornment from "@material-ui/core/InputAdornment";
7 | import AddIcon from "@material-ui/icons/Add";
8 | import Switch from "@material-ui/core/Switch";
9 | import FormGroup from "@material-ui/core/FormGroup";
10 | import FormControlLabel from "@material-ui/core/FormControlLabel";
11 | import Table from "@material-ui/core/Table";
12 | import TableBody from "@material-ui/core/TableBody";
13 | import TableHead from "@material-ui/core/TableHead";
14 | import TableContainer from "@material-ui/core/TableContainer";
15 | import TableRow from "@material-ui/core/TableRow";
16 | import TableCell from "@material-ui/core/TableCell";
17 | import Paper from "@material-ui/core/Paper";
18 | import FilterListIcon from "@material-ui/icons/FilterList";
19 | import Dialog from "@material-ui/core/Dialog";
20 | import DialogContent from "@material-ui/core/DialogContent";
21 | import {
22 | MuiPickersUtilsProvider,
23 | KeyboardDatePicker
24 | } from "@material-ui/pickers";
25 | import DateFnsUtils from "@date-io/date-fns";
26 | import RadioGroup from "@material-ui/core/RadioGroup";
27 | import Radio from "@material-ui/core/Radio";
28 | import Select from "@material-ui/core/Select";
29 | import MenuItem from "@material-ui/core/MenuItem";
30 | import Button from "@material-ui/core/Button";
31 | import { format } from "date-fns";
32 | import EnhancedTable from "../src/ui/EnhancedTable";
33 | import useMediaQuery from "@material-ui/core/useMediaQuery";
34 | import Hidden from "@material-ui/core/Hidden";
35 |
36 | const useStyles = makeStyles(theme => ({
37 | service: {
38 | fontWeight: 300
39 | },
40 | users: {
41 | marginRight: 0
42 | },
43 | button: {
44 | color: "#fff",
45 | backgroundColor: theme.palette.common.orange,
46 | borderRadius: 50,
47 | textTransform: "none",
48 | "&:hover": {
49 | backgroundColor: theme.palette.secondary.light
50 | }
51 | }
52 | }));
53 |
54 | function createData(
55 | name,
56 | date,
57 | service,
58 | features,
59 | complexity,
60 | platforms,
61 | users,
62 | total,
63 | search
64 | ) {
65 | return {
66 | name,
67 | date,
68 | service,
69 | features,
70 | complexity,
71 | platforms,
72 | users,
73 | total,
74 | search
75 | };
76 | }
77 |
78 | export default function ProjectManager() {
79 | const classes = useStyles();
80 | const theme = useTheme();
81 | const [rows, setRows] = useState([
82 | createData(
83 | "Zachary Reece",
84 | "11/2/19",
85 | "Website",
86 | "E-Commerce",
87 | "N/A",
88 | "N/A",
89 | "N/A",
90 | "$1500",
91 | true
92 | ),
93 | createData(
94 | "Bill Gates",
95 | "10/17/19",
96 | "Custom Software",
97 | "GPS, Push Notifications, Users/Authentication, File Transfer",
98 | "Medium",
99 | "Web Application",
100 | "0-10",
101 | "$1600",
102 | true
103 | ),
104 | createData(
105 | "Steve Jobs",
106 | "2/13/19",
107 | "Custom Software",
108 | "Photo/Video, File Transfer, Users/Authentication",
109 | "Low",
110 | "Web Application",
111 | "10-100",
112 | "$1250",
113 | true
114 | ),
115 | createData(
116 | "Stan Smith",
117 | "2/13/19",
118 | "Mobile App",
119 | "Photo/Video, File Transfer, Users/Authentication",
120 | "Low",
121 | "iOS, Android",
122 | "10-100",
123 | "$1250",
124 | true
125 | ),
126 | createData(
127 | "Albert Einstein",
128 | "2/13/19",
129 | "Mobile App",
130 | "Photo/Video, File Transfer, Users/Authentication",
131 | "Low",
132 | "Android",
133 | "10-100",
134 | "$1250",
135 | true
136 | )
137 | ]);
138 |
139 | const platformOptions = ["Web", "iOS", "Android"];
140 | var featureOptions = [
141 | "Photo/Video",
142 | "GPS",
143 | "File Transfer",
144 | "Users/Authentication",
145 | "Biometrics",
146 | "Push Notifications"
147 | ];
148 | var websiteOptions = ["Basic", "Interactive", "E-Commerce"];
149 |
150 | const [websiteChecked, setWebsiteChecked] = useState(false);
151 | const [iOSChecked, setiOSChecked] = useState(false);
152 | const [androidChecked, setAndroidChecked] = useState(false);
153 | const [softwareChecked, setSoftwareChecked] = useState(false);
154 | const [dialogOpen, setDialogOpen] = useState(false);
155 | const [name, setName] = useState("");
156 | const [date, setDate] = useState(new Date());
157 | const [total, setTotal] = useState("");
158 | const [service, setService] = useState("");
159 | const [complexity, setComplexity] = useState("");
160 | const [users, setUsers] = useState("");
161 | const [platforms, setPlatforms] = useState([]);
162 | const [features, setFeatures] = useState([]);
163 | const [search, setSearch] = useState("");
164 | const [page, setPage] = React.useState(0);
165 | const matchesMD = useMediaQuery(theme.breakpoints.down("md"));
166 | const matchesSM = useMediaQuery(theme.breakpoints.down("sm"));
167 |
168 | const addProject = () => {
169 | setRows([
170 | ...rows,
171 | createData(
172 | name,
173 | format(date, "MM/dd/yy"),
174 | service,
175 | features.join(", "),
176 | service === "Website" ? "N/A" : complexity,
177 | service === "Website" ? "N/A" : platforms.join(", "),
178 | service === "Website" ? "N/A" : users,
179 | `$${total}`,
180 | true
181 | )
182 | ]);
183 | setDialogOpen(false);
184 | setName("");
185 | setDate(new Date());
186 | setTotal("");
187 | setService("");
188 | setComplexity("");
189 | setUsers("");
190 | setPlatforms([]);
191 | setFeatures([]);
192 | };
193 |
194 | const handleSearch = event => {
195 | setSearch(event.target.value);
196 |
197 | const rowData = rows.map(row =>
198 | Object.values(row).filter(option => option !== true && option !== false)
199 | );
200 |
201 | const matches = rowData.map(row =>
202 | row.map(option =>
203 | option.toLowerCase().includes(event.target.value.toLowerCase())
204 | )
205 | );
206 |
207 | const newRows = [...rows];
208 | matches.map((row, index) =>
209 | row.includes(true)
210 | ? (newRows[index].search = true)
211 | : (newRows[index].search = false)
212 | );
213 |
214 | setRows(newRows);
215 | setPage(0);
216 | };
217 |
218 | const serviceQuestions = (
219 |
220 |
221 | Service
222 |
223 |
224 | {
229 | setService(event.target.value);
230 | setFeatures([]);
231 | }}
232 | >
233 | }
238 | />
239 | }
244 | />
245 | }
250 | />
251 |
252 |
253 |
254 | );
255 |
256 | const complexityQuestions = (
257 |
258 |
264 |
265 | Complexity
266 |
267 |
268 | setComplexity(event.target.value)}
273 | >
274 | }
280 | />
281 | }
287 | />
288 | }
294 | />
295 |
296 |
297 |
298 |
299 | );
300 |
301 | const userQuestions = (
302 |
303 |
309 |
310 | Users
311 |
312 |
313 | setUsers(event.target.value)}
318 | >
319 | }
328 | />
329 | }
338 | />
339 | }
348 | />
349 |
350 |
351 |
352 |
353 | );
354 |
355 | return (
356 |
357 |
362 |
366 | Projects
367 |
368 |
369 | setDialogOpen(true)}
383 | >
384 |
385 |
386 | )
387 | }}
388 | />
389 |
390 |
394 |
395 |
400 |
401 | setWebsiteChecked(!websiteChecked)}
408 | />
409 | }
410 | label="Websites"
411 | labelPlacement={matchesSM ? "end" : "start"}
412 | />
413 |
414 |
415 | setiOSChecked(!iOSChecked)}
422 | />
423 | }
424 | label="iOS Apps"
425 | labelPlacement={matchesSM ? "end" : "start"}
426 | />
427 |
428 |
429 | setAndroidChecked(!androidChecked)}
436 | />
437 | }
438 | label="Android Apps"
439 | labelPlacement={matchesSM ? "end" : "start"}
440 | />
441 |
442 |
443 | setSoftwareChecked(!softwareChecked)}
449 | />
450 | }
451 | label="Custom Software"
452 | labelPlacement={matchesSM ? "end" : "start"}
453 | />
454 |
455 |
456 |
457 |
458 |
466 |
476 |
477 |
657 |
658 |
659 | );
660 | }
661 |
--------------------------------------------------------------------------------