From 5344bf8f46f3eed4ef342b76032e6e9ecaf3558d Mon Sep 17 00:00:00 2001 From: Nicholas Zuber Date: Mon, 1 Apr 2019 19:38:34 -0400 Subject: [PATCH] Graph --- package-lock.json | 149 +++++++++++++++++++ package.json | 1 + src/pages/Notifications/index.js | 12 +- src/pages/Notifications/redesign/Scene.js | 173 ++++++++++++++++++++-- src/styles/index.css | 5 + 5 files changed, 326 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 179a782..11e6c69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3939,6 +3939,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js-light": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.0.tgz", + "integrity": "sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg==" + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -4170,6 +4175,29 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.2.tgz", + "integrity": "sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + } + } + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -8177,6 +8205,11 @@ "lodash._reinterpolate": "~3.0.0" } }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -8258,6 +8291,11 @@ "object-visit": "^1.0.0" } }, + "math-expression-evaluator": { + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", + "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=" + }, "math-random": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", @@ -12181,6 +12219,28 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-resize-detector": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-2.3.0.tgz", + "integrity": "sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ==", + "requires": { + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "prop-types": "^15.6.0", + "resize-observer-polyfill": "^1.5.0" + } + }, + "react-smooth": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-1.0.2.tgz", + "integrity": "sha512-pIGzL1g9VGAsRsdZQokIK0vrCkcdKtnOnS1gyB2rrowdLy69lNSWoIjCTWAfgbiYvria8tm5hEZqj+jwXMkV4A==", + "requires": { + "lodash": "~4.17.4", + "prop-types": "^15.6.0", + "raf": "^3.4.0", + "react-transition-group": "^2.5.0" + } + }, "react-spring": { "version": "8.0.18", "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.18.tgz", @@ -12213,6 +12273,17 @@ "prop-types": "^15.5.8" } }, + "react-transition-group": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.7.1.tgz", + "integrity": "sha512-b0VJTzNRnXxRpCuxng6QJbAzmmrhBn1BZJfPPnHbH2PIo8msdkajqwtfdyGm/OypPXZNfAHKEqeN15wjMXrRJQ==", + "requires": { + "dom-helpers": "^3.3.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -12567,6 +12638,47 @@ "util.promisify": "^1.0.0" } }, + "recharts": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-1.5.0.tgz", + "integrity": "sha512-bsKJRvh4NepPJlXoI4L9oRKhrv59W7fjs6qTqk6le9MFAywe8EYiW/t07RDQHBVIj6icb+UdSFsHxEo8c5AcTg==", + "requires": { + "classnames": "~2.2.5", + "core-js": "~2.5.1", + "d3-interpolate": "~1.3.0", + "d3-scale": "~2.1.0", + "d3-shape": "~1.2.0", + "lodash": "~4.17.5", + "prop-types": "~15.6.0", + "react-resize-detector": "~2.3.0", + "react-smooth": "~1.0.0", + "recharts-scale": "^0.4.2", + "reduce-css-calc": "~1.3.0" + }, + "dependencies": { + "d3-scale": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.1.2.tgz", + "integrity": "sha512-bESpd64ylaKzCDzvULcmHKZTlzA/6DGSVwx7QSDj/EnX9cpSevsdiwdHFYI9ouo9tNBbV3v5xztHS2uFeOzh8Q==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + } + } + }, + "recharts-scale": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.2.tgz", + "integrity": "sha512-p/cKt7j17D1CImLgX2f5+6IXLbRHGUQkogIp06VUoci/XkhOQiGSzUrsD1uRmiI7jha4u8XNFOjkHkzzBPivMg==", + "requires": { + "decimal.js-light": "^2.4.1" + } + }, "recompose": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", @@ -12588,6 +12700,38 @@ "minimatch": "3.0.4" } }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + } + } + }, + "reduce-function-call": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", + "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "requires": { + "balanced-match": "^0.4.2" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + } + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -12805,6 +12949,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", diff --git a/package.json b/package.json index 2a799c5..9435a58 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "react-emotion": "^9.2.12", "react-spring": "^8.0.18", "react-svg-inline": "^2.1.1", + "recharts": "^1.5.0", "recompose": "^0.30.0", "resolve": "1.8.1", "sass-loader": "7.1.0", diff --git a/src/pages/Notifications/index.js b/src/pages/Notifications/index.js index 3289a05..f2f70c6 100644 --- a/src/pages/Notifications/index.js +++ b/src/pages/Notifications/index.js @@ -473,7 +473,14 @@ class NotificationsPage extends React.Component { lastNumbered = 0; } + const todayLastWeek = this.state.currentTime.clone().subtract(1, 'week'); + const stagedTodayCount = this.props.storageApi.getStat('stagedCount')[0]; + const stagedTodayLastWeekCount = this.props.storageApi.getStat( + 'stagedCount', + todayLastWeek, + todayLastWeek.clone().add(1, 'day') + )[0]; const stagedStatistics = this.props.storageApi.getStat( 'stagedCount', this.state.currentTime.clone().startOf('week').subtract(1, 'week'), @@ -483,14 +490,15 @@ class NotificationsPage extends React.Component { return ( o) { + return (n - o) / o * 100; + } else { + return (o - n) / o * 100; + } +} + +function prettify (n) { + if (Math.floor(n) === n) { + return n.toString(); + } else { + return n.toFixed(1); + } +} + // ======================================================================== // END OF 'MOVE TO A UTILS FILE' // ======================================================================== @@ -194,9 +221,10 @@ const CardSection = styled('div')` `; const Card = styled('div')` + position: relative; width: 250px; padding: 20px 24px; - height: 100px; + min-height: 100px; margin: 32px auto 0; background: ${WHITE}; border: 1px solid #E5E6EB; @@ -205,9 +233,22 @@ const Card = styled('div')` `; const CardTitle = styled(Title)` + line-height: 1.5em; font-size: 1.5em; `; +const CardSubTitle = styled(CardTitle)` + font-size: 1.2em; + color: #9d9b97; +`; + +const ScoreDiff = styled(CardTitle)` + position: absolute; + top: 30px; + right: 24px; + color: ${props => props.under ? '#ef055f' : '#457cff'}; +`; + const IconContainer = styled('div')` position: relative; height: 72px; @@ -223,7 +264,7 @@ const IconContainer = styled('div')` content: ""; position: absolute; width: 3px; - background: ${props => !props.noBorder && props.selected ? props.primary : WHITE}; + background: ${props => !props.noBorder && props.selected ? props.primary : 'transparent'}; right: 0; top: 4px; bottom: 4px; @@ -270,7 +311,7 @@ const SubTitleSection = styled('div')` margin: 8px 0; font-weight: 500; font-size: 1rem; - color: #19223dab; + color: #9d9b97; } `; @@ -715,6 +756,97 @@ function SortingItem ({children, selected, onChange, descending, setDescending, ); } +function CustomTick ({x, y, stroke, payload}) { + if (!payload) return null; + return ( + + + {payload.value.substring(0, 2)} + + + ); +} + +function ReadCountGraph ({data}) { + return ( + + } + tickFormatter={tick => tick.substring(0, 2)} + /> + { + switch (name) { + case 'cur': + return [value, 'This week']; + case 'prev': + return [value, 'Last week']; + default: + return [value, name]; + } + }} + /> + + + + ); +} + export default function Scene ({ notifications, notificationsPermission, @@ -747,20 +879,30 @@ export default function Scene ({ setNotificationsPermission, onStageThread, onArchiveThread, + readStatistics, + readTodayCount, + readTodayLastWeekCount, }) { const [menuOpen, setMenuOpen] = React.useState(false); const [dropdownOpen, setDropdownOpen] = React.useState(false); const [mode, setMode] = React.useState(Mode.ALL); const hasNotificationsOn = notificationsPermission === 'granted'; - // @TODO move to index file - if (sort === Sort.TITLE) { - if (descending) { - notifications.sort((a, b) => a.name.localeCompare(b.name)); - } else { - notifications.sort((a, b) => b.name.localeCompare(a.name)); - } - } + readStatistics = readStatistics.map(n => parseInt(n, 10)); + const lastWeekStats = readStatistics.slice(0, 7).map(n => n || null); + const thisWeekStats = readStatistics.slice(7).map(n => n || null); + + const percentageDeltaToday = getPercentageDelta(readTodayCount, readTodayLastWeekCount); + + const data = [ + {name: 'Sunday', cur: thisWeekStats[0], prev: lastWeekStats[0]}, + {name: 'Monday', cur: thisWeekStats[1], prev: lastWeekStats[1]}, + {name: 'Tuesday', cur: thisWeekStats[2], prev: lastWeekStats[2]}, + {name: 'Wednesday', cur: thisWeekStats[3], prev: lastWeekStats[3]}, + {name: 'Thursday', cur: thisWeekStats[4], prev: lastWeekStats[4]}, + {name: 'Friday', cur: thisWeekStats[5], prev: lastWeekStats[5]}, + {name: 'Saturday', cur: thisWeekStats[6], prev: lastWeekStats[6]}, + ]; // const notificationTransitions = useTransition(notifications, item => item.id, { // from: {background: 'green', opacity: 0, transform: 'translate3d(50px, 0, 0)'}, @@ -881,7 +1023,14 @@ export default function Scene ({ - {currentTime.format('dddd, MMMM Do')} + {currentTime.format('dddd')} + {currentTime.format('MMMM Do, YYYY')} + readTodayCount}> + {readTodayLastWeekCount > readTodayCount ? '-' : '+'} + {prettify(percentageDeltaToday)} + {'%'} + + diff --git a/src/styles/index.css b/src/styles/index.css index 9498c9e..5e3f685 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -90,6 +90,11 @@ div { vertical-align: top; } +line[stroke="#666"] { + stroke-width: 2; + stroke: #e5e7ea; +} + .react-tooltip { z-index: 999999; pointer-events: none;