From 76abd59c4739fe609d4ac90a718c556b33a5ee87 Mon Sep 17 00:00:00 2001
From: Karel Hala <khala@redhat.com>
Date: Sun, 26 Apr 2020 19:52:52 +0200
Subject: [PATCH] Add proper filtering and style pagination

---
 package.json                |   3 +
 src/components/DataTable.js | 102 ++++++++++++++++++++++++++
 src/views/HomePage.js       | 141 +++++++++++-------------------------
 yarn.lock                   |  47 +++++++++++-
 4 files changed, 193 insertions(+), 100 deletions(-)
 create mode 100644 src/components/DataTable.js

diff --git a/package.json b/package.json
index 4e76d96..5efe222 100644
--- a/package.json
+++ b/package.json
@@ -11,11 +11,14 @@
   },
   "dependencies": {
     "@auth0/auth0-spa-js": "^1.0.2",
+    "@date-io/date-fns": "1.3.13",
     "@material-ui/core": "^4.9.5",
     "@material-ui/icons": "^4.9.1",
     "@material-ui/lab": "^4.0.0-alpha.46",
+    "@material-ui/pickers": "^3.2.10",
     "axios": "^0.19.0",
     "clsx": "^1.1.0",
+    "date-fns": "^2.12.0",
     "final-form": "^4.19.1",
     "lodash": "^4.17.15",
     "react": "^16.8.6",
diff --git a/src/components/DataTable.js b/src/components/DataTable.js
new file mode 100644
index 0000000..f46a2da
--- /dev/null
+++ b/src/components/DataTable.js
@@ -0,0 +1,102 @@
+import React from "react";
+import { makeStyles } from "@material-ui/core/styles";
+import PropTypes from "prop-types";
+import Table from "@material-ui/core/Table";
+import TableBody from "@material-ui/core/TableBody";
+import TableCell from "@material-ui/core/TableCell";
+import TableContainer from "@material-ui/core/TableContainer";
+import TableHead from "@material-ui/core/TableHead";
+import TableRow from "@material-ui/core/TableRow";
+import Paper from "@material-ui/core/Paper";
+import Skeleton from "@material-ui/lab/Skeleton";
+import { useHistory } from "react-router-dom";
+
+const useStyles = makeStyles((theme) => ({
+  table: {
+    minWidth: 650,
+    overflowY: "scroll"
+  },
+  tableContainer: {
+    margin: 24
+  },
+  tableRow: {
+    "&:hover": {
+      cursor: "pointer",
+      backgroundColor: theme.palette.background.default
+    }
+  }
+}));
+
+const createNumbers = (numbers) => [...Array(31)].map((_value, index) => (numbers ? numbers[index + 1] : index + 1));
+
+function createData({ month, year, ...row }) {
+  return { month, year, sunValues: createNumbers(row) };
+}
+
+const headerTemplate = {
+  year: "Year",
+  month: "Měsíc",
+  labels: createNumbers()
+};
+
+const DataTable = ({ rows, initialized }) => {
+  const history = useHistory();
+  const classes = useStyles();
+  return (
+    <TableContainer className={classes.tableContainer} component={Paper}>
+      <Table className={classes.table} aria-label="simple table">
+        <TableHead>
+          <TableRow>
+            <TableCell>{headerTemplate.year}</TableCell>
+            <TableCell align="right">{headerTemplate.month}</TableCell>
+            {headerTemplate.labels.map((label) => (
+              <TableCell key={label}>{label}</TableCell>
+            ))}
+          </TableRow>
+        </TableHead>
+        <TableBody>
+          {initialized &&
+            rows.map((data, index) => {
+              const row = createData(data);
+              return (
+                <TableRow key={`${row.name}-${index}`} onClick={() => history.push(`/detail/${data._id}`)} className={classes.tableRow}>
+                  <TableCell component="th" scope="row">
+                    {row.year}
+                  </TableCell>
+                  <TableCell align="right">{row.month}</TableCell>
+                  {row.sunValues.map((number, index) => (
+                    <TableCell key={`${number}-${index}`}>{number}</TableCell>
+                  ))}
+                </TableRow>
+              );
+            })}
+          {!initialized &&
+            [...new Array(10)].map((_i, key) => (
+              <TableRow key={`${key}-loader`} className={classes.tableRow}>
+                <TableCell scope="row" colSpan={headerTemplate.labels.length + 1}>
+                  <Skeleton />
+                </TableCell>
+              </TableRow>
+            ))}
+        </TableBody>
+      </Table>
+    </TableContainer>
+  );
+};
+
+DataTable.propTypes = {
+  rows: PropTypes.arrayOf(
+    PropTypes.shape({
+      month: PropTypes.node,
+      year: PropTypes.node
+    })
+  ),
+  initialized: PropTypes.bool
+};
+
+DataTable.defaultProps = {
+  rows: [],
+  initialized: false
+};
+
+export default DataTable;
diff --git a/src/views/HomePage.js b/src/views/HomePage.js
index 39d0e05..b393bc4 100644
--- a/src/views/HomePage.js
+++ b/src/views/HomePage.js
@@ -1,89 +1,18 @@
 import React, { useEffect, useState } from "react";
 import Api from "../utils/api";
-
-import debounce from "lodash/debounce";
-import { makeStyles } from "@material-ui/core/styles";
-import Table from "@material-ui/core/Table";
-import TableBody from "@material-ui/core/TableBody";
-import TableCell from "@material-ui/core/TableCell";
-import TableContainer from "@material-ui/core/TableContainer";
-import TableHead from "@material-ui/core/TableHead";
-import TableRow from "@material-ui/core/TableRow";
-import Paper from "@material-ui/core/Paper";
 import Pagination from "@material-ui/lab/Pagination";
-import CircularProgress from "@material-ui/core/CircularProgress";
 import TextField from "@material-ui/core/TextField";
-import { useHistory } from "react-router-dom";
-
-const useStyles = makeStyles((theme) => ({
-  table: {
-    minWidth: 650,
-    overflowY: "scroll"
-  },
-  tableContainer: {
-    margin: 24
-  },
-  tableRow: {
-    "&:hover": {
-      cursor: "pointer",
-      backgroundColor: theme.palette.background.default
-    }
-  }
-}));
-
-const createNumbers = (numbers) => [...Array(31)].map((_value, index) => (numbers ? numbers[index + 1] : index + 1));
-
-function createData({ month, year, ...row }) {
-  return { month, year, sunValues: createNumbers(row) };
-}
-
-const headerTemplate = {
-  year: "Year",
-  month: "Měsíc",
-  labels: createNumbers()
-};
-
-const DataTable = ({ rows }) => {
-  const history = useHistory();
-  const classes = useStyles();
-  return (
-    <TableContainer className={classes.tableContainer} component={Paper}>
-      <Table className={classes.table} aria-label="simple table">
-        <TableHead>
-          <TableRow>
-            <TableCell>{headerTemplate.year}</TableCell>
-            <TableCell align="right">{headerTemplate.month}</TableCell>
-            {headerTemplate.labels.map((label) => (
-              <TableCell key={label}>{label}</TableCell>
-            ))}
-          </TableRow>
-        </TableHead>
-        <TableBody>
-          {rows.map((data, index) => {
-            const row = createData(data);
-            return (
-              <TableRow key={`${row.name}-${index}`} onClick={() => history.push(`/detail/${data._id}`)} className={classes.tableRow}>
-                <TableCell component="th" scope="row">
-                  {row.year}
-                </TableCell>
-                <TableCell align="right">{row.month}</TableCell>
-                {row.sunValues.map((number, index) => (
-                  <TableCell key={`${number}-${index}`}>{number}</TableCell>
-                ))}
-              </TableRow>
-            );
-          })}
-        </TableBody>
-      </Table>
-    </TableContainer>
-  );
-};
+import DataTable from "../components/DataTable";
+import Grid from "@material-ui/core/Grid";
+import Toolbar from "@material-ui/core/Toolbar";
+import Autocomplete from "@material-ui/lab/Autocomplete";
 
 const queryBuilder = ({ max = 15, skip = 0, filter, sortBy }) => {
-  return `max=${max}&skip=${skip}`;
+  return `q={${filter ? `"year": ${filter}` : ""}}&max=${max}&skip=${skip}`;
 };
 
 const HomePage = () => {
+  const [years, setYears] = useState([]);
   const [data, setData] = useState([]);
   const [{ max, skip, count, filterValue }, setPagination] = useState({
     max: 15,
@@ -92,41 +21,32 @@ const HomePage = () => {
     filterValue: ""
   });
   const [initialized, setInitialized] = useState(false);
-  const [filter, setFilter] = useState("");
-  const handleFilter = ({ target: { value } }) => {
-    setFilter(value);
-  };
-  const onFilter = () =>
+
+  const onFilter = (e, filter) => {
     setPagination((prevPagination) => ({
       ...prevPagination,
       filterValue: filter
     }));
+  };
 
   const handlePagination = (_event, page) => {
+    setInitialized(false);
     setPagination((prevPagination) => ({
       ...prevPagination,
       skip: (page - 1) * prevPagination.max
     }));
   };
-  const query = queryBuilder({ max, skip, filterValue });
   useEffect(() => {
     setInitialized(false);
     Promise.all([
-      Api.get(`rest/sunshine?q={}&${query}`).then((data) => setData(data)),
-      Api.get(`rest/sunshine?q={}&h={"$aggregate":["COUNT:"]}`).then((data) =>
-        setPagination((prevPagination) => ({ ...prevPagination, count: data["COUNT "] }))
-      )
+      Api.get(`rest/sunshine?${queryBuilder({ max, skip, filter: filterValue })}&totals=true`).then(({ data, totals }) => {
+        setData(data);
+        setPagination((prevPagination) => ({ ...prevPagination, count: totals.total }));
+      }),
+      Api.get(`rest/sunshine?q={}&h={"$fields": {"year": 1}}`).then((data) => setYears([...new Set(data.map(({ year }) => `${year}`))]))
     ]).then(() => setInitialized(true));
   }, [skip, filterValue]);
 
-  if (!initialized) {
-    return (
-      <div>
-        <CircularProgress />
-      </div>
-    );
-  }
-
   const addMessage = () => Api.updateData("professors.messages", { message: "Hola" }, "add");
   const replaceMessage = () => Api.updateData("professors.messages", { message: "Hola" }, "replace");
   return (
@@ -134,9 +54,34 @@ const HomePage = () => {
       <h1>Home page</h1>
       <button onClick={addMessage}>Add message</button>
       <button onClick={replaceMessage}>replace message</button>
-      <TextField onBlur={onFilter} type="number" label="Filter by year" value={filter} onChange={handleFilter} />
-      <Pagination count={Math.ceil(count / max)} page={skip / max + 1} onChange={handlePagination} />
-      <DataTable rows={data} />
+      <Toolbar>
+        <Grid container spacing={3} justify="space-between">
+          <Grid item>
+            <Autocomplete
+              style={{ minWidth: "150px" }}
+              options={years}
+              id="year-picker"
+              value={filterValue}
+              onChange={onFilter}
+              disabled={!initialized}
+              renderInput={(params) => <TextField {...params} label="Select year" margin="normal" />}
+            />
+          </Grid>
+          <Grid item>
+            <Pagination
+              style={{ marginTop: "12px" }}
+              count={Math.ceil(count / max)}
+              page={skip / max + 1}
+              onChange={handlePagination}
+              variant="outlined"
+              shape="rounded"
+              showFirstButton
+              showLastButton
+            />
+          </Grid>
+        </Grid>
+      </Toolbar>
+      <DataTable rows={data} initialized={initialized} />
     </div>
   );
 };
diff --git a/yarn.lock b/yarn.lock
index 17164db..f67f285 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -959,7 +959,7 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@babel/runtime@^7.8.3", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.6.0", "@babel/runtime@^7.8.3", "@babel/runtime@^7.9.2":
   version "7.9.2"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
   integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
@@ -1017,6 +1017,18 @@
   resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-9.0.1.tgz#c27b391d8457d1e893f1eddeaf5e5412d12ffbb5"
   integrity sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA==
 
+"@date-io/core@1.x", "@date-io/core@^1.3.13":
+  version "1.3.13"
+  resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa"
+  integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==
+
+"@date-io/date-fns@1.3.13":
+  version "1.3.13"
+  resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-1.3.13.tgz#7798844041640ab393f7e21a7769a65d672f4735"
+  integrity sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA==
+  dependencies:
+    "@date-io/core" "^1.3.13"
+
 "@emotion/hash@^0.7.4":
   version "0.7.4"
   resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.4.tgz#f14932887422c9056b15a8d222a9074a7dfa2831"
@@ -1238,6 +1250,18 @@
     prop-types "^15.7.2"
     react-is "^16.8.0"
 
+"@material-ui/pickers@^3.2.10":
+  version "3.2.10"
+  resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae"
+  integrity sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w==
+  dependencies:
+    "@babel/runtime" "^7.6.0"
+    "@date-io/core" "1.x"
+    "@types/styled-jsx" "^2.2.8"
+    clsx "^1.0.2"
+    react-transition-group "^4.0.0"
+    rifm "^0.7.0"
+
 "@material-ui/styles@^4.9.0":
   version "4.9.0"
   resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.9.0.tgz#10c31859f6868cfa9d3adf6b6c3e32c9d676bc76"
@@ -1496,6 +1520,13 @@
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
   integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
 
+"@types/styled-jsx@^2.2.8":
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234"
+  integrity sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg==
+  dependencies:
+    "@types/react" "*"
+
 "@types/yargs-parser@*":
   version "13.1.0"
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
@@ -3259,6 +3290,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
     whatwg-mimetype "^2.2.0"
     whatwg-url "^7.0.0"
 
+date-fns@^2.12.0:
+  version "2.12.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.12.0.tgz#01754c8a2f3368fc1119cf4625c3dad8c1845ee6"
+  integrity sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw==
+
 debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -8353,7 +8389,7 @@ react-scripts@3.0.1:
   optionalDependencies:
     fsevents "2.0.6"
 
-react-transition-group@^4.3.0:
+react-transition-group@^4.0.0, react-transition-group@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683"
   integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==
@@ -8697,6 +8733,13 @@ rgba-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
 
+rifm@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be"
+  integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+
 rimraf@2.6.3:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
-- 
GitLab