Initial Activity View
Add New Colors.jsx Allow Dark model Toggle from the Navbar
This commit is contained in:
parent
4fc836a34b
commit
d4c36e2057
17 changed files with 1326 additions and 310 deletions
398
package-lock.json
generated
398
package-lock.json
generated
|
@ -33,11 +33,13 @@
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-calendar": "^5.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"react-router-dom": "^6.21.1",
|
"react-router-dom": "^6.21.1",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"reactjs-social-login": "^2.6.3",
|
"reactjs-social-login": "^2.6.3",
|
||||||
|
"recharts": "^2.15.0",
|
||||||
"reusify": "^1.0.4",
|
"reusify": "^1.0.4",
|
||||||
"vite-plugin-pwa": "^0.20.0"
|
"vite-plugin-pwa": "^0.20.0"
|
||||||
},
|
},
|
||||||
|
@ -4048,6 +4050,69 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-array": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-color": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-ease": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-interpolate": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-color": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-path": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-scale": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-time": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-shape": {
|
||||||
|
"version": "3.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
|
||||||
|
"integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-path": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-time": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-timer": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
@ -4303,6 +4368,15 @@
|
||||||
"vite": "^4 || ^5 || ^6"
|
"vite": "^4 || ^5 || ^6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@wojtekmaj/date-utils": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/wojtekmaj/date-utils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@xml-tools/parser": {
|
"node_modules/@xml-tools/parser": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz",
|
||||||
|
@ -5794,6 +5868,127 @@
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-array": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "1 - 2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-ease": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-format": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-path": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2.10.0 - 3",
|
||||||
|
"d3-format": "1 - 3",
|
||||||
|
"d3-interpolate": "1.2.0 - 3",
|
||||||
|
"d3-time": "2.1.1 - 3",
|
||||||
|
"d3-time-format": "2 - 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-shape": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time-format": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dargs": {
|
"node_modules/dargs": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz",
|
||||||
|
@ -5919,6 +6114,12 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js-light": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/decode-bmp": {
|
"node_modules/decode-bmp": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/decode-bmp/-/decode-bmp-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/decode-bmp/-/decode-bmp-0.2.1.tgz",
|
||||||
|
@ -7112,6 +7313,12 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/expand-template": {
|
"node_modules/expand-template": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||||
|
@ -7135,6 +7342,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-equals": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-fifo": {
|
"node_modules/fast-fifo": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
|
||||||
|
@ -7719,6 +7935,18 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-user-locale": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-O2GWvQkhnbDoWFUJfaBlDIKUEdND8ATpBXD6KXcbhxlfktyD/d8w6mkzM/IlQEqGZAMz/PW6j6Hv53BiigKLUQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mem": "^8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/git-raw-commits": {
|
"node_modules/git-raw-commits": {
|
||||||
"version": "2.0.11",
|
"version": "2.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz",
|
||||||
|
@ -8248,6 +8476,15 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/internmap": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||||
|
@ -9204,6 +9441,18 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/map-age-cleaner": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-defer": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/map-obj": {
|
"node_modules/map-obj": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
|
||||||
|
@ -9236,6 +9485,22 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mem": {
|
||||||
|
"version": "8.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz",
|
||||||
|
"integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"map-age-cleaner": "^0.1.3",
|
||||||
|
"mimic-fn": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sindresorhus/mem?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/meow": {
|
"node_modules/meow": {
|
||||||
"version": "8.1.2",
|
"version": "8.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz",
|
||||||
|
@ -9454,6 +9719,15 @@
|
||||||
"integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==",
|
"integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/mimic-fn": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mimic-response": {
|
"node_modules/mimic-response": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||||
|
@ -9976,6 +10250,15 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-defer": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||||
|
@ -10743,6 +11026,31 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-calendar": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-09o/rQHPZGEi658IXAJtWfra1N69D1eFnuJ3FQm9qUVzlzNnos1+GWgGiUeSs22QOpNm32aoVFOimq0p3Ug9Eg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@wojtekmaj/date-utils": "^1.1.3",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"get-user-locale": "^2.2.1",
|
||||||
|
"warning": "^4.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/wojtekmaj/react-calendar?sponsor=1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
|
@ -10820,6 +11128,21 @@
|
||||||
"react-dom": ">=16.8"
|
"react-dom": ">=16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-smooth": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-equals": "^5.0.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-transition-group": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
|
@ -11041,6 +11364,44 @@
|
||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recharts": {
|
||||||
|
"version": "2.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz",
|
||||||
|
"integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"eventemitter3": "^4.0.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"react-is": "^18.3.1",
|
||||||
|
"react-smooth": "^4.0.0",
|
||||||
|
"recharts-scale": "^0.4.4",
|
||||||
|
"tiny-invariant": "^1.3.1",
|
||||||
|
"victory-vendor": "^36.6.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/recharts-scale": {
|
||||||
|
"version": "0.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
|
||||||
|
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decimal.js-light": "^2.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/recharts/node_modules/react-is": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/redent": {
|
"node_modules/redent": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||||
|
@ -12716,6 +13077,12 @@
|
||||||
"readable-stream": "3"
|
"readable-stream": "3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-invariant": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.10",
|
"version": "0.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz",
|
||||||
|
@ -13243,6 +13610,28 @@
|
||||||
"spdx-expression-parse": "^3.0.0"
|
"spdx-expression-parse": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/victory-vendor": {
|
||||||
|
"version": "36.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||||
|
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
|
||||||
|
"license": "MIT AND ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "^3.0.3",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
|
"@types/d3-interpolate": "^3.0.1",
|
||||||
|
"@types/d3-scale": "^4.0.2",
|
||||||
|
"@types/d3-shape": "^3.1.0",
|
||||||
|
"@types/d3-time": "^3.0.0",
|
||||||
|
"@types/d3-timer": "^3.0.0",
|
||||||
|
"d3-array": "^3.1.6",
|
||||||
|
"d3-ease": "^3.0.1",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-scale": "^4.0.2",
|
||||||
|
"d3-shape": "^3.1.0",
|
||||||
|
"d3-time": "^3.0.0",
|
||||||
|
"d3-timer": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.11",
|
"version": "5.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
||||||
|
@ -13355,6 +13744,15 @@
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/warning": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "donetick",
|
"name": "donetick",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.83",
|
"version": "0.1.85",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,jsx,ts,tsx}": [
|
"*.{js,jsx,ts,tsx}": [
|
||||||
|
@ -47,11 +47,13 @@
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-calendar": "^5.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-query": "^3.39.3",
|
"react-query": "^3.39.3",
|
||||||
"react-router-dom": "^6.21.1",
|
"react-router-dom": "^6.21.1",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"reactjs-social-login": "^2.6.3",
|
"reactjs-social-login": "^2.6.3",
|
||||||
|
"recharts": "^2.15.0",
|
||||||
"reusify": "^1.0.4",
|
"reusify": "^1.0.4",
|
||||||
"vite-plugin-pwa": "^0.20.0"
|
"vite-plugin-pwa": "^0.20.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Error from '@/views/Error'
|
||||||
import Settings from '@/views/Settings/Settings'
|
import Settings from '@/views/Settings/Settings'
|
||||||
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
|
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
|
||||||
import ForgotPasswordView from '../views/Authorization/ForgotPasswordView'
|
import ForgotPasswordView from '../views/Authorization/ForgotPasswordView'
|
||||||
|
import LoginSettings from '../views/Authorization/LoginSettings'
|
||||||
import LoginView from '../views/Authorization/LoginView'
|
import LoginView from '../views/Authorization/LoginView'
|
||||||
import SignupView from '../views/Authorization/Signup'
|
import SignupView from '../views/Authorization/Signup'
|
||||||
import UpdatePasswordView from '../views/Authorization/UpdatePasswordView'
|
import UpdatePasswordView from '../views/Authorization/UpdatePasswordView'
|
||||||
|
@ -21,7 +22,7 @@ import TermsView from '../views/Terms/TermsView'
|
||||||
import TestView from '../views/TestView/Test'
|
import TestView from '../views/TestView/Test'
|
||||||
import ThingsHistory from '../views/Things/ThingsHistory'
|
import ThingsHistory from '../views/Things/ThingsHistory'
|
||||||
import ThingsView from '../views/Things/ThingsView'
|
import ThingsView from '../views/Things/ThingsView'
|
||||||
import LoginSettings from '../views/Authorization/LoginSettings'
|
import UserActivities from '../views/User/UserActivities'
|
||||||
const getMainRoute = () => {
|
const getMainRoute = () => {
|
||||||
if (import.meta.env.VITE_IS_LANDING_DEFAULT === 'true') {
|
if (import.meta.env.VITE_IS_LANDING_DEFAULT === 'true') {
|
||||||
return <Landing />
|
return <Landing />
|
||||||
|
@ -66,6 +67,10 @@ const Router = createBrowserRouter([
|
||||||
path: '/my/chores',
|
path: '/my/chores',
|
||||||
element: <MyChores />,
|
element: <MyChores />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/activities',
|
||||||
|
element: <UserActivities />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
element: <LoginView />,
|
element: <LoginView />,
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { useState } from 'react'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { CreateChore, GetChoresNew } from '../utils/Fetcher'
|
import { CreateChore, GetChoresHistory, GetChoresNew } from '../utils/Fetcher'
|
||||||
|
|
||||||
export const useChores = () => {
|
export const useChores = includeArchive => {
|
||||||
return useQuery('chores', GetChoresNew)
|
return useQuery(['chores', includeArchive], () =>
|
||||||
|
GetChoresNew(includeArchive),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCreateChore = () => {
|
export const useCreateChore = () => {
|
||||||
|
@ -15,3 +18,17 @@ export const useCreateChore = () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useChoresHistory = initialLimit => {
|
||||||
|
const [limit, setLimit] = useState(initialLimit) // Initially, no limit is selected
|
||||||
|
|
||||||
|
const { data, error, isLoading } = useQuery(['choresHistory', limit], () =>
|
||||||
|
GetChoresHistory(limit),
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleLimitChange = newLimit => {
|
||||||
|
setLimit(newLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, error, isLoading, handleLimitChange }
|
||||||
|
}
|
||||||
|
|
160
src/utils/Chores.jsx
Normal file
160
src/utils/Chores.jsx
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import { TASK_COLOR } from './Colors.jsx'
|
||||||
|
|
||||||
|
export const ChoresGrouper = (groupBy, chores) => {
|
||||||
|
// sort by priority then due date:
|
||||||
|
chores.sort((a, b) => {
|
||||||
|
// no priority is lowest priority:
|
||||||
|
if (a.priority === 0) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (a.priority !== b.priority) {
|
||||||
|
return a.priority - b.priority
|
||||||
|
}
|
||||||
|
if (a.nextDueDate === null) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (b.nextDueDate === null) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return new Date(a.nextDueDate) - new Date(b.nextDueDate)
|
||||||
|
})
|
||||||
|
|
||||||
|
var groups = []
|
||||||
|
switch (groupBy) {
|
||||||
|
case 'due_date':
|
||||||
|
var groupRaw = {
|
||||||
|
Today: [],
|
||||||
|
'In a week': [],
|
||||||
|
'This month': [],
|
||||||
|
Later: [],
|
||||||
|
Overdue: [],
|
||||||
|
Anytime: [],
|
||||||
|
}
|
||||||
|
chores.forEach(chore => {
|
||||||
|
if (chore.nextDueDate === null) {
|
||||||
|
groupRaw['Anytime'].push(chore)
|
||||||
|
} else if (new Date(chore.nextDueDate) < new Date()) {
|
||||||
|
groupRaw['Overdue'].push(chore)
|
||||||
|
} else if (
|
||||||
|
new Date(chore.nextDueDate).toDateString() ===
|
||||||
|
new Date().toDateString()
|
||||||
|
) {
|
||||||
|
groupRaw['Today'].push(chore)
|
||||||
|
} else if (
|
||||||
|
new Date(chore.nextDueDate) <
|
||||||
|
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) &&
|
||||||
|
new Date(chore.nextDueDate) > new Date()
|
||||||
|
) {
|
||||||
|
groupRaw['In a week'].push(chore)
|
||||||
|
} else if (
|
||||||
|
new Date(chore.nextDueDate).getMonth() === new Date().getMonth()
|
||||||
|
) {
|
||||||
|
groupRaw['This month'].push(chore)
|
||||||
|
} else {
|
||||||
|
groupRaw['Later'].push(chore)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
groups = [
|
||||||
|
{
|
||||||
|
name: 'Overdue',
|
||||||
|
content: groupRaw['Overdue'],
|
||||||
|
color: TASK_COLOR.OVERDUE,
|
||||||
|
},
|
||||||
|
{ name: 'Today', content: groupRaw['Today'], color: TASK_COLOR.TODAY },
|
||||||
|
{
|
||||||
|
name: 'In a week',
|
||||||
|
content: groupRaw['In a week'],
|
||||||
|
color: TASK_COLOR.IN_A_WEEK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'This month',
|
||||||
|
content: groupRaw['This month'],
|
||||||
|
color: TASK_COLOR.THIS_MONTH,
|
||||||
|
},
|
||||||
|
{ name: 'Later', content: groupRaw['Later'], color: TASK_COLOR.LATER },
|
||||||
|
{
|
||||||
|
name: 'Anytime',
|
||||||
|
content: groupRaw['Anytime'],
|
||||||
|
color: TASK_COLOR.ANYTIME,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
break
|
||||||
|
case 'priority':
|
||||||
|
groupRaw = {
|
||||||
|
p1: [],
|
||||||
|
p2: [],
|
||||||
|
p3: [],
|
||||||
|
p4: [],
|
||||||
|
no_priority: [],
|
||||||
|
}
|
||||||
|
chores.forEach(chore => {
|
||||||
|
switch (chore.priority) {
|
||||||
|
case 1:
|
||||||
|
groupRaw['p1'].push(chore)
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
groupRaw['p2'].push(chore)
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
groupRaw['p3'].push(chore)
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
groupRaw['p4'].push(chore)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
groupRaw['no_priority'].push(chore)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
groups = [
|
||||||
|
{
|
||||||
|
name: 'Priority 1',
|
||||||
|
content: groupRaw['p1'],
|
||||||
|
color: TASK_COLOR.PRIORITY_1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Priority 2',
|
||||||
|
content: groupRaw['p2'],
|
||||||
|
color: TASK_COLOR.PRIORITY_2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Priority 3',
|
||||||
|
content: groupRaw['p3'],
|
||||||
|
color: TASK_COLOR.PRIORITY_3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Priority 4',
|
||||||
|
content: groupRaw['p4'],
|
||||||
|
color: TASK_COLOR.PRIORITY_4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No Priority',
|
||||||
|
content: groupRaw['no_priority'],
|
||||||
|
color: TASK_COLOR.NO_PRIORITY,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
break
|
||||||
|
case 'labels':
|
||||||
|
groupRaw = {}
|
||||||
|
var labels = {}
|
||||||
|
chores.forEach(chore => {
|
||||||
|
chore.labelsV2.forEach(label => {
|
||||||
|
labels[label.id] = label
|
||||||
|
if (groupRaw[label.id] === undefined) {
|
||||||
|
groupRaw[label.id] = []
|
||||||
|
}
|
||||||
|
groupRaw[label.id].push(chore)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
groups = Object.keys(groupRaw).map(key => {
|
||||||
|
return {
|
||||||
|
name: labels[key].name,
|
||||||
|
content: groupRaw[key],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
groups.sort((a, b) => {
|
||||||
|
a.name < b.name ? 1 : -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
|
@ -26,6 +26,60 @@ const LABEL_COLORS = [
|
||||||
{ name: 'Sand', value: '#d7ccc8' },
|
{ name: 'Sand', value: '#d7ccc8' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const COLORS = {
|
||||||
|
white: '#FFFFFF',
|
||||||
|
salmon: '#ff7961',
|
||||||
|
teal: '#26a69a',
|
||||||
|
skyBlue: '#80d8ff',
|
||||||
|
grape: '#7e57c2',
|
||||||
|
sunshine: '#ffee58',
|
||||||
|
coral: '#ff7043',
|
||||||
|
lavender: '#ce93d8',
|
||||||
|
rose: '#f48fb1',
|
||||||
|
charcoal: '#616161',
|
||||||
|
sienna: '#8d6e63',
|
||||||
|
mint: '#a7ffeb',
|
||||||
|
amber: '#ffc107',
|
||||||
|
cobalt: '#3f51b5',
|
||||||
|
emerald: '#4caf50',
|
||||||
|
peach: '#ffab91',
|
||||||
|
ocean: '#0288d1',
|
||||||
|
mustard: '#ffca28',
|
||||||
|
ruby: '#d32f2f',
|
||||||
|
periwinkle: '#b39ddb',
|
||||||
|
turquoise: '#00bcd4',
|
||||||
|
lime: '#cddc39',
|
||||||
|
blush: '#f8bbd0',
|
||||||
|
ash: '#90a4ae',
|
||||||
|
sand: '#d7ccc8',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TASK_COLOR = {
|
||||||
|
COMPLETED: '#4ec1a2',
|
||||||
|
LATE: '#f6ad55',
|
||||||
|
MISSED: '#F03A47',
|
||||||
|
UPCOMING: '#AF5B5B',
|
||||||
|
SKIPPED: '#E2C2FF',
|
||||||
|
|
||||||
|
// For the calendar
|
||||||
|
OVERDUE: '#F03A47',
|
||||||
|
TODAY: '#ffc107',
|
||||||
|
IN_A_WEEK: '#4ec1a2',
|
||||||
|
THIS_MONTH: '#00bcd4',
|
||||||
|
LATER: '#d7ccc8',
|
||||||
|
ANYTIME: '#90a4ae',
|
||||||
|
|
||||||
|
// FOR ASSIGNEE:
|
||||||
|
ASSIGNED_TO_ME: '#4ec1a2',
|
||||||
|
ASSIGNED_TO_OTHER: '#b39ddb',
|
||||||
|
|
||||||
|
// FOR PRIORITY:
|
||||||
|
PRIORITY_1: '#F03A47',
|
||||||
|
PRIORITY_2: '#ffc107',
|
||||||
|
PRIORITY_3: '#00bcd4',
|
||||||
|
PRIORITY_4: '#7e57c2',
|
||||||
|
NO_PRIORITY: '#90a4ae',
|
||||||
|
}
|
||||||
export default LABEL_COLORS
|
export default LABEL_COLORS
|
||||||
|
|
||||||
export const getTextColorFromBackgroundColor = bgColor => {
|
export const getTextColorFromBackgroundColor = bgColor => {
|
|
@ -1,9 +1,5 @@
|
||||||
import { API_URL } from '../Config'
|
|
||||||
import { Fetch, HEADERS, apiManager } from './TokenManager'
|
import { Fetch, HEADERS, apiManager } from './TokenManager'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const createChore = userID => {
|
const createChore = userID => {
|
||||||
return Fetch(`/chores/`, {
|
return Fetch(`/chores/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -33,7 +29,7 @@ const UpdatePassword = newPassword => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const login = (username, password) => {
|
const login = (username, password) => {
|
||||||
const baseURL = apiManager.getApiURL();
|
const baseURL = apiManager.getApiURL()
|
||||||
return fetch(`${baseURL}/auth/login`, {
|
return fetch(`${baseURL}/auth/login`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@ -49,8 +45,13 @@ const GetAllUsers = () => {
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const GetChoresNew = async () => {
|
const GetChoresNew = async includeArchived => {
|
||||||
const resp = await Fetch(`/chores/`, {
|
var url = `/chores/`
|
||||||
|
if (includeArchived) {
|
||||||
|
url += `?includeArchived=true`
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await Fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
})
|
})
|
||||||
|
@ -95,7 +96,7 @@ const GetChoreDetailById = id => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const MarkChoreComplete = (id, note, completedDate, performer) => {
|
const MarkChoreComplete = (id, note, completedDate, performer) => {
|
||||||
var markChoreURL =`/chores/${id}/do`
|
var markChoreURL = `/chores/${id}/do`
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
note,
|
note,
|
||||||
|
@ -109,15 +110,13 @@ const MarkChoreComplete = (id, note, completedDate, performer) => {
|
||||||
}
|
}
|
||||||
if (performer) {
|
if (performer) {
|
||||||
body.performer = Number(performer)
|
body.performer = Number(performer)
|
||||||
if(completedDateFormated === ''){
|
if (completedDateFormated === '') {
|
||||||
markChoreURL += `&performer=${performer}`
|
markChoreURL += `&performer=${performer}`
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
markChoreURL += `?performer=${performer}`
|
markChoreURL += `?performer=${performer}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Fetch(markChoreURL, {
|
return Fetch(markChoreURL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
|
@ -244,13 +243,10 @@ const LeaveCircle = id => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteCircleMember = (circleID, memberID) => {
|
const DeleteCircleMember = (circleID, memberID) => {
|
||||||
return Fetch(
|
return Fetch(`/circles/${circleID}/members/delete?member_id=${memberID}`, {
|
||||||
`/circles/${circleID}/members/delete?member_id=${memberID}`,
|
method: 'DELETE',
|
||||||
{
|
headers: HEADERS(),
|
||||||
method: 'DELETE',
|
})
|
||||||
headers: HEADERS(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateUserDetails = userDetails => {
|
const UpdateUserDetails = userDetails => {
|
||||||
|
@ -345,13 +341,12 @@ const GetLongLiveTokens = () => {
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const PutNotificationTarget = ( platform, deviceToken) => {
|
const PutNotificationTarget = (platform, deviceToken) => {
|
||||||
return Fetch(`/users/targets`, {
|
return Fetch(`/users/targets`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
body: JSON.stringify({ platform,deviceToken }),
|
body: JSON.stringify({ platform, deviceToken }),
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const CreateLabel = label => {
|
const CreateLabel = label => {
|
||||||
return Fetch(`/labels`, {
|
return Fetch(`/labels`, {
|
||||||
|
@ -383,22 +378,19 @@ const DeleteLabel = id => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChangePassword = (verifiticationCode, password) => {
|
const ChangePassword = (verifiticationCode, password) => {
|
||||||
const baseURL = apiManager.getApiURL();
|
const baseURL = apiManager.getApiURL()
|
||||||
return fetch(
|
return fetch(`${baseURL}/auth/password?c=${verifiticationCode}`, {
|
||||||
`${baseURL}/auth/password?c=${verifiticationCode}`,
|
method: 'POST',
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ password: password }),
|
|
||||||
},
|
},
|
||||||
)
|
body: JSON.stringify({ password: password }),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResetPassword = email => {
|
const ResetPassword = email => {
|
||||||
const basedURL = apiManager.getApiURL();
|
const basedURL = apiManager.getApiURL()
|
||||||
return fetch(`${basedURL}/auth/reset`, {
|
return fetch(`${basedURL}/auth/reset`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -422,24 +414,34 @@ const UpdateDueDate = (id, dueDate) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RefreshToken = () => {
|
const RefreshToken = () => {
|
||||||
const basedURL = apiManager.getApiURL();
|
const basedURL = apiManager.getApiURL()
|
||||||
return fetch(basedURL + '/auth/refresh', {
|
return fetch(basedURL + '/auth/refresh', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const GetChoresHistory = async limit => {
|
||||||
|
var url = `/chores/history`
|
||||||
|
if (limit) {
|
||||||
|
url += `?limit=${limit}`
|
||||||
|
}
|
||||||
|
const resp = await Fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: HEADERS(),
|
||||||
|
})
|
||||||
|
return resp.json()
|
||||||
|
}
|
||||||
export {
|
export {
|
||||||
AcceptCircleMemberRequest,
|
AcceptCircleMemberRequest,
|
||||||
ArchiveChore,
|
ArchiveChore,
|
||||||
CancelSubscription,
|
CancelSubscription,
|
||||||
createChore,
|
ChangePassword,
|
||||||
CreateChore,
|
CreateChore,
|
||||||
CreateLabel,
|
CreateLabel,
|
||||||
CreateLongLiveToken,
|
CreateLongLiveToken,
|
||||||
CreateThing,
|
CreateThing,
|
||||||
DeleteChore,
|
DeleteChore,
|
||||||
DeleteChoreHistory,
|
DeleteChoreHistory,
|
||||||
ChangePassword,
|
|
||||||
DeleteCircleMember,
|
DeleteCircleMember,
|
||||||
DeleteLabel,
|
DeleteLabel,
|
||||||
DeleteLongLiveToken,
|
DeleteLongLiveToken,
|
||||||
|
@ -451,6 +453,7 @@ export {
|
||||||
GetChoreDetailById,
|
GetChoreDetailById,
|
||||||
GetChoreHistory,
|
GetChoreHistory,
|
||||||
GetChores,
|
GetChores,
|
||||||
|
GetChoresHistory,
|
||||||
GetChoresNew,
|
GetChoresNew,
|
||||||
GetCircleMemberRequests,
|
GetCircleMemberRequests,
|
||||||
GetLabels,
|
GetLabels,
|
||||||
|
@ -462,14 +465,12 @@ export {
|
||||||
GetUserProfile,
|
GetUserProfile,
|
||||||
JoinCircle,
|
JoinCircle,
|
||||||
LeaveCircle,
|
LeaveCircle,
|
||||||
login,
|
|
||||||
MarkChoreComplete,
|
MarkChoreComplete,
|
||||||
RefreshToken,
|
|
||||||
ResetPassword,
|
|
||||||
PutNotificationTarget,
|
PutNotificationTarget,
|
||||||
|
RefreshToken,
|
||||||
|
ResetPassword,
|
||||||
SaveChore,
|
SaveChore,
|
||||||
SaveThing,
|
SaveThing,
|
||||||
signUp,
|
|
||||||
SkipChore,
|
SkipChore,
|
||||||
UnArchiveChore,
|
UnArchiveChore,
|
||||||
UpdateChoreAssignee,
|
UpdateChoreAssignee,
|
||||||
|
@ -481,4 +482,7 @@ export {
|
||||||
UpdatePassword,
|
UpdatePassword,
|
||||||
UpdateThingState,
|
UpdateThingState,
|
||||||
UpdateUserDetails,
|
UpdateUserDetails,
|
||||||
|
createChore,
|
||||||
|
login,
|
||||||
|
signUp,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Divider,
|
Divider,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
|
FormLabel,
|
||||||
Input,
|
Input,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
|
@ -20,12 +21,14 @@ import {
|
||||||
Sheet,
|
Sheet,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
Stack,
|
Stack,
|
||||||
|
Switch,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useContext, useEffect, useState } from 'react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { UserContext } from '../../contexts/UserContext'
|
||||||
|
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
||||||
import {
|
import {
|
||||||
CreateChore,
|
CreateChore,
|
||||||
DeleteChore,
|
DeleteChore,
|
||||||
|
@ -36,7 +39,6 @@ import {
|
||||||
SaveChore,
|
SaveChore,
|
||||||
} from '../../utils/Fetcher'
|
} from '../../utils/Fetcher'
|
||||||
import { isPlusAccount } from '../../utils/Helpers'
|
import { isPlusAccount } from '../../utils/Helpers'
|
||||||
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
|
|
||||||
import { useLabels } from '../Labels/LabelQueries'
|
import { useLabels } from '../Labels/LabelQueries'
|
||||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||||
import LabelModal from '../Modals/Inputs/LabelModal'
|
import LabelModal from '../Modals/Inputs/LabelModal'
|
||||||
|
@ -72,6 +74,7 @@ const ChoreEdit = () => {
|
||||||
const [frequencyMetadata, setFrequencyMetadata] = useState({})
|
const [frequencyMetadata, setFrequencyMetadata] = useState({})
|
||||||
const [labels, setLabels] = useState([])
|
const [labels, setLabels] = useState([])
|
||||||
const [labelsV2, setLabelsV2] = useState([])
|
const [labelsV2, setLabelsV2] = useState([])
|
||||||
|
const [points, setPoints] = useState(-1)
|
||||||
const [allUserThings, setAllUserThings] = useState([])
|
const [allUserThings, setAllUserThings] = useState([])
|
||||||
const [thingTrigger, setThingTrigger] = useState(null)
|
const [thingTrigger, setThingTrigger] = useState(null)
|
||||||
const [isThingValid, setIsThingValid] = useState(false)
|
const [isThingValid, setIsThingValid] = useState(false)
|
||||||
|
@ -196,6 +199,7 @@ const ChoreEdit = () => {
|
||||||
labelsV2: labelsV2,
|
labelsV2: labelsV2,
|
||||||
notificationMetadata: notificationMetadata,
|
notificationMetadata: notificationMetadata,
|
||||||
thingTrigger: thingTrigger,
|
thingTrigger: thingTrigger,
|
||||||
|
points: points < 0 ? null : points,
|
||||||
}
|
}
|
||||||
let SaveFunction = CreateChore
|
let SaveFunction = CreateChore
|
||||||
if (choreId > 0) {
|
if (choreId > 0) {
|
||||||
|
@ -247,6 +251,9 @@ const ChoreEdit = () => {
|
||||||
setFrequency(data.res.frequency)
|
setFrequency(data.res.frequency)
|
||||||
|
|
||||||
setNotificationMetadata(JSON.parse(data.res.notificationMetadata))
|
setNotificationMetadata(JSON.parse(data.res.notificationMetadata))
|
||||||
|
setPoints(
|
||||||
|
data.res.points && data.res.points > -1 ? data.res.points : -1,
|
||||||
|
)
|
||||||
// setLabels(data.res.labels ? data.res.labels.split(',') : [])
|
// setLabels(data.res.labels ? data.res.labels.split(',') : [])
|
||||||
|
|
||||||
setLabelsV2(data.res.labelsV2)
|
setLabelsV2(data.res.labelsV2)
|
||||||
|
@ -758,23 +765,6 @@ const ChoreEdit = () => {
|
||||||
<Typography level='h5'>
|
<Typography level='h5'>
|
||||||
Things to remember about this chore or to tag it
|
Things to remember about this chore or to tag it
|
||||||
</Typography>
|
</Typography>
|
||||||
{/* <FreeSoloCreateOption
|
|
||||||
options={[...labels, 'test']}
|
|
||||||
selected={labels}
|
|
||||||
onSelectChange={changes => {
|
|
||||||
const newLabels = []
|
|
||||||
changes.map(change => {
|
|
||||||
// if type is string :
|
|
||||||
if (typeof change === 'string') {
|
|
||||||
// add the change to the labels array:
|
|
||||||
newLabels.push(change)
|
|
||||||
} else {
|
|
||||||
newLabels.push(change.inputValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setLabels(newLabels)
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
onChange={(event, newValue) => {
|
onChange={(event, newValue) => {
|
||||||
|
@ -839,37 +829,80 @@ const ChoreEdit = () => {
|
||||||
Add New Label
|
Add New Label
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{/* <Card>
|
|
||||||
<List
|
|
||||||
orientation='horizontal'
|
|
||||||
wrap
|
|
||||||
sx={{
|
|
||||||
'--List-gap': '8px',
|
|
||||||
'--ListItem-radius': '20px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{labels?.map((label, index) => (
|
|
||||||
<ListItem key={label}>
|
|
||||||
<Chip
|
|
||||||
onClick={() => {
|
|
||||||
setLabels(labels.filter(l => l !== label))
|
|
||||||
}}
|
|
||||||
checked={true}
|
|
||||||
overlay
|
|
||||||
variant='soft'
|
|
||||||
color='primary'
|
|
||||||
size='lg'
|
|
||||||
endDecorator={<Cancel />}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Chip>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</Card> */}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box mt={2}>
|
||||||
|
<Typography level='h4' gutterBottom>
|
||||||
|
Others :
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<FormControl
|
||||||
|
orientation='horizontal'
|
||||||
|
sx={{ width: 400, justifyContent: 'space-between' }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<FormLabel>Assign Points</FormLabel>
|
||||||
|
<FormHelperText sx={{ mt: 0 }}>
|
||||||
|
Assign points to this task and user will earn points when they
|
||||||
|
completed it
|
||||||
|
</FormHelperText>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={points > -1}
|
||||||
|
onClick={event => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (points > -1) {
|
||||||
|
setPoints(-1)
|
||||||
|
} else {
|
||||||
|
setPoints(1)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color={points !== -1 ? 'success' : 'neutral'}
|
||||||
|
variant={points !== -1 ? 'solid' : 'outlined'}
|
||||||
|
// endDecorator={points !== -1 ? 'On' : 'Off'}
|
||||||
|
slotProps={{
|
||||||
|
endDecorator: {
|
||||||
|
sx: {
|
||||||
|
minWidth: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{points != -1 && (
|
||||||
|
<Card variant='outlined'>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: 0,
|
||||||
|
ml: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography level='body-sm'>Points:</Typography>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
value={points}
|
||||||
|
sx={{ maxWidth: 100 }}
|
||||||
|
// add min points is 0 and max is 1000
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
min: 0,
|
||||||
|
max: 1000,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
placeholder='Points'
|
||||||
|
onChange={e => {
|
||||||
|
setPoints(parseInt(e.target.value))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{choreId > 0 && (
|
{choreId > 0 && (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mt: 3 }}>
|
||||||
<Sheet
|
<Sheet
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
|
@ -900,6 +933,7 @@ const ChoreEdit = () => {
|
||||||
</Sheet>
|
</Sheet>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Divider sx={{ mb: 9 }} />
|
<Divider sx={{ mb: 9 }} />
|
||||||
|
|
||||||
{/* <Box mt={2} alignSelf={'flex-start'} display='flex' gap={2}>
|
{/* <Box mt={2} alignSelf={'flex-start'} display='flex' gap={2}>
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { Divider } from '@mui/material'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
|
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
||||||
import {
|
import {
|
||||||
GetAllUsers,
|
GetAllUsers,
|
||||||
GetChoreDetailById,
|
GetChoreDetailById,
|
||||||
|
@ -43,7 +44,6 @@ import {
|
||||||
SkipChore,
|
SkipChore,
|
||||||
UpdateChorePriority,
|
UpdateChorePriority,
|
||||||
} from '../../utils/Fetcher'
|
} from '../../utils/Fetcher'
|
||||||
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
|
|
||||||
import Priorities from '../../utils/Priorities'
|
import Priorities from '../../utils/Priorities'
|
||||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||||
const IconCard = styled('div')({
|
const IconCard = styled('div')({
|
||||||
|
|
|
@ -42,8 +42,8 @@ import {
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { API_URL } from '../../Config'
|
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { UserContext } from '../../contexts/UserContext'
|
||||||
|
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
||||||
import {
|
import {
|
||||||
ArchiveChore,
|
ArchiveChore,
|
||||||
DeleteChore,
|
DeleteChore,
|
||||||
|
@ -53,9 +53,7 @@ import {
|
||||||
UpdateChoreAssignee,
|
UpdateChoreAssignee,
|
||||||
UpdateDueDate,
|
UpdateDueDate,
|
||||||
} from '../../utils/Fetcher'
|
} from '../../utils/Fetcher'
|
||||||
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
|
|
||||||
import Priorities from '../../utils/Priorities'
|
import Priorities from '../../utils/Priorities'
|
||||||
import { Fetch } from '../../utils/TokenManager'
|
|
||||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||||
import DateModal from '../Modals/Inputs/DateModal'
|
import DateModal from '../Modals/Inputs/DateModal'
|
||||||
import SelectModal from '../Modals/Inputs/SelectModal'
|
import SelectModal from '../Modals/Inputs/SelectModal'
|
||||||
|
@ -130,8 +128,7 @@ const ChoreCard = ({
|
||||||
message: 'Are you sure you want to delete this chore?',
|
message: 'Are you sure you want to delete this chore?',
|
||||||
onClose: isConfirmed => {
|
onClose: isConfirmed => {
|
||||||
if (isConfirmed === true) {
|
if (isConfirmed === true) {
|
||||||
DeleteChore(chore.id)
|
DeleteChore(chore.id).then(response => {
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
onChoreRemove(chore)
|
onChoreRemove(chore)
|
||||||
}
|
}
|
||||||
|
@ -181,7 +178,7 @@ const ChoreCard = ({
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
const id = setTimeout(() => {
|
const id = setTimeout(() => {
|
||||||
MarkChoreComplete(chore.id, null, null,null)
|
MarkChoreComplete(chore.id, null, null, null)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
return resp.json().then(data => {
|
return resp.json().then(data => {
|
||||||
|
@ -221,9 +218,13 @@ const ChoreCard = ({
|
||||||
alert('Please select a performer')
|
alert('Please select a performer')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkChoreComplete(chore.id, null, new Date(newDate).toISOString(), null)
|
MarkChoreComplete(
|
||||||
.then(response => {
|
chore.id,
|
||||||
|
null,
|
||||||
|
new Date(newDate).toISOString(),
|
||||||
|
null,
|
||||||
|
).then(response => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
response.json().then(data => {
|
response.json().then(data => {
|
||||||
const newChore = data.res
|
const newChore = data.res
|
||||||
|
@ -243,9 +244,7 @@ const ChoreCard = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const handleCompleteWithNote = note => {
|
const handleCompleteWithNote = note => {
|
||||||
|
MarkChoreComplete(chore.id, note, null, null).then(response => {
|
||||||
MarkChoreComplete(chore.id, note, null, null)
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
response.json().then(data => {
|
response.json().then(data => {
|
||||||
const newChore = data.res
|
const newChore = data.res
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Chip, Menu, MenuItem, Typography } from '@mui/joy'
|
import { Chip, Menu, MenuItem, Typography } from '@mui/joy'
|
||||||
import IconButton from '@mui/joy/IconButton'
|
import IconButton from '@mui/joy/IconButton'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
|
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
||||||
|
|
||||||
const IconButtonWithMenu = ({
|
const IconButtonWithMenu = ({
|
||||||
key,
|
key,
|
||||||
|
|
|
@ -43,7 +43,11 @@ import { useLabels } from '../Labels/LabelQueries'
|
||||||
import ChoreCard from './ChoreCard'
|
import ChoreCard from './ChoreCard'
|
||||||
import IconButtonWithMenu from './IconButtonWithMenu'
|
import IconButtonWithMenu from './IconButtonWithMenu'
|
||||||
|
|
||||||
import { canScheduleNotification, scheduleChoreNotification } from './LocalNotificationScheduler'
|
import { ChoresGrouper } from '../../utils/Chores'
|
||||||
|
import {
|
||||||
|
canScheduleNotification,
|
||||||
|
scheduleChoreNotification,
|
||||||
|
} from './LocalNotificationScheduler'
|
||||||
import NotificationAccessSnackbar from './NotificationAccessSnackbar'
|
import NotificationAccessSnackbar from './NotificationAccessSnackbar'
|
||||||
|
|
||||||
const MyChores = () => {
|
const MyChores = () => {
|
||||||
|
@ -92,159 +96,40 @@ const MyChores = () => {
|
||||||
return aDueDate - bDueDate // Sort ascending by due date
|
return aDueDate - bDueDate // Sort ascending by due date
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionSorter = (t, chores) => {
|
|
||||||
// sort by priority then due date:
|
|
||||||
chores.sort((a, b) => {
|
|
||||||
// no priority is lowest priority:
|
|
||||||
if (a.priority === 0) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if (a.priority !== b.priority) {
|
|
||||||
return a.priority - b.priority
|
|
||||||
}
|
|
||||||
if (a.nextDueDate === null) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if (b.nextDueDate === null) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return new Date(a.nextDueDate) - new Date(b.nextDueDate)
|
|
||||||
})
|
|
||||||
|
|
||||||
var groups = []
|
|
||||||
switch (t) {
|
|
||||||
case 'due_date':
|
|
||||||
var groupRaw = {
|
|
||||||
Today: [],
|
|
||||||
'In a week': [],
|
|
||||||
'This month': [],
|
|
||||||
Later: [],
|
|
||||||
Overdue: [],
|
|
||||||
Anytime: [],
|
|
||||||
}
|
|
||||||
chores.forEach(chore => {
|
|
||||||
if (chore.nextDueDate === null) {
|
|
||||||
groupRaw['Anytime'].push(chore)
|
|
||||||
} else if (new Date(chore.nextDueDate) < new Date()) {
|
|
||||||
groupRaw['Overdue'].push(chore)
|
|
||||||
} else if (
|
|
||||||
new Date(chore.nextDueDate).toDateString() ===
|
|
||||||
new Date().toDateString()
|
|
||||||
) {
|
|
||||||
groupRaw['Today'].push(chore)
|
|
||||||
} else if (
|
|
||||||
new Date(chore.nextDueDate) <
|
|
||||||
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) &&
|
|
||||||
new Date(chore.nextDueDate) > new Date()
|
|
||||||
) {
|
|
||||||
groupRaw['In a week'].push(chore)
|
|
||||||
} else if (
|
|
||||||
new Date(chore.nextDueDate).getMonth() === new Date().getMonth()
|
|
||||||
) {
|
|
||||||
groupRaw['This month'].push(chore)
|
|
||||||
} else {
|
|
||||||
groupRaw['Later'].push(chore)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
groups = [
|
|
||||||
{ name: 'Overdue', content: groupRaw['Overdue'] },
|
|
||||||
{ name: 'Today', content: groupRaw['Today'] },
|
|
||||||
{ name: 'In a week', content: groupRaw['In a week'] },
|
|
||||||
{ name: 'This month', content: groupRaw['This month'] },
|
|
||||||
{ name: 'Later', content: groupRaw['Later'] },
|
|
||||||
{ name: 'Anytime', content: groupRaw['Anytime'] },
|
|
||||||
]
|
|
||||||
break
|
|
||||||
case 'priority':
|
|
||||||
groupRaw = {
|
|
||||||
p1: [],
|
|
||||||
p2: [],
|
|
||||||
p3: [],
|
|
||||||
p4: [],
|
|
||||||
no_priority: [],
|
|
||||||
}
|
|
||||||
chores.forEach(chore => {
|
|
||||||
switch (chore.priority) {
|
|
||||||
case 1:
|
|
||||||
groupRaw['p1'].push(chore)
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
groupRaw['p2'].push(chore)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
groupRaw['p3'].push(chore)
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
groupRaw['p4'].push(chore)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
groupRaw['no_priority'].push(chore)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
groups = [
|
|
||||||
{ name: 'Priority 1', content: groupRaw['p1'] },
|
|
||||||
{ name: 'Priority 2', content: groupRaw['p2'] },
|
|
||||||
{ name: 'Priority 3', content: groupRaw['p3'] },
|
|
||||||
{ name: 'Priority 4', content: groupRaw['p4'] },
|
|
||||||
{ name: 'No Priority', content: groupRaw['no_priority'] },
|
|
||||||
]
|
|
||||||
break
|
|
||||||
case 'labels':
|
|
||||||
groupRaw = {}
|
|
||||||
var labels = {}
|
|
||||||
chores.forEach(chore => {
|
|
||||||
chore.labelsV2.forEach(label => {
|
|
||||||
labels[label.id] = label
|
|
||||||
if (groupRaw[label.id] === undefined) {
|
|
||||||
groupRaw[label.id] = []
|
|
||||||
}
|
|
||||||
groupRaw[label.id].push(chore)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
groups = Object.keys(groupRaw).map(key => {
|
|
||||||
return {
|
|
||||||
name: labels[key].name,
|
|
||||||
content: groupRaw[key],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
groups.sort((a, b) => {
|
|
||||||
a.name < b.name ? 1 : -1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
Promise.all([GetChores(), GetAllUsers(), GetUserProfile()]).then(
|
||||||
|
responses => {
|
||||||
|
const [choresResponse, usersResponse, userProfileResponse] = responses
|
||||||
Promise.all([GetChores(), GetAllUsers(),GetUserProfile()]).then(responses => {
|
if (!choresResponse.ok) {
|
||||||
const [choresResponse, usersResponse, userProfileResponse] = responses;
|
throw new Error(choresResponse.statusText)
|
||||||
if (!choresResponse.ok) {
|
|
||||||
throw new Error(choresResponse.statusText);
|
|
||||||
}
|
|
||||||
if (!usersResponse.ok) {
|
|
||||||
throw new Error(usersResponse.statusText);
|
|
||||||
}
|
|
||||||
if (!userProfileResponse.ok) {
|
|
||||||
throw new Error(userProfileResponse.statusText);
|
|
||||||
}
|
}
|
||||||
Promise.all([choresResponse.json(), usersResponse.json(), userProfileResponse.json()]).then(data => {
|
if (!usersResponse.ok) {
|
||||||
const [choresData, usersData, userProfileData] = data;
|
throw new Error(usersResponse.statusText)
|
||||||
setUserProfile(userProfileData.res);
|
}
|
||||||
choresData.res.sort(choreSorter);
|
if (!userProfileResponse.ok) {
|
||||||
setChores(choresData.res);
|
throw new Error(userProfileResponse.statusText)
|
||||||
setFilteredChores(choresData.res);
|
}
|
||||||
setPerformers(usersData.res);
|
Promise.all([
|
||||||
|
choresResponse.json(),
|
||||||
|
usersResponse.json(),
|
||||||
|
userProfileResponse.json(),
|
||||||
|
]).then(data => {
|
||||||
|
const [choresData, usersData, userProfileData] = data
|
||||||
|
setUserProfile(userProfileData.res)
|
||||||
|
choresData.res.sort(choreSorter)
|
||||||
|
setChores(choresData.res)
|
||||||
|
setFilteredChores(choresData.res)
|
||||||
|
setPerformers(usersData.res)
|
||||||
if (canScheduleNotification()) {
|
if (canScheduleNotification()) {
|
||||||
scheduleChoreNotification(choresData.res, userProfileData.res, usersData.res);
|
scheduleChoreNotification(
|
||||||
|
choresData.res,
|
||||||
|
userProfileData.res,
|
||||||
|
usersData.res,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// GetAllUsers()
|
// GetAllUsers()
|
||||||
// .then(response => response.json())
|
// .then(response => response.json())
|
||||||
|
@ -266,7 +151,7 @@ const MyChores = () => {
|
||||||
const sortedChores = choresData.res.sort(choreSorter)
|
const sortedChores = choresData.res.sort(choreSorter)
|
||||||
setChores(sortedChores)
|
setChores(sortedChores)
|
||||||
setFilteredChores(sortedChores)
|
setFilteredChores(sortedChores)
|
||||||
const sections = sectionSorter('due_date', sortedChores)
|
const sections = ChoresGrouper('due_date', sortedChores)
|
||||||
setChoreSections(sections)
|
setChoreSections(sections)
|
||||||
setOpenChoreSections(
|
setOpenChoreSections(
|
||||||
Object.keys(sections).reduce((acc, key) => {
|
Object.keys(sections).reduce((acc, key) => {
|
||||||
|
@ -354,7 +239,7 @@ const MyChores = () => {
|
||||||
}
|
}
|
||||||
setChores(newChores)
|
setChores(newChores)
|
||||||
setFilteredChores(newFilteredChores)
|
setFilteredChores(newFilteredChores)
|
||||||
setChoreSections(sectionSorter('due_date', newChores))
|
setChoreSections(ChoresGrouper('due_date', newChores))
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'completed':
|
case 'completed':
|
||||||
|
@ -385,7 +270,7 @@ const MyChores = () => {
|
||||||
)
|
)
|
||||||
setChores(newChores)
|
setChores(newChores)
|
||||||
setFilteredChores(newFilteredChores)
|
setFilteredChores(newFilteredChores)
|
||||||
setChoreSections(sectionSorter('due_date', newChores))
|
setChoreSections(ChoresGrouper('due_date', newChores))
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchOptions = {
|
const searchOptions = {
|
||||||
|
@ -419,13 +304,13 @@ const MyChores = () => {
|
||||||
setSearchTerm(term)
|
setSearchTerm(term)
|
||||||
setFilteredChores(fuse.search(term).map(result => result.item))
|
setFilteredChores(fuse.search(term).map(result => result.item))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
userProfile === null ||
|
userProfile === null ||
|
||||||
userLabelsLoading ||
|
userLabelsLoading ||
|
||||||
performers.length === 0 ||
|
performers.length === 0 ||
|
||||||
choresLoading
|
choresLoading
|
||||||
) {
|
) {
|
||||||
return <LoadingComponent />
|
return <LoadingComponent />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,7 +465,7 @@ const MyChores = () => {
|
||||||
]}
|
]}
|
||||||
selectedItem={selectedChoreSection}
|
selectedItem={selectedChoreSection}
|
||||||
onItemSelect={selected => {
|
onItemSelect={selected => {
|
||||||
const section = sectionSorter(selected.value, chores)
|
const section = ChoresGrouper(selected.value, chores)
|
||||||
setChoreSections(section)
|
setChoreSections(section)
|
||||||
setSelectedChoreSection(selected.value)
|
setSelectedChoreSection(selected.value)
|
||||||
setFilteredChores(chores)
|
setFilteredChores(chores)
|
||||||
|
|
|
@ -11,6 +11,49 @@ import {
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
|
export const getCompletedChip = historyEntry => {
|
||||||
|
var text = 'No Due Date'
|
||||||
|
var color = 'info'
|
||||||
|
var icon = <CalendarViewDay />
|
||||||
|
// if completed few hours +-6 hours
|
||||||
|
if (
|
||||||
|
historyEntry.dueDate &&
|
||||||
|
historyEntry.completedAt > historyEntry.dueDate - 1000 * 60 * 60 * 6 &&
|
||||||
|
historyEntry.completedAt < historyEntry.dueDate + 1000 * 60 * 60 * 6
|
||||||
|
) {
|
||||||
|
text = 'On Time'
|
||||||
|
color = 'success'
|
||||||
|
icon = <Check />
|
||||||
|
} else if (
|
||||||
|
historyEntry.dueDate &&
|
||||||
|
historyEntry.completedAt < historyEntry.dueDate
|
||||||
|
) {
|
||||||
|
text = 'On Time'
|
||||||
|
color = 'success'
|
||||||
|
icon = <Check />
|
||||||
|
}
|
||||||
|
|
||||||
|
// if completed after due date then it's late
|
||||||
|
else if (
|
||||||
|
historyEntry.dueDate &&
|
||||||
|
historyEntry.completedAt > historyEntry.dueDate
|
||||||
|
) {
|
||||||
|
text = 'Late'
|
||||||
|
color = 'warning'
|
||||||
|
icon = <Timelapse />
|
||||||
|
} else {
|
||||||
|
text = 'No Due Date'
|
||||||
|
color = 'neutral'
|
||||||
|
icon = <CalendarViewDay />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chip startDecorator={icon} color={color}>
|
||||||
|
{text}
|
||||||
|
</Chip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const HistoryCard = ({
|
const HistoryCard = ({
|
||||||
allHistory,
|
allHistory,
|
||||||
performers,
|
performers,
|
||||||
|
@ -38,49 +81,6 @@ const HistoryCard = ({
|
||||||
return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
|
return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCompletedChip = historyEntry => {
|
|
||||||
var text = 'No Due Date'
|
|
||||||
var color = 'info'
|
|
||||||
var icon = <CalendarViewDay />
|
|
||||||
// if completed few hours +-6 hours
|
|
||||||
if (
|
|
||||||
historyEntry.dueDate &&
|
|
||||||
historyEntry.completedAt > historyEntry.dueDate - 1000 * 60 * 60 * 6 &&
|
|
||||||
historyEntry.completedAt < historyEntry.dueDate + 1000 * 60 * 60 * 6
|
|
||||||
) {
|
|
||||||
text = 'On Time'
|
|
||||||
color = 'success'
|
|
||||||
icon = <Check />
|
|
||||||
} else if (
|
|
||||||
historyEntry.dueDate &&
|
|
||||||
historyEntry.completedAt < historyEntry.dueDate
|
|
||||||
) {
|
|
||||||
text = 'On Time'
|
|
||||||
color = 'success'
|
|
||||||
icon = <Check />
|
|
||||||
}
|
|
||||||
|
|
||||||
// if completed after due date then it's late
|
|
||||||
else if (
|
|
||||||
historyEntry.dueDate &&
|
|
||||||
historyEntry.completedAt > historyEntry.dueDate
|
|
||||||
) {
|
|
||||||
text = 'Late'
|
|
||||||
color = 'warning'
|
|
||||||
icon = <Timelapse />
|
|
||||||
} else {
|
|
||||||
text = 'No Due Date'
|
|
||||||
color = 'neutral'
|
|
||||||
icon = <CalendarViewDay />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Chip startDecorator={icon} color={color}>
|
|
||||||
{text}
|
|
||||||
</Chip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }} onClick={onClick}>
|
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }} onClick={onClick}>
|
||||||
|
|
|
@ -12,8 +12,8 @@ import {
|
||||||
|
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
import LABEL_COLORS from '../../../utils/Colors.jsx'
|
||||||
import { CreateLabel, UpdateLabel } from '../../../utils/Fetcher'
|
import { CreateLabel, UpdateLabel } from '../../../utils/Fetcher'
|
||||||
import LABEL_COLORS from '../../../utils/LabelColors'
|
|
||||||
import { useLabels } from '../../Labels/LabelQueries'
|
import { useLabels } from '../../Labels/LabelQueries'
|
||||||
|
|
||||||
function LabelModal({ isOpen, onClose, onSave, label }) {
|
function LabelModal({ isOpen, onClose, onSave, label }) {
|
||||||
|
|
51
src/views/Settings/ThemeToggleButton.jsx
Normal file
51
src/views/Settings/ThemeToggleButton.jsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import useStickyState from '@/hooks/useStickyState'
|
||||||
|
import {
|
||||||
|
BrightnessAuto,
|
||||||
|
DarkModeOutlined,
|
||||||
|
LightModeOutlined,
|
||||||
|
} from '@mui/icons-material'
|
||||||
|
import { FormControl, IconButton, useColorScheme } from '@mui/joy'
|
||||||
|
|
||||||
|
const ELEMENTID = 'select-theme-mode'
|
||||||
|
|
||||||
|
const ThemeToggleButton = ({ sx }) => {
|
||||||
|
const { mode, setMode } = useColorScheme()
|
||||||
|
const [themeMode, setThemeMode] = useStickyState(mode, 'themeMode')
|
||||||
|
|
||||||
|
const handleThemeModeChange = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
let newThemeMode
|
||||||
|
switch (themeMode) {
|
||||||
|
case 'light':
|
||||||
|
newThemeMode = 'dark'
|
||||||
|
break
|
||||||
|
case 'dark':
|
||||||
|
newThemeMode = 'system'
|
||||||
|
break
|
||||||
|
case 'system':
|
||||||
|
default:
|
||||||
|
newThemeMode = 'light'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
setThemeMode(newThemeMode)
|
||||||
|
setMode(newThemeMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl sx={sx}>
|
||||||
|
<IconButton onClick={handleThemeModeChange}>
|
||||||
|
{themeMode === 'light' ? (
|
||||||
|
<DarkModeOutlined />
|
||||||
|
) : themeMode === 'dark' ? (
|
||||||
|
<BrightnessAuto />
|
||||||
|
) : (
|
||||||
|
<LightModeOutlined />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</FormControl>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ThemeToggleButton
|
395
src/views/User/UserActivities.jsx
Normal file
395
src/views/User/UserActivities.jsx
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel'
|
||||||
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
|
||||||
|
import CircleIcon from '@mui/icons-material/Circle'
|
||||||
|
import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts'
|
||||||
|
|
||||||
|
import { Toll } from '@mui/icons-material'
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
Chip,
|
||||||
|
Container,
|
||||||
|
Divider,
|
||||||
|
Grid,
|
||||||
|
Stack,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
Tabs,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/joy'
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import { UserContext } from '../../contexts/UserContext'
|
||||||
|
import { useChores, useChoresHistory } from '../../queries/ChoreQueries'
|
||||||
|
import { ChoresGrouper } from '../../utils/Chores'
|
||||||
|
import { TASK_COLOR } from '../../utils/Colors.jsx'
|
||||||
|
import LoadingComponent from '../components/Loading'
|
||||||
|
|
||||||
|
const groupByDate = history => {
|
||||||
|
const aggregated = {}
|
||||||
|
for (let i = 0; i < history.length; i++) {
|
||||||
|
const item = history[i]
|
||||||
|
const date = new Date(item.completedAt).toLocaleDateString()
|
||||||
|
if (!aggregated[date]) {
|
||||||
|
aggregated[date] = []
|
||||||
|
}
|
||||||
|
aggregated[date].push(item)
|
||||||
|
}
|
||||||
|
return aggregated
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChoreHistoryItem = ({ time, name, points, status }) => {
|
||||||
|
const statusIcon = {
|
||||||
|
completed: <CheckCircleIcon color='success' />,
|
||||||
|
missed: <CancelIcon color='error' />,
|
||||||
|
pending: <CircleIcon color='neutral' />,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack direction='row' alignItems='center' spacing={2}>
|
||||||
|
<Typography level='body-md' sx={{ minWidth: 80 }}>
|
||||||
|
{time}
|
||||||
|
</Typography>
|
||||||
|
<Box>
|
||||||
|
{statusIcon[status] ? statusIcon[status] : statusIcon['completed']}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
minHeight: 40,
|
||||||
|
// center vertically:
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
maxWidth: '50vw',
|
||||||
|
}}
|
||||||
|
level='body-md'
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
{points && (
|
||||||
|
<Chip size='sm' color='success' startDecorator={<Toll />}>
|
||||||
|
{`${points} points`}
|
||||||
|
</Chip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChoreHistoryTimeline = ({ history }) => {
|
||||||
|
const groupedHistory = groupByDate(history)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container sx={{ p: 2 }}>
|
||||||
|
<Typography level='h4' sx={{ mb: 2 }}>
|
||||||
|
Activities Timeline
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{Object.entries(groupedHistory).map(([date, items]) => (
|
||||||
|
<Box key={date} sx={{ mb: 4 }}>
|
||||||
|
<Typography level='title-sm' sx={{ mb: 0.5 }}>
|
||||||
|
{new Date(date).toLocaleDateString([], {
|
||||||
|
weekday: 'long',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
<Divider />
|
||||||
|
<Stack spacing={1}>
|
||||||
|
{items.map(record => (
|
||||||
|
<>
|
||||||
|
<ChoreHistoryItem
|
||||||
|
key={record.id}
|
||||||
|
time={new Date(record.completedAt).toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})}
|
||||||
|
name={record.choreName}
|
||||||
|
points={record.points}
|
||||||
|
status={record.status}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderPieChart = (data, size, isPrimary) => (
|
||||||
|
<PieChart width={size} height={size}>
|
||||||
|
<Pie
|
||||||
|
data={data}
|
||||||
|
dataKey='value'
|
||||||
|
nameKey='label'
|
||||||
|
cx='50%'
|
||||||
|
cy='50%'
|
||||||
|
innerRadius={isPrimary ? size / 4 : size / 6}
|
||||||
|
paddingAngle={5}
|
||||||
|
cornerRadius={5}
|
||||||
|
>
|
||||||
|
{data.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
{isPrimary && <Tooltip />}
|
||||||
|
{isPrimary && (
|
||||||
|
<Legend
|
||||||
|
layout='horizontal'
|
||||||
|
verticalAlign='bottom'
|
||||||
|
align='center'
|
||||||
|
// format as : {entry.payload.label}: {value}
|
||||||
|
iconType='circle'
|
||||||
|
formatter={(label, value) => `${label}: ${value.payload.value}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PieChart>
|
||||||
|
)
|
||||||
|
|
||||||
|
const UserActivites = () => {
|
||||||
|
const { userProfile } = React.useContext(UserContext)
|
||||||
|
const [tabValue, setTabValue] = React.useState(30)
|
||||||
|
const [selectedHistory, setSelectedHistory] = React.useState([])
|
||||||
|
const [selectedChart, setSelectedChart] = React.useState('history')
|
||||||
|
|
||||||
|
const [historyPieChartData, setHistoryPieChartData] = React.useState([])
|
||||||
|
const [choreDuePieChartData, setChoreDuePieChartData] = React.useState([])
|
||||||
|
const [choresAssignedChartData, setChoresAssignedChartData] = React.useState(
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
const [choresPriorityChartData, setChoresPriorityChartData] = React.useState(
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
const { data: choresData, isLoading: isChoresLoading } = useChores(true)
|
||||||
|
const {
|
||||||
|
data: choresHistory,
|
||||||
|
isChoresHistoryLoading,
|
||||||
|
handleLimitChange: refetchHistory,
|
||||||
|
} = useChoresHistory(tabValue ? tabValue : 30)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isChoresHistoryLoading && !isChoresLoading && choresHistory) {
|
||||||
|
const enrichedHistory = choresHistory.res.map(item => {
|
||||||
|
const chore = choresData.res.find(chore => chore.id === item.choreId)
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
choreName: chore?.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setSelectedHistory(enrichedHistory)
|
||||||
|
setHistoryPieChartData(generateHistoryPieChartData(enrichedHistory))
|
||||||
|
}
|
||||||
|
}, [isChoresHistoryLoading, isChoresLoading, choresHistory])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isChoresLoading && choresData) {
|
||||||
|
const choreDuePieChartData = generateChoreDuePieChartData(choresData.res)
|
||||||
|
setChoreDuePieChartData(choreDuePieChartData)
|
||||||
|
setChoresAssignedChartData(generateChoreAssignedChartData(choresData.res))
|
||||||
|
setChoresPriorityChartData(
|
||||||
|
generateChorePriorityPieChartData(choresData.res),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [isChoresLoading, choresData])
|
||||||
|
|
||||||
|
const generateChoreAssignedChartData = chores => {
|
||||||
|
var assignedToMe = 0
|
||||||
|
var assignedToOthers = 0
|
||||||
|
chores.forEach(chore => {
|
||||||
|
if (chore.assignedTo === userProfile.id) {
|
||||||
|
assignedToMe++
|
||||||
|
} else assignedToOthers++
|
||||||
|
})
|
||||||
|
|
||||||
|
const group = []
|
||||||
|
if (assignedToMe > 0) {
|
||||||
|
group.push({
|
||||||
|
label: `Assigned to me`,
|
||||||
|
value: assignedToMe,
|
||||||
|
color: TASK_COLOR.ASSIGNED_TO_ME,
|
||||||
|
id: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (assignedToOthers > 0) {
|
||||||
|
group.push({
|
||||||
|
label: `Assigned to others`,
|
||||||
|
value: assignedToOthers,
|
||||||
|
color: TASK_COLOR.ASSIGNED_TO_OTHERS,
|
||||||
|
id: 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateChoreDuePieChartData = chores => {
|
||||||
|
const groups = ChoresGrouper('due_date', chores)
|
||||||
|
return groups
|
||||||
|
.map(group => {
|
||||||
|
return {
|
||||||
|
label: group.name,
|
||||||
|
value: group.content.length,
|
||||||
|
color: group.color,
|
||||||
|
id: group.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(item => item.value > 0)
|
||||||
|
}
|
||||||
|
const generateChorePriorityPieChartData = chores => {
|
||||||
|
const groups = ChoresGrouper('priority', chores)
|
||||||
|
return groups
|
||||||
|
.map(group => {
|
||||||
|
return {
|
||||||
|
label: group.name,
|
||||||
|
value: group.content.length,
|
||||||
|
color: group.color,
|
||||||
|
id: group.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(item => item.value > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateHistoryPieChartData = history => {
|
||||||
|
const totalCompleted = history.filter(
|
||||||
|
item => item.dueDate > item.completedAt,
|
||||||
|
).length
|
||||||
|
const totalLate = history.filter(
|
||||||
|
item => item.dueDate < item.completedAt,
|
||||||
|
).length
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: `On time`,
|
||||||
|
value: totalCompleted,
|
||||||
|
color: '#4ec1a2',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Late`,
|
||||||
|
value: totalLate,
|
||||||
|
color: '#f6ad55',
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (isChoresHistoryLoading || isChoresLoading) {
|
||||||
|
return <LoadingComponent />
|
||||||
|
}
|
||||||
|
const COLORS = historyPieChartData.map(item => item.color)
|
||||||
|
const chartData = {
|
||||||
|
history: {
|
||||||
|
data: historyPieChartData,
|
||||||
|
title: 'Status',
|
||||||
|
description: 'Completed tasks status',
|
||||||
|
},
|
||||||
|
due: {
|
||||||
|
data: choreDuePieChartData,
|
||||||
|
title: 'Due Date',
|
||||||
|
description: 'Current tasks due date',
|
||||||
|
},
|
||||||
|
assigned: {
|
||||||
|
data: choresAssignedChartData,
|
||||||
|
title: 'Assignee',
|
||||||
|
description: 'Tasks assigned to you vs others',
|
||||||
|
},
|
||||||
|
priority: {
|
||||||
|
data: choresPriorityChartData,
|
||||||
|
title: 'Priority',
|
||||||
|
description: 'Tasks by priority',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
maxWidth='md'
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
onChange={(e, tabValue) => {
|
||||||
|
setTabValue(tabValue)
|
||||||
|
refetchHistory(tabValue)
|
||||||
|
}}
|
||||||
|
defaultValue={7}
|
||||||
|
sx={{
|
||||||
|
py: 0.5,
|
||||||
|
borderRadius: 16,
|
||||||
|
maxWidth: 400,
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TabList
|
||||||
|
disableUnderline
|
||||||
|
sx={{
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
boxShadow: 1,
|
||||||
|
justifyContent: 'space-evenly',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ label: '7 Days', value: 7 },
|
||||||
|
{ label: '30 Days', value: 30 },
|
||||||
|
{ label: '90 Days', value: 90 },
|
||||||
|
].map((tab, index) => (
|
||||||
|
<Tab
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
borderRadius: 16,
|
||||||
|
color: 'text.secondary',
|
||||||
|
'&.Mui-selected': {
|
||||||
|
color: 'text.primary',
|
||||||
|
backgroundColor: 'primary.light',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
disableIndicator
|
||||||
|
value={tab.value}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</TabList>
|
||||||
|
</Tabs>
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography level='h4' textAlign='center'>
|
||||||
|
{chartData[selectedChart].title}
|
||||||
|
</Typography>
|
||||||
|
<Typography level='body-xs' textAlign='center'>
|
||||||
|
{chartData[selectedChart].description}
|
||||||
|
</Typography>
|
||||||
|
{renderPieChart(chartData[selectedChart].data, 250, true)}
|
||||||
|
</Box>
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
{Object.entries(chartData)
|
||||||
|
.filter(([key]) => key !== selectedChart)
|
||||||
|
.map(([key, { data, title }]) => (
|
||||||
|
<Grid item key={key} xs={4}>
|
||||||
|
<Card
|
||||||
|
onClick={() => setSelectedChart(key)}
|
||||||
|
sx={{ cursor: 'pointer', p: 1 }}
|
||||||
|
>
|
||||||
|
<Typography textAlign='center' level='body-xs' mb={-2}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
{renderPieChart(data, 75, false)}
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<ChoreHistoryTimeline history={selectedHistory} />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserActivites
|
|
@ -1,6 +1,7 @@
|
||||||
import Logo from '@/assets/logo.svg'
|
import Logo from '@/assets/logo.svg'
|
||||||
import {
|
import {
|
||||||
AccountBox,
|
AccountBox,
|
||||||
|
History,
|
||||||
HomeOutlined,
|
HomeOutlined,
|
||||||
ListAlt,
|
ListAlt,
|
||||||
Logout,
|
Logout,
|
||||||
|
@ -23,6 +24,7 @@ import {
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { version } from '../../../package.json'
|
import { version } from '../../../package.json'
|
||||||
|
import ThemeToggleButton from '../Settings/ThemeToggleButton'
|
||||||
import NavBarLink from './NavBarLink'
|
import NavBarLink from './NavBarLink'
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
|
@ -41,6 +43,11 @@ const links = [
|
||||||
label: 'Things',
|
label: 'Things',
|
||||||
icon: <Widgets />,
|
icon: <Widgets />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
to: 'activities',
|
||||||
|
label: 'Activities',
|
||||||
|
icon: <History />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
to: 'labels',
|
to: 'labels',
|
||||||
label: 'Labels',
|
label: 'Labels',
|
||||||
|
@ -120,7 +127,12 @@ const NavBar = () => {
|
||||||
tick✓
|
tick✓
|
||||||
</span>
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<ThemeToggleButton
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Drawer
|
<Drawer
|
||||||
open={drawerOpen}
|
open={drawerOpen}
|
||||||
|
|
Loading…
Add table
Reference in a new issue