Compare commits
53 commits
sse-featur
...
master
Author | SHA1 | Date | |
---|---|---|---|
920bccb45e | |||
ab2b508e98 | |||
aa706bfcaf | |||
9cfba93f65 | |||
b60f33b454 | |||
e82a083c86 | |||
3544f44a74 | |||
84e6cb106c | |||
47032055b6 | |||
46194ab4fe | |||
a18f1b0fb2 | |||
|
9145059e46 | ||
|
935eab8d97 | ||
b9cfb52098 | |||
12c8ceb5e1 | |||
5977940a39 | |||
7af0422937 | |||
7a6854bfc7 | |||
206ef4c503 | |||
6a96ef55c0 | |||
98d19e5e0b | |||
edc54207d1 | |||
dc8d0803b2 | |||
0433ef1c28 | |||
cee0139e06 | |||
d435d5db04 | |||
2770f0e8d8 | |||
1f4bc947b4 | |||
97e0bd4c43 | |||
706d3a9641 | |||
cb14c24d58 | |||
167a002f95 | |||
7b71848e52 | |||
c17c6cfa02 | |||
18c4991c52 | |||
9d4d08d0a5 | |||
7c422d58af | |||
2cba0279e6 | |||
db6dc225cf | |||
20a1378e03 | |||
e9154924f8 | |||
8c884779bf | |||
2525b3c805 | |||
978fa507bd | |||
d9cd7ae327 | |||
cac93173fc | |||
e3e3651c56 | |||
e178ba4ac9 | |||
a0ef6e7dff | |||
8e189a9e7c | |||
2556a424a1 | |||
6e179766a8 | |||
2e2074ea1e |
41 changed files with 1671 additions and 239 deletions
19
.prettierrc
Normal file
19
.prettierrc
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"useTabs": false,
|
||||
"vueIndentScriptAndStyle": false
|
||||
}
|
420
package-lock.json
generated
420
package-lock.json
generated
|
@ -1855,9 +1855,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ssri": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz",
|
||||
"integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.1.tgz",
|
||||
"integrity": "sha512-w+daCzXN89PseTL99MkA+fxJEcU3wfaE/ah0i0lnOlpG1CYLJ2ZjzEry68YBKfLs4JfoTShrTEsJkAZuNZ/stw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1",
|
||||
|
@ -2585,11 +2585,18 @@
|
|||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
"follow-redirects": "^1.14.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"follow-redirects": {
|
||||
"version": "1.14.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
|
||||
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-eslint": {
|
||||
|
@ -2979,16 +2986,30 @@
|
|||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.16.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz",
|
||||
"integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==",
|
||||
"version": "4.16.6",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
|
||||
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001165",
|
||||
"colorette": "^1.2.1",
|
||||
"electron-to-chromium": "^1.3.621",
|
||||
"caniuse-lite": "^1.0.30001219",
|
||||
"colorette": "^1.2.2",
|
||||
"electron-to-chromium": "^1.3.723",
|
||||
"escalade": "^3.1.1",
|
||||
"node-releases": "^1.1.67"
|
||||
"node-releases": "^1.1.71"
|
||||
},
|
||||
"dependencies": {
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001245",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001245.tgz",
|
||||
"integrity": "sha512-768fM9j1PKXpOCKws6eTo3RHmvTUsG9UrpT4WoREFeZgJBTi4/X9g565azS/rVUGtqb8nt7FjLeF5u4kukERnA==",
|
||||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
|
||||
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
|
@ -3555,9 +3576,9 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz",
|
||||
"integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz",
|
||||
"integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
|
@ -4138,15 +4159,72 @@
|
|||
"dev": true
|
||||
},
|
||||
"cssnano": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz",
|
||||
"integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
|
||||
"integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cosmiconfig": "^5.0.0",
|
||||
"cssnano-preset-default": "^4.0.7",
|
||||
"cssnano-preset-default": "^4.0.8",
|
||||
"is-resolvable": "^1.0.0",
|
||||
"postcss": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano-preset-default": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz",
|
||||
"integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css-declaration-sorter": "^4.0.1",
|
||||
"cssnano-util-raw-cache": "^4.0.1",
|
||||
"postcss": "^7.0.0",
|
||||
"postcss-calc": "^7.0.1",
|
||||
"postcss-colormin": "^4.0.3",
|
||||
"postcss-convert-values": "^4.0.1",
|
||||
"postcss-discard-comments": "^4.0.2",
|
||||
"postcss-discard-duplicates": "^4.0.2",
|
||||
"postcss-discard-empty": "^4.0.1",
|
||||
"postcss-discard-overridden": "^4.0.1",
|
||||
"postcss-merge-longhand": "^4.0.11",
|
||||
"postcss-merge-rules": "^4.0.3",
|
||||
"postcss-minify-font-values": "^4.0.2",
|
||||
"postcss-minify-gradients": "^4.0.2",
|
||||
"postcss-minify-params": "^4.0.2",
|
||||
"postcss-minify-selectors": "^4.0.2",
|
||||
"postcss-normalize-charset": "^4.0.1",
|
||||
"postcss-normalize-display-values": "^4.0.2",
|
||||
"postcss-normalize-positions": "^4.0.2",
|
||||
"postcss-normalize-repeat-style": "^4.0.2",
|
||||
"postcss-normalize-string": "^4.0.2",
|
||||
"postcss-normalize-timing-functions": "^4.0.2",
|
||||
"postcss-normalize-unicode": "^4.0.1",
|
||||
"postcss-normalize-url": "^4.0.1",
|
||||
"postcss-normalize-whitespace": "^4.0.2",
|
||||
"postcss-ordered-values": "^4.1.2",
|
||||
"postcss-reduce-initial": "^4.0.3",
|
||||
"postcss-reduce-transforms": "^4.0.2",
|
||||
"postcss-svgo": "^4.0.3",
|
||||
"postcss-unique-selectors": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"postcss-svgo": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz",
|
||||
"integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss": "^7.0.0",
|
||||
"postcss-value-parser": "^3.0.0",
|
||||
"svgo": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cssnano-preset-default": {
|
||||
|
@ -4617,9 +4695,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"dns-packet": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
|
||||
"integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
|
||||
"integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ip": "^1.1.0",
|
||||
|
@ -4684,12 +4762,20 @@
|
|||
"dev": true
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz",
|
||||
"integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "1"
|
||||
"domelementtype": "^2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"domelementtype": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
|
||||
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
|
@ -4770,30 +4856,30 @@
|
|||
"dev": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.633",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.633.tgz",
|
||||
"integrity": "sha512-bsVCsONiVX1abkWdH7KtpuDAhsQ3N3bjPYhROSAXE78roJKet0Y5wznA14JE9pzbwSZmSMAW6KiKYf1RvbTJkA==",
|
||||
"version": "1.3.775",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.775.tgz",
|
||||
"integrity": "sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
@ -5726,7 +5812,8 @@
|
|||
"follow-redirects": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
|
||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==",
|
||||
"dev": true
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
|
@ -5981,9 +6068,9 @@
|
|||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
|
@ -6223,9 +6310,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"hpack.js": {
|
||||
|
@ -6351,34 +6438,43 @@
|
|||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
|
||||
"integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^1.3.1",
|
||||
"domhandler": "^2.3.0",
|
||||
"domutils": "^1.5.1",
|
||||
"entities": "^1.1.1",
|
||||
"inherits": "^2.0.1",
|
||||
"readable-stream": "^3.1.1"
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.0.0",
|
||||
"domutils": "^2.5.2",
|
||||
"entities": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dom-serializer": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
|
||||
"integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.2.0",
|
||||
"entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
|
||||
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
|
||||
"dev": true
|
||||
},
|
||||
"domutils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
|
||||
"integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-serializer": "^1.0.1",
|
||||
"domelementtype": "^2.2.0",
|
||||
"domhandler": "^4.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7416,9 +7512,9 @@
|
|||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.defaultsdeep": {
|
||||
|
@ -8102,9 +8198,9 @@
|
|||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.67",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz",
|
||||
"integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==",
|
||||
"version": "1.1.73",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz",
|
||||
"integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==",
|
||||
"dev": true
|
||||
},
|
||||
"node-sass": {
|
||||
|
@ -8788,9 +8884,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
|
@ -8920,9 +9016,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "7.0.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
|
||||
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
|
||||
"version": "7.0.36",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
|
||||
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.4.2",
|
||||
|
@ -10042,16 +10138,16 @@
|
|||
"dev": true
|
||||
},
|
||||
"renderkid": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz",
|
||||
"integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==",
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz",
|
||||
"integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css-select": "^1.1.0",
|
||||
"dom-converter": "^0.2",
|
||||
"htmlparser2": "^3.3.0",
|
||||
"lodash": "^4.17.20",
|
||||
"strip-ansi": "^3.0.0"
|
||||
"css-select": "^4.1.3",
|
||||
"dom-converter": "^0.2.0",
|
||||
"htmlparser2": "^6.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"strip-ansi": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
|
@ -10061,31 +10157,59 @@
|
|||
"dev": true
|
||||
},
|
||||
"css-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
|
||||
"integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"boolbase": "~1.0.0",
|
||||
"css-what": "2.1",
|
||||
"domutils": "1.5.1",
|
||||
"nth-check": "~1.0.1"
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^5.0.0",
|
||||
"domhandler": "^4.2.0",
|
||||
"domutils": "^2.6.0",
|
||||
"nth-check": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"css-what": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
||||
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz",
|
||||
"integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==",
|
||||
"dev": true
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
|
||||
"integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.2.0",
|
||||
"entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
|
||||
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
|
||||
"dev": true
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
|
||||
"integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
"dom-serializer": "^1.0.1",
|
||||
"domelementtype": "^2.2.0",
|
||||
"domhandler": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"nth-check": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
|
||||
"integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"boolbase": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
|
@ -11147,9 +11271,9 @@
|
|||
}
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
|
@ -11298,9 +11422,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
@ -12082,9 +12206,9 @@
|
|||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
||||
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
|
||||
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
|
@ -12280,9 +12404,9 @@
|
|||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
|
||||
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
|
||||
"version": "npm:vue-loader@16.8.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
|
||||
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -12292,9 +12416,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
|
||||
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -12344,6 +12468,11 @@
|
|||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||
"dev": true
|
||||
},
|
||||
"vue-timers": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-timers/-/vue-timers-2.0.4.tgz",
|
||||
"integrity": "sha512-QOEVdO4V4o9WjFG6C0Kn9tfdTeeECjqvEQozcQlfL1Tn8v0qx4uUPhTYoc1+s6qoJnSbu8f68x8+nm1ZEir0kw=="
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.0.tgz",
|
||||
|
@ -13093,45 +13222,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
|
@ -13176,9 +13272,9 @@
|
|||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
|
||||
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
|
||||
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
"lint:fix": "eslint --fix --ext .js,.vue ."
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.0",
|
||||
"axios": "^0.21.4",
|
||||
"bootstrap": "^4.5.3",
|
||||
"bootstrap-vue": "^2.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.12",
|
||||
"vue-router": "^3.2.0",
|
||||
"vue-timers": "^2.0.4",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -5,8 +5,8 @@ error_reporting(E_ALL | E_STRICT);
|
|||
|
||||
$config = [
|
||||
"host" => "localhost",
|
||||
"dbname" => "juggl",
|
||||
"username" => "juggl",
|
||||
"password" => "?=5,}f_F&){;@xthx-[i",
|
||||
"dbname" => "admin_juggl",
|
||||
"username" => "admin_juggl",
|
||||
"password" => "}dyn{5O!tUlZD;9R?lbi$.@=I,_a2L",
|
||||
"table_prefix" => "ju_"
|
||||
];
|
||||
|
|
|
@ -24,8 +24,12 @@ class GetRecordsBranch extends ApiBranch
|
|||
if ($params->exists(["finished"])) {
|
||||
$finished = $params->get("finished");
|
||||
}
|
||||
$visible = NULL;
|
||||
if ($params->exists(["visible"])) {
|
||||
$visible = $params->get("visible");
|
||||
}
|
||||
|
||||
$records = getRecords($user_id, $limit, $finished);
|
||||
$records = getRecords($user_id, $limit, $finished, $visible);
|
||||
|
||||
$json = new JsonBuilder();
|
||||
$json->addRecords($records);
|
||||
|
|
38
public/api/getStats.php
Normal file
38
public/api/getStats.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
session_start();
|
||||
require_once(__DIR__ . "/services/apiBranch.inc.php");
|
||||
require_once(__DIR__ . "/services/jsonBuilder.inc.php");
|
||||
require_once(__DIR__ . "/services/responses.inc.php");
|
||||
require_once(__DIR__ . "/services/jugglDbApi.inc.php");
|
||||
|
||||
class GetStatsBranch extends ApiBranch
|
||||
{
|
||||
function get(ParamCleaner $params)
|
||||
{
|
||||
respondStatus(405);
|
||||
}
|
||||
|
||||
function post(ParamCleaner $params)
|
||||
{
|
||||
$user_id = $params->get("user_id");
|
||||
|
||||
$from_date = date("Y-m-d");
|
||||
if ($params->exists(["from_date"])) {
|
||||
$from_date = $params->get("from_date");
|
||||
}
|
||||
$until_date = date("Y-m-d");
|
||||
if ($params->exists(["until_date"])) {
|
||||
$until_date = $params->get("until_date");
|
||||
}
|
||||
|
||||
$stats = getStats($user_id, $from_date, $until_date);
|
||||
|
||||
$json = new JsonBuilder();
|
||||
$json->addStats($stats);
|
||||
|
||||
respondJson($json);
|
||||
}
|
||||
}
|
||||
|
||||
$branch = new GetStatsBranch();
|
||||
$branch->execute();
|
|
@ -7,6 +7,7 @@ class DbOperations
|
|||
{
|
||||
$this->resetQuery();
|
||||
$this->tablePrefix = $tablePrefix;
|
||||
$this->customValueId = 0;
|
||||
|
||||
require(__DIR__ . "/../config/config.php");
|
||||
$this->config = $config;
|
||||
|
@ -55,6 +56,22 @@ class DbOperations
|
|||
return $this;
|
||||
}
|
||||
|
||||
function groupBy(array $attributes, bool $addTableName = true)
|
||||
{
|
||||
for ($i = 0; $i < count($attributes); $i++) {
|
||||
$a = $attributes[$i];
|
||||
// Add table name prefix if missing
|
||||
if ($addTableName && strpos($a, ".") === false) {
|
||||
$attributes[$i] = "$this->table.$a";
|
||||
}
|
||||
}
|
||||
$formattedAttributes = implode(', ', $attributes);
|
||||
|
||||
$this->addToQuery("GROUP BY $formattedAttributes");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
function orderBy(string $attribute, string $order = Order::ASC)
|
||||
{
|
||||
|
|
|
@ -86,7 +86,8 @@ class JsonBuilder
|
|||
"record_tag_id" => "",
|
||||
"name" => "",
|
||||
"user_id" => "",
|
||||
"visible" => ""
|
||||
"visible" => "",
|
||||
"bundle" => ""
|
||||
);
|
||||
|
||||
$this->jsonData['record_tags'] = array();
|
||||
|
@ -96,6 +97,27 @@ class JsonBuilder
|
|||
return $this;
|
||||
}
|
||||
|
||||
function addStats(array $stats)
|
||||
{
|
||||
if ($stats === null) return;
|
||||
|
||||
$columns = array(
|
||||
"name" => "",
|
||||
"project_id" => "",
|
||||
"color" => "",
|
||||
"visible" => "",
|
||||
"duration" => "",
|
||||
"record_count" => "",
|
||||
"date" => ""
|
||||
);
|
||||
|
||||
$this->jsonData['stats'] = array();
|
||||
foreach ($stats as $tag) {
|
||||
$this->jsonData['stats'][] = $this->createJsonArray($tag, $columns);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function createJsonArray(array $data, array $columns)
|
||||
{
|
||||
$jsonArray = array();
|
||||
|
|
|
@ -160,11 +160,17 @@ function getRunningRecords($user_id)
|
|||
return $results;
|
||||
}
|
||||
|
||||
function getRecords($user_id, $limit = NULL, $finished = NULL)
|
||||
function getRecords($user_id, $limit = NULL, $finished = NULL, $visible = NULL)
|
||||
{
|
||||
$db = new DbOperations();
|
||||
$db->select("time_records");
|
||||
$db->where("user_id", Comparison::EQUAL, $user_id);
|
||||
$db->select("time_records", ["record_id", "start_time", "end_time", "duration", "user_id", "project_id", "start_device_id"]);
|
||||
if ($visible != NULL) {
|
||||
$db->innerJoin("projects", "project_id");
|
||||
}
|
||||
$db->where("ju_time_records.user_id", Comparison::EQUAL, $user_id);
|
||||
if ($visible != NULL) {
|
||||
$db->where("ju_projects.visible", Comparison::EQUAL, $visible);
|
||||
}
|
||||
if ($finished != NULL) {
|
||||
if ($finished) {
|
||||
$db->addSql(" AND end_time IS NOT NULL");
|
||||
|
@ -210,7 +216,7 @@ function updateRecord($user_id, $record)
|
|||
$data = [];
|
||||
$props = ["end_time", "start_time", "duration", "project_id"];
|
||||
foreach ($props as $p) {
|
||||
if (array_key_exists ($p, $record)) {
|
||||
if (array_key_exists($p, $record)) {
|
||||
$data[$p] = $record[$p];
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +236,7 @@ function updateProject($user_id, $project)
|
|||
$data = [];
|
||||
$props = ["name", "start_date", "color", "visible"];
|
||||
foreach ($props as $p) {
|
||||
if (array_key_exists ($p, $project)) {
|
||||
if (array_key_exists($p, $project)) {
|
||||
$data[$p] = $project[$p];
|
||||
}
|
||||
}
|
||||
|
@ -248,9 +254,9 @@ function updateRecordTag($user_id, $tag)
|
|||
|
||||
// Update given parameters
|
||||
$data = [];
|
||||
$props = ["name", "visible"];
|
||||
$props = ["name", "visible", "bundle"];
|
||||
foreach ($props as $p) {
|
||||
if (array_key_exists ($p, $tag)) {
|
||||
if (array_key_exists($p, $tag)) {
|
||||
$data[$p] = $tag[$p];
|
||||
}
|
||||
}
|
||||
|
@ -373,7 +379,9 @@ function getRecordExternalData($record)
|
|||
$data = [
|
||||
"record_tag_id" => $tag["record_tag_id"],
|
||||
"name" => $tag["name"],
|
||||
"user_id" => $tag["user_id"]
|
||||
"user_id" => $tag["user_id"],
|
||||
"visible" => $tag["visible"],
|
||||
"bundle" => $tag["bundle"]
|
||||
];
|
||||
$tags[] = $data;
|
||||
}
|
||||
|
@ -456,3 +464,30 @@ function removeTagFromRecord($tag_id, $record_id)
|
|||
$db->where("record_id", Comparison::EQUAL, $record_id);
|
||||
$db->execute();
|
||||
}
|
||||
|
||||
function getStats($user_id, $from_date, $until_date)
|
||||
{
|
||||
$sum_duration = "SUM(ju_time_records.duration) AS duration";
|
||||
$date = "DATE(ju_time_records.start_time) AS date";
|
||||
$record_count = "COUNT(*) AS record_count";
|
||||
|
||||
$where1 = " AND DATE(ju_time_records.start_time) >= DATE( ";
|
||||
$where2 = " ) AND DATE(ju_time_records.start_time) <= DATE( ";
|
||||
$where3 = " ) AND ju_time_records.end_time IS NOT NULL ";
|
||||
|
||||
$db = new DbOperations();
|
||||
$db->select("projects", ["ju_projects.name AS name", "ju_projects.project_id", "color", "visible", $sum_duration, $date, $record_count], false);
|
||||
$db->innerJoin("time_records", "project_id");
|
||||
// $db->innerJoin("tags_on_records", "record_id", "record_id", "time_records");
|
||||
// $db->innerJoin("record_tags", "record_tag_id", "record_tag_id", "tags_on_records");
|
||||
|
||||
$db->where("ju_projects.user_id", Comparison::EQUAL, $user_id);
|
||||
$db->addSql($where1);
|
||||
$db->addValue($from_date);
|
||||
$db->addSql($where2);
|
||||
$db->addValue($until_date);
|
||||
$db->addSql($where3);
|
||||
|
||||
$db->groupBy(["ju_projects.project_id", "date"], false);
|
||||
return $db->execute();
|
||||
}
|
||||
|
|
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow:
|
Binary file not shown.
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 25 KiB |
BIN
src/assets/title.png
Normal file
BIN
src/assets/title.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
|
@ -1,17 +1,15 @@
|
|||
<template>
|
||||
<div>
|
||||
<b-link to="/">
|
||||
<b-img
|
||||
:src="require('../../assets/logo.png')"
|
||||
alt="Juggl"
|
||||
:width="widthSize"
|
||||
:center="center"
|
||||
/>
|
||||
<b-img :src="image" alt="Juggl" :width="widthSize" :center="center" />
|
||||
</b-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import logo from "./../../assets/logo.png";
|
||||
import title from "./../../assets/title.png";
|
||||
|
||||
export default {
|
||||
name: "BaseLogo",
|
||||
props: {
|
||||
|
@ -22,11 +20,15 @@ export default {
|
|||
center: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
iconOnly: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
widthSize: function() {
|
||||
let sizes = {
|
||||
let titleSizes = {
|
||||
mini: "35px",
|
||||
tiny: "80px",
|
||||
smaller: "110px",
|
||||
|
@ -38,9 +40,34 @@ export default {
|
|||
huge: "800px",
|
||||
massive: "960px"
|
||||
};
|
||||
let logoSizes = {
|
||||
mini: "12px",
|
||||
tiny: "30px",
|
||||
smaller: "50px",
|
||||
small: "65px",
|
||||
normal: "80x",
|
||||
medium: "100px",
|
||||
large: "150px",
|
||||
big: "200px",
|
||||
huge: "300px",
|
||||
massive: "400px"
|
||||
};
|
||||
|
||||
var sizes = titleSizes;
|
||||
if (this.iconOnly) {
|
||||
sizes = logoSizes;
|
||||
}
|
||||
|
||||
let targetSize = sizes[this.size];
|
||||
if (targetSize === undefined) return sizes["small"];
|
||||
else return targetSize;
|
||||
},
|
||||
image: function() {
|
||||
if (this.iconOnly) {
|
||||
return logo;
|
||||
} else {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<section>
|
||||
<BaseTitle v-if="title" center size="large">{{ title }}</BaseTitle>
|
||||
<BaseTitle v-if="title" center size="huge">{{ title }}</BaseTitle>
|
||||
<slot />
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<h1 id="title" :class="[size, { center: center }]" class="bold pt-5 pb-3">
|
||||
<h1
|
||||
id="title"
|
||||
:class="[size, { center: center }, { outline: outline }]"
|
||||
class="bold"
|
||||
>
|
||||
<slot />
|
||||
</h1>
|
||||
</template>
|
||||
|
@ -15,12 +19,18 @@ export default {
|
|||
center: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
},
|
||||
outline: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import '@/style/theme.sass'
|
||||
|
||||
#title.tiny
|
||||
font-size: 1em
|
||||
#title.small
|
||||
|
@ -31,10 +41,17 @@ export default {
|
|||
font-size: 1.7em
|
||||
#title.huge
|
||||
font-size: 2em
|
||||
#title.giant
|
||||
font-size: 3em
|
||||
|
||||
#title.center
|
||||
text-align: center
|
||||
|
||||
#title.outline
|
||||
color: transparent
|
||||
-webkit-text-stroke-width: 1.5px
|
||||
-webkit-text-stroke-color: $secondary
|
||||
|
||||
.bold
|
||||
font-weight: bold
|
||||
</style>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
variant="outline-primary"
|
||||
right
|
||||
>
|
||||
<b-dropdown-item to="/statistics">Statistics</b-dropdown-item>
|
||||
<b-dropdown-item to="/logout">Log out</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</template>
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<template>
|
||||
<b-form @submit="submitForm">
|
||||
<b-form-group id="id-group" label-for="id" label="Record ID">
|
||||
<b-form-group id="id-group" label-for="id" label="Project ID">
|
||||
<b-form-input id="id" v-model="form.project_id" required trim disabled>
|
||||
</b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-checkbox id="visible" v-model="form.visible" dark>
|
||||
Visible
|
||||
</b-form-checkbox>
|
||||
<b-form-group id="name-group" label-for="name" label="Name">
|
||||
<b-form-input id="name" v-model="form.name" required trim> </b-form-input>
|
||||
</b-form-group>
|
||||
|
@ -63,7 +66,8 @@ export default {
|
|||
project_id: undefined,
|
||||
start_date: undefined,
|
||||
name: undefined,
|
||||
color: undefined
|
||||
color: undefined,
|
||||
visible: undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -105,6 +109,7 @@ export default {
|
|||
this.form.name = this.project.name;
|
||||
this.form.start_date = this.project.start_date;
|
||||
this.form.color = this.project.color;
|
||||
this.form.visible = this.project.visible;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -156,6 +156,7 @@ export default {
|
|||
start_time: this.times.start.date + " " + this.times.start.time,
|
||||
end_time: this.times.end.date + " " + this.times.end.time,
|
||||
duration: this.times.duration,
|
||||
tags: this.record.tags,
|
||||
start_device_id: this.record.start_device_id // TODO: Remove at some time
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<template>
|
||||
<b-form @submit="submitForm">
|
||||
<b-form-group id="id-group" label-for="id" label="Record ID">
|
||||
<b-form-group id="id-group" label-for="id" label="Tag ID">
|
||||
<b-form-input id="id" v-model="form.record_tag_id" required trim disabled>
|
||||
</b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-checkbox id="visible" v-model="form.visible" dark>
|
||||
Visible
|
||||
</b-form-checkbox>
|
||||
<b-form-group id="name-group" label-for="name" label="Name">
|
||||
<b-form-input id="name" v-model="form.name" required trim> </b-form-input>
|
||||
</b-form-group>
|
||||
|
@ -48,7 +51,8 @@ export default {
|
|||
working: false,
|
||||
form: {
|
||||
record_tag_id: undefined,
|
||||
name: undefined
|
||||
name: undefined,
|
||||
visible: undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -84,6 +88,7 @@ export default {
|
|||
created: function() {
|
||||
this.form.record_tag_id = this.tag.record_tag_id;
|
||||
this.form.name = this.tag.name;
|
||||
this.form.visible = this.tag.visible;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
151
src/components/juggl/JugglDailyStatisticsList.vue
Normal file
151
src/components/juggl/JugglDailyStatisticsList.vue
Normal file
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<b-table
|
||||
:items="aggregatedStatistics"
|
||||
primary-key="day"
|
||||
hover
|
||||
:busy="isLoading"
|
||||
:fields="statistic_fields"
|
||||
sort-by="day"
|
||||
sort-desc
|
||||
>
|
||||
<template #table-busy>
|
||||
<div class="text-center">
|
||||
<b-spinner></b-spinner>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Custom data -->
|
||||
<template #cell(duration)="data">
|
||||
{{ getDurationTimestamp(data.item.duration) }}
|
||||
</template>
|
||||
|
||||
<template #cell(distribution)="data">
|
||||
<JugglProjectDistribution :projects="data.item.projects" />
|
||||
</template>
|
||||
|
||||
<template #cell(details)="data">
|
||||
<b-button
|
||||
size="sm"
|
||||
@click="data.toggleDetails"
|
||||
variant="outline-secondary"
|
||||
>
|
||||
<b-icon class="icon-btn" icon="bar-chart-steps" />
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<template #row-details="data">
|
||||
<b-card>
|
||||
<JugglProjectDistribution
|
||||
:projects="data.item.projects"
|
||||
names
|
||||
class="mb-3"
|
||||
/>
|
||||
<JugglProjectStatisticsList :statistics="data.item.statistics" />
|
||||
</b-card>
|
||||
</template>
|
||||
</b-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { helperService } from "@/services/helper.service.js";
|
||||
import JugglProjectStatisticsList from "./JugglProjectStatisticsList.vue";
|
||||
import JugglProjectDistribution from "./JugglProjectDistribution.vue";
|
||||
|
||||
export default {
|
||||
name: "JugglDailyStatisticsList",
|
||||
components: { JugglProjectStatisticsList, JugglProjectDistribution },
|
||||
props: {
|
||||
statistics: {
|
||||
required: true,
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
statistic_fields: [
|
||||
{
|
||||
key: "day",
|
||||
label: "Day"
|
||||
},
|
||||
{
|
||||
key: "duration",
|
||||
label: "Duration"
|
||||
},
|
||||
{
|
||||
key: "distribution",
|
||||
lavel: "Distribution"
|
||||
},
|
||||
{
|
||||
key: "record_count",
|
||||
label: "Records"
|
||||
},
|
||||
{
|
||||
key: "details",
|
||||
label: "Details"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading: function() {
|
||||
return this.statistics === undefined;
|
||||
},
|
||||
aggregatedStatistics: function() {
|
||||
if (this.statistics === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregated = {};
|
||||
this.statistics.forEach(stat => {
|
||||
var dayName = stat.date;
|
||||
|
||||
if (aggregated[dayName] === undefined) {
|
||||
aggregated[dayName] = {
|
||||
day: dayName,
|
||||
duration: 0,
|
||||
record_count: 0,
|
||||
statistics: [],
|
||||
projects: {}
|
||||
};
|
||||
}
|
||||
|
||||
if (aggregated[dayName].projects[stat.project_id] === undefined) {
|
||||
aggregated[dayName].projects[stat.project_id] = {
|
||||
project_id: stat.project_id,
|
||||
duration: 0,
|
||||
record_count: 0,
|
||||
color: stat.color,
|
||||
name: stat.name,
|
||||
visible: stat.visible,
|
||||
statistics: []
|
||||
};
|
||||
}
|
||||
|
||||
aggregated[dayName].duration += Number(stat.duration);
|
||||
aggregated[dayName].record_count += Number(stat.record_count);
|
||||
aggregated[dayName].statistics.push(stat);
|
||||
|
||||
aggregated[dayName].projects[stat.project_id].duration += Number(
|
||||
stat.duration
|
||||
);
|
||||
aggregated[dayName].projects[stat.project_id].record_count += Number(
|
||||
stat.record_count
|
||||
);
|
||||
aggregated[dayName].projects[stat.project_id].statistics.push(stat);
|
||||
});
|
||||
|
||||
// Simplyfying object to lists
|
||||
aggregated = Object.values(aggregated);
|
||||
aggregated.forEach(stat => {
|
||||
stat.projects = Object.values(stat.projects);
|
||||
});
|
||||
return aggregated;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: helperService.getDurationTimestamp
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass"></style>
|
151
src/components/juggl/JugglMonthlyStatisticsList.vue
Normal file
151
src/components/juggl/JugglMonthlyStatisticsList.vue
Normal file
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<b-table
|
||||
:items="aggregatedStatistics"
|
||||
primary-key="month"
|
||||
hover
|
||||
:busy="isLoading"
|
||||
:fields="statistic_fields"
|
||||
sort-by="month"
|
||||
sort-desc
|
||||
>
|
||||
<template #table-busy>
|
||||
<div class="text-center">
|
||||
<b-spinner></b-spinner>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Custom data -->
|
||||
<template #cell(duration)="data">
|
||||
{{ getDurationTimestamp(data.item.duration) }}
|
||||
</template>
|
||||
|
||||
<template #cell(distribution)="data">
|
||||
<JugglProjectDistribution :projects="data.item.projects" class="mb-3" />
|
||||
</template>
|
||||
|
||||
<template #cell(details)="data">
|
||||
<b-button
|
||||
size="sm"
|
||||
@click="data.toggleDetails"
|
||||
variant="outline-secondary"
|
||||
>
|
||||
<b-icon class="icon-btn" icon="bar-chart-steps" />
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<template #row-details="data">
|
||||
<b-card>
|
||||
<JugglProjectDistribution
|
||||
:projects="data.item.projects"
|
||||
names
|
||||
class="mb-3"
|
||||
/>
|
||||
<JugglProjectStatisticsList :statistics="data.item.statistics" />
|
||||
</b-card>
|
||||
</template>
|
||||
</b-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { helperService } from "@/services/helper.service.js";
|
||||
import JugglProjectStatisticsList from "./JugglProjectStatisticsList.vue";
|
||||
import JugglProjectDistribution from "./JugglProjectDistribution.vue";
|
||||
|
||||
export default {
|
||||
name: "JugglMonthlyStatisticsList",
|
||||
components: { JugglProjectStatisticsList, JugglProjectDistribution },
|
||||
props: {
|
||||
statistics: {
|
||||
required: true,
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
statistic_fields: [
|
||||
{
|
||||
key: "month",
|
||||
label: "Month"
|
||||
},
|
||||
{
|
||||
key: "duration",
|
||||
label: "Duration"
|
||||
},
|
||||
{
|
||||
key: "distribution",
|
||||
lavel: "Distribution"
|
||||
},
|
||||
{
|
||||
key: "record_count",
|
||||
label: "Records"
|
||||
},
|
||||
{
|
||||
key: "details",
|
||||
label: "Details"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading: function() {
|
||||
return this.statistics === undefined;
|
||||
},
|
||||
aggregatedStatistics: function() {
|
||||
if (this.statistics === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var aggregated = {};
|
||||
this.statistics.forEach(stat => {
|
||||
var monthName = stat.date.substring(0, 7); // date is in the format "YYYY-MM-DD", so simply cutting of the day
|
||||
|
||||
if (aggregated[monthName] === undefined) {
|
||||
aggregated[monthName] = {
|
||||
month: monthName,
|
||||
duration: 0,
|
||||
record_count: 0,
|
||||
statistics: [],
|
||||
projects: {}
|
||||
};
|
||||
}
|
||||
|
||||
if (aggregated[monthName].projects[stat.project_id] === undefined) {
|
||||
aggregated[monthName].projects[stat.project_id] = {
|
||||
project_id: stat.project_id,
|
||||
duration: 0,
|
||||
record_count: 0,
|
||||
color: stat.color,
|
||||
name: stat.name,
|
||||
visible: stat.visible,
|
||||
statistics: []
|
||||
};
|
||||
}
|
||||
|
||||
aggregated[monthName].duration += Number(stat.duration);
|
||||
aggregated[monthName].record_count += Number(stat.record_count);
|
||||
aggregated[monthName].statistics.push(stat);
|
||||
|
||||
aggregated[monthName].projects[stat.project_id].duration += Number(
|
||||
stat.duration
|
||||
);
|
||||
aggregated[monthName].projects[stat.project_id].record_count += Number(
|
||||
stat.record_count
|
||||
);
|
||||
aggregated[monthName].projects[stat.project_id].statistics.push(stat);
|
||||
});
|
||||
|
||||
// Simplyfying object to lists
|
||||
aggregated = Object.values(aggregated);
|
||||
aggregated.forEach(stat => {
|
||||
stat.projects = Object.values(stat.projects);
|
||||
});
|
||||
return aggregated;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: helperService.getDurationTimestamp
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass"></style>
|
45
src/components/juggl/JugglProjectDistribution.vue
Normal file
45
src/components/juggl/JugglProjectDistribution.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<b-progress :max="totalDuration">
|
||||
<b-progress-bar
|
||||
v-for="(project, index) in projects"
|
||||
v-bind:key="project.project_id"
|
||||
:value="project.duration"
|
||||
:style="'background-color: ' + project.color + ' !important'"
|
||||
:variant="index % 2 == 0 ? 'primary' : 'dark'"
|
||||
v-b-tooltip.hover
|
||||
:title="project.name + ' · ' + getDurationTimestamp(project.duration)"
|
||||
><span v-if="names">{{ project.name }}</span></b-progress-bar
|
||||
>
|
||||
</b-progress>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { helperService } from "@/services/helper.service.js";
|
||||
|
||||
export default {
|
||||
name: "JugglProjectDistribution",
|
||||
props: {
|
||||
projects: {
|
||||
required: true,
|
||||
type: Array
|
||||
},
|
||||
names: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalDuration: function() {
|
||||
var duration = 0;
|
||||
this.projects.forEach(stat => (duration += Number(stat.duration)));
|
||||
return duration;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: helperService.getDurationTimestamp
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
112
src/components/juggl/JugglProjectStatisticsList.vue
Normal file
112
src/components/juggl/JugglProjectStatisticsList.vue
Normal file
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<b-table
|
||||
:items="projectStatistics"
|
||||
primary-key="project_id"
|
||||
hover
|
||||
:busy="isLoading"
|
||||
:fields="statistic_fields"
|
||||
sort-by="duration"
|
||||
sort-desc
|
||||
>
|
||||
<template #table-busy>
|
||||
<div class="text-center">
|
||||
<b-spinner></b-spinner>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Custom data -->
|
||||
<template #cell(project)="data">
|
||||
<JugglProjectName :project="data.item" />
|
||||
</template>
|
||||
|
||||
<template #cell(duration)="data">
|
||||
{{ getDurationTimestamp(data.item.duration) }}
|
||||
</template>
|
||||
|
||||
<template #cell(distribution)="data">
|
||||
{{ ((data.item.duration / totalDuration) * 100).toFixed(0) }}%
|
||||
</template>
|
||||
</b-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JugglProjectName from "@/components/juggl/JugglProjectName";
|
||||
import { helperService } from "@/services/helper.service.js";
|
||||
|
||||
export default {
|
||||
name: "JugglProjectStatisticsList",
|
||||
components: {
|
||||
JugglProjectName
|
||||
},
|
||||
props: {
|
||||
statistics: {
|
||||
required: true,
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
statistic_fields: [
|
||||
{
|
||||
key: "project",
|
||||
label: "Project"
|
||||
},
|
||||
{
|
||||
key: "duration",
|
||||
label: "Duration"
|
||||
},
|
||||
{
|
||||
key: "record_count",
|
||||
label: "Records"
|
||||
},
|
||||
{
|
||||
key: "distribution",
|
||||
label: "Distribution"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading: function() {
|
||||
return this.statistics === undefined;
|
||||
},
|
||||
totalDuration: function() {
|
||||
var duration = 0;
|
||||
this.statistics.forEach(stat => (duration += Number(stat.duration)));
|
||||
return duration;
|
||||
},
|
||||
projectStatistics: function() {
|
||||
if (this.statistics === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var projects = {};
|
||||
this.statistics.forEach(stat => {
|
||||
if (projects[stat.project_id] === undefined) {
|
||||
projects[stat.project_id] = {
|
||||
project_id: stat.project_id,
|
||||
duration: 0,
|
||||
record_count: 0,
|
||||
color: stat.color,
|
||||
name: stat.name,
|
||||
visible: stat.visible,
|
||||
statistics: []
|
||||
};
|
||||
}
|
||||
|
||||
projects[stat.project_id].duration += Number(stat.duration);
|
||||
projects[stat.project_id].record_count += Number(stat.record_count);
|
||||
projects[stat.project_id].statistics.push(stat);
|
||||
});
|
||||
|
||||
// Simplyfying object to lists
|
||||
return Object.values(projects);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: helperService.getDurationTimestamp
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass"></style>
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<b-table
|
||||
:items="records"
|
||||
primary-key="record_id"
|
||||
hover
|
||||
:busy="isLoading"
|
||||
:fields="fields"
|
||||
|
@ -27,7 +28,10 @@
|
|||
</template>
|
||||
|
||||
<template #cell(tags)="data">
|
||||
<JugglTagField :recordId="data.item.record_id" />
|
||||
<JugglTagField
|
||||
:recordId="data.item.record_id"
|
||||
:onlyVisible="onlyVisibleTags"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #cell(details)="data">
|
||||
|
@ -90,11 +94,17 @@ export default {
|
|||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onlyVisibleTags: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
iconScale: 1.6,
|
||||
timerDelta: 0,
|
||||
requiredFields: [
|
||||
{
|
||||
key: "project",
|
||||
|
@ -139,8 +149,18 @@ export default {
|
|||
return fields;
|
||||
}
|
||||
},
|
||||
timers: {
|
||||
"increaseTimer": { time: 1000, autostart: true, repeat: true }
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: helperService.getDurationTimestamp,
|
||||
getDurationTimestamp: function(duration) {
|
||||
var localDuration = duration;
|
||||
if (this.running) {
|
||||
localDuration += this.timerDelta;
|
||||
}
|
||||
|
||||
return helperService.getDurationTimestamp(localDuration);
|
||||
},
|
||||
getProject: function(id) {
|
||||
var project = store.getters.getProjectById(id);
|
||||
|
||||
|
@ -155,6 +175,15 @@ export default {
|
|||
},
|
||||
detailsRecord: function(id) {
|
||||
this.$router.push("/record/" + id);
|
||||
},
|
||||
increaseTimer: function() {
|
||||
if (this.records.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var record = this.records[0];
|
||||
var liveDuration = (new Date().getTime() - new Date(record.start_time).getTime()) / 1000;
|
||||
this.timerDelta = liveDuration - record.duration;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div :id="containerId" class="tag-container">
|
||||
<!-- Tag item list -->
|
||||
<div
|
||||
class="tag-item"
|
||||
v-for="tag in addedTags"
|
||||
|
@ -9,10 +10,12 @@
|
|||
{{ tag.name }}
|
||||
</div>
|
||||
|
||||
<!-- Add-button -->
|
||||
<div :id="btnId">
|
||||
<b-icon icon="plus" />
|
||||
</div>
|
||||
|
||||
<!-- Popover -->
|
||||
<b-popover
|
||||
:target="btnId"
|
||||
triggers="click"
|
||||
|
@ -53,6 +56,11 @@ export default {
|
|||
props: {
|
||||
recordId: {
|
||||
required: true
|
||||
},
|
||||
onlyVisible: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -95,10 +103,16 @@ export default {
|
|||
);
|
||||
},
|
||||
allTags: function() {
|
||||
return store.getters.tags;
|
||||
if (this.onlyVisible) {
|
||||
return store.getters.getFilteredTags({ visible: true });
|
||||
} else {
|
||||
return store.getters.tags;
|
||||
}
|
||||
},
|
||||
addedTags: function() {
|
||||
return this.record.tags;
|
||||
return Object.values(this.record.tags).filter(
|
||||
t => !this.onlyVisible || t.visible
|
||||
);
|
||||
},
|
||||
usedTagIds: function() {
|
||||
var ids = [];
|
||||
|
|
33
src/components/layout/LayoutFooter.vue
Normal file
33
src/components/layout/LayoutFooter.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<footer>
|
||||
<BaseContainer
|
||||
width="medium"
|
||||
center
|
||||
>
|
||||
<b-link to="/tools" class="surround-space">Tools</b-link>
|
||||
<b-link to="/changelog" class="surround-space">Changelog</b-link>
|
||||
<b-link to="/credits" class="surround-space">Credits</b-link>
|
||||
</BaseContainer>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseContainer from "@/components/base/BaseContainer.vue";
|
||||
|
||||
export default {
|
||||
name: "LayoutFooter",
|
||||
components: {
|
||||
BaseContainer,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
footer
|
||||
margin-bottom: 3rem
|
||||
text-align: center
|
||||
|
||||
.surround-space
|
||||
margin-left: 1rem
|
||||
margin-right: 1rem
|
||||
</style>
|
|
@ -1,35 +1,46 @@
|
|||
<template>
|
||||
<main>
|
||||
<BaseLogo id="logo" size="medium" center class="space-top" />
|
||||
<BaseContainer
|
||||
:width="width"
|
||||
center
|
||||
class="space-bottom"
|
||||
:class="{ 'center-content': center }"
|
||||
>
|
||||
<BaseTitle v-if="title" center size="huge" class="centered">
|
||||
{{ title }}
|
||||
</BaseTitle>
|
||||
<slot />
|
||||
</BaseContainer>
|
||||
</main>
|
||||
<div>
|
||||
<main>
|
||||
<BaseLogo id="logo" size="medium" center class="space-top space-bottom-small" icon-only />
|
||||
<BaseContainer
|
||||
:width="width"
|
||||
center
|
||||
class="space-bottom"
|
||||
:class="{ 'center-content': center }"
|
||||
>
|
||||
<BaseTitle
|
||||
v-if="title"
|
||||
center
|
||||
size="giant"
|
||||
class="centered space-bottom"
|
||||
outline
|
||||
>
|
||||
{{ title.toUpperCase() }}
|
||||
</BaseTitle>
|
||||
<slot />
|
||||
</BaseContainer>
|
||||
</main>
|
||||
<LayoutFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseContainer from "@/components/base/BaseContainer";
|
||||
import BaseLogo from "@/components/base/BaseLogo";
|
||||
import BaseTitle from "@/components/base/BaseTitle";
|
||||
import LayoutFooter from "@/components/layout/LayoutFooter";
|
||||
|
||||
export default {
|
||||
name: "LayoutMinimal",
|
||||
components: {
|
||||
BaseContainer,
|
||||
BaseLogo,
|
||||
BaseTitle
|
||||
BaseTitle,
|
||||
LayoutFooter
|
||||
},
|
||||
props: {
|
||||
width: {
|
||||
default: "",
|
||||
default: "slim",
|
||||
type: String
|
||||
},
|
||||
title: {
|
||||
|
@ -54,4 +65,7 @@ export default {
|
|||
|
||||
.center-content
|
||||
text-align: center
|
||||
|
||||
.space-bottom-small
|
||||
margin-bottom: 1rem
|
||||
</style>
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
center
|
||||
:class="{ 'center-content': center }"
|
||||
>
|
||||
<BaseTitle v-if="title" center size="huge" class="centered">
|
||||
{{ title }}
|
||||
<BaseTitle v-if="title" center size="giant" class="centered" outline>
|
||||
{{ title.toUpperCase() }}
|
||||
</BaseTitle>
|
||||
<slot />
|
||||
</BaseContainer>
|
||||
</main>
|
||||
<LayoutFooter/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -32,6 +33,7 @@ import BaseContainer from "@/components/base/BaseContainer.vue";
|
|||
import BaseLogo from "@/components/base/BaseLogo.vue";
|
||||
import BaseUserDropdown from "@/components/base/BaseUserDropdown.vue";
|
||||
import BaseTitle from "@/components/base/BaseTitle";
|
||||
import LayoutFooter from "@/components/layout/LayoutFooter";
|
||||
|
||||
export default {
|
||||
name: "LayoutNavbarPrivate",
|
||||
|
@ -39,7 +41,8 @@ export default {
|
|||
BaseContainer,
|
||||
BaseLogo,
|
||||
BaseUserDropdown,
|
||||
BaseTitle
|
||||
BaseTitle,
|
||||
LayoutFooter
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
|
@ -86,6 +89,14 @@ main
|
|||
margin-top: 6rem
|
||||
margin-bottom: 6rem
|
||||
|
||||
footer
|
||||
margin-bottom: 3rem
|
||||
text-align: center
|
||||
|
||||
*
|
||||
margin-left: 1rem
|
||||
margin-right: 1rem
|
||||
|
||||
.center-content
|
||||
text-align: center
|
||||
</style>
|
||||
|
|
|
@ -3,9 +3,12 @@ import App from "./App.vue";
|
|||
import router from "./router";
|
||||
import store from "./store";
|
||||
import { BootstrapVue, BootstrapVueIcons } from "bootstrap-vue";
|
||||
import VueTimers from 'vue-timers'
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
Vue.use(VueTimers);
|
||||
|
||||
// Install BootstrapVue
|
||||
Vue.use(BootstrapVue);
|
||||
Vue.use(BootstrapVueIcons);
|
||||
|
|
|
@ -6,6 +6,10 @@ import NotFound from "../views/NotFound.vue";
|
|||
import Home from "../views/Home.vue";
|
||||
import History from "../views/History.vue";
|
||||
import Manage from "../views/Manage.vue";
|
||||
import Statistics from "../views/Statistics.vue";
|
||||
import Changelog from "../views/Changelog.vue";
|
||||
import Credits from "../views/Credits.vue";
|
||||
import ToolsDocumentation from "../views/ToolsDocumentation.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
@ -37,6 +41,27 @@ const routes = [
|
|||
component: Home,
|
||||
beforeEnter: requireAuth
|
||||
},
|
||||
{
|
||||
path: "/statistics",
|
||||
name: "Statistics",
|
||||
component: Statistics,
|
||||
beforeEnter: requireAuth
|
||||
},
|
||||
{
|
||||
path: "/changelog",
|
||||
name: "Changelog",
|
||||
component: Changelog
|
||||
},
|
||||
{
|
||||
path: "/credits",
|
||||
name: "Credits",
|
||||
component: Credits
|
||||
},
|
||||
{
|
||||
path: "/tools",
|
||||
name: "ToolsDocumentation",
|
||||
component: ToolsDocumentation
|
||||
},
|
||||
{
|
||||
path: "/logout",
|
||||
name: "Logout",
|
||||
|
|
|
@ -64,13 +64,28 @@ export const helperService = {
|
|||
* @returns Date as string in the used format
|
||||
*/
|
||||
toISODate(date) {
|
||||
var timezoneOffset = date.getMinutes() + date.getTimezoneOffset();
|
||||
var timestamp = date.getTime() + timezoneOffset * 1000;
|
||||
var correctDate = new Date(timestamp);
|
||||
return (
|
||||
date.getFullYear() +
|
||||
"-" +
|
||||
(date.getMonth() + 1).toString().padStart(2, "0") +
|
||||
"-" +
|
||||
date
|
||||
.getDate()
|
||||
.toString()
|
||||
.padStart(2, "0") +
|
||||
"T00:00.000Z"
|
||||
);
|
||||
},
|
||||
|
||||
correctDate.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
return correctDate.toISOString();
|
||||
/**
|
||||
* Converts a date object into a date-time-timezone string and only keeps the date.
|
||||
*
|
||||
* @param {*} date A date object
|
||||
*
|
||||
* @returns Date as string in YYYY-MM-DD format
|
||||
*/
|
||||
toISODateOnly(date) {
|
||||
return helperService.toISODate(date).substring(0, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,7 @@ export const jugglService = {
|
|||
getProjects() {
|
||||
return apiService.post("/getProjects.php").then(r => {
|
||||
return {
|
||||
data: r.data,
|
||||
data: { projects: processProjects(r.data.projects) },
|
||||
msg: ""
|
||||
};
|
||||
});
|
||||
|
@ -31,12 +31,33 @@ export const jugglService = {
|
|||
getTags() {
|
||||
return apiService.post("/getRecordTags.php").then(r => {
|
||||
return {
|
||||
data: r.data,
|
||||
data: { record_tags: processTags(r.data.record_tags) },
|
||||
msg: ""
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getStatistics(options = {}) {
|
||||
if (options.from === undefined) {
|
||||
options.from = new Date();
|
||||
}
|
||||
if (options.until === undefined) {
|
||||
options.until = new Date();
|
||||
}
|
||||
|
||||
return apiService
|
||||
.post("/getStats.php", {
|
||||
from_date: helperService.toISODateOnly(options.from),
|
||||
until_date: helperService.toISODateOnly(options.until)
|
||||
})
|
||||
.then(r => {
|
||||
return {
|
||||
data: { statistics: processVisibility(r.data.stats) },
|
||||
msg: ""
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getRecord(recordId) {
|
||||
return apiService
|
||||
.post("/getRecord.php", {
|
||||
|
@ -98,6 +119,9 @@ export const jugglService = {
|
|||
if (options.finished !== undefined) {
|
||||
payload.finished = options.finished;
|
||||
}
|
||||
if (options.visible !== undefined) {
|
||||
payload.visible = options.visible;
|
||||
}
|
||||
|
||||
return apiService.post("/getRecords.php", payload).then(r => {
|
||||
return {
|
||||
|
@ -248,7 +272,26 @@ function processRecords(data) {
|
|||
rec.duration = helperService.calcDurationInSeconds(rec.start_time);
|
||||
}
|
||||
|
||||
rec.tags = Object.values(rec.tags);
|
||||
rec.tags = processTags(Object.values(rec.tags));
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function processTags(tags) {
|
||||
return processVisibility(tags);
|
||||
}
|
||||
|
||||
function processProjects(projects) {
|
||||
return processVisibility(projects);
|
||||
}
|
||||
|
||||
function processVisibility(items) {
|
||||
Object.values(items).forEach(item => {
|
||||
if (item.visible === "1") {
|
||||
item.visible = true;
|
||||
} else {
|
||||
item.visible = false;
|
||||
}
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ export const juggl = {
|
|||
projects: [],
|
||||
records: [],
|
||||
tags: [],
|
||||
statistics: [],
|
||||
user: undefined,
|
||||
auth: undefined,
|
||||
recordsLimit: 0
|
||||
|
@ -20,6 +21,9 @@ export const juggl = {
|
|||
setTags(state, tags) {
|
||||
state.tags = tags;
|
||||
},
|
||||
setStatistics(state, statistics) {
|
||||
state.statistics = statistics;
|
||||
},
|
||||
setRecordsLimit(state, limit) {
|
||||
state.recordsLimit = limit;
|
||||
},
|
||||
|
@ -44,10 +48,55 @@ export const juggl = {
|
|||
finishedRecords: state => {
|
||||
return Object.values(state.records).filter(record => !record.running);
|
||||
},
|
||||
getFilteredRecords: (state, getters) => ({
|
||||
running = undefined,
|
||||
projectVisible = undefined,
|
||||
records = undefined
|
||||
}) => {
|
||||
if (records == undefined) {
|
||||
records = getters.records;
|
||||
}
|
||||
|
||||
var visibleProjects = getters.visibleProjects;
|
||||
var visibleIds = [];
|
||||
Object.values(visibleProjects)
|
||||
.filter(p => p.visible)
|
||||
.forEach(p => {
|
||||
visibleIds.push(p.project_id);
|
||||
});
|
||||
|
||||
return Object.values(records).filter(rec => {
|
||||
if (running !== undefined && running !== rec.running) {
|
||||
return false;
|
||||
}
|
||||
var recProjectVisible = visibleIds.includes(rec.project_id);
|
||||
if (
|
||||
projectVisible !== undefined &&
|
||||
projectVisible !== recProjectVisible
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
getFilteredStatistics: (state, getters) => ({
|
||||
projectVisible = undefined
|
||||
}) => {
|
||||
return Object.values(getters.statistics).filter(statistic => {
|
||||
if (
|
||||
projectVisible !== undefined &&
|
||||
statistic.visible !== projectVisible
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
auth: state => state.auth,
|
||||
apiUrl: state => state.apiUrl,
|
||||
user: state => state.user,
|
||||
isLoggedIn: state => !!state.auth,
|
||||
statistics: state => state.statistics,
|
||||
records: state => state.records,
|
||||
projects: state => state.projects,
|
||||
tags: state => state.tags,
|
||||
|
@ -73,10 +122,10 @@ export const juggl = {
|
|||
return getters.projectIds.filter(id => !runningProjectIds.includes(id));
|
||||
},
|
||||
finishedProjects: (state, getters) => {
|
||||
var ids = getters.finishedProjectIds;
|
||||
return Object.values(state.projects).filter(project =>
|
||||
ids.includes(project.project_id)
|
||||
);
|
||||
return getters.getFilteredProjects({ finished: true });
|
||||
},
|
||||
visibleProjects: (state, getters) => {
|
||||
return getters.getFilteredProjects({ visible: true });
|
||||
},
|
||||
runningProjects: (state, getters) => {
|
||||
var ids = getters.runningProjectIds;
|
||||
|
@ -89,6 +138,43 @@ export const juggl = {
|
|||
project => project.project_id === id
|
||||
);
|
||||
},
|
||||
getFilteredProjects: (state, getters) => ({
|
||||
finished = undefined,
|
||||
visible = undefined,
|
||||
projects = undefined
|
||||
}) => {
|
||||
if (projects == undefined) {
|
||||
projects = getters.projects;
|
||||
}
|
||||
|
||||
var runningIds = getters.runningProjectIds;
|
||||
return Object.values(projects).filter(project => {
|
||||
var projectFinished = !runningIds.includes(project.project_id);
|
||||
|
||||
if (finished !== undefined && finished !== projectFinished) {
|
||||
return false;
|
||||
}
|
||||
if (visible !== undefined && visible !== project.visible) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
getFilteredTags: (state, getters) => ({
|
||||
visible = undefined,
|
||||
tags = undefined
|
||||
}) => {
|
||||
if (tags == undefined) {
|
||||
tags = getters.tags;
|
||||
}
|
||||
|
||||
return Object.values(tags).filter(tag => {
|
||||
if (visible != undefined && visible !== tag.visible) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
getTagById: (state, getters) => id => {
|
||||
return Object.values(getters.tags).find(tag => tag.record_tag_id === id);
|
||||
},
|
||||
|
@ -135,12 +221,16 @@ export const juggl = {
|
|||
return true;
|
||||
});
|
||||
},
|
||||
loadRecords({ commit, state, getters }, { limit, finished }) {
|
||||
loadRecords({ commit, state, getters }, { limit, finished, visible }) {
|
||||
if (limit !== undefined) {
|
||||
commit("setRecordsLimit", limit);
|
||||
}
|
||||
|
||||
var payload = { limit: state.recordsLimit, finished: finished };
|
||||
var payload = {
|
||||
limit: state.recordsLimit,
|
||||
finished: finished,
|
||||
visible: visible
|
||||
};
|
||||
|
||||
return jugglService.getRecords(payload).then(r => {
|
||||
var allRecords = Object.values(r.data.records);
|
||||
|
@ -154,6 +244,29 @@ export const juggl = {
|
|||
commit("setRecords", allRecords);
|
||||
});
|
||||
},
|
||||
loadDailyStatistics({ dispatch }, { date }) {
|
||||
dispatch("loadStatistics", { from: date, until: date });
|
||||
},
|
||||
loadMonthlyStatistics(
|
||||
{ dispatch },
|
||||
{ startYear, startMonth, endYear, endMonth }
|
||||
) {
|
||||
|
||||
// Month in date object goes from 0 - 11
|
||||
var options = {
|
||||
from: new Date(startYear, startMonth - 1, 1),
|
||||
until: new Date(endYear, endMonth, 0) // 0 leads to the last day of the previous month
|
||||
};
|
||||
|
||||
dispatch("loadStatistics", options);
|
||||
},
|
||||
async loadStatistics({ commit }, options) {
|
||||
var results = Object.values(
|
||||
(await jugglService.getStatistics(options)).data.statistics
|
||||
);
|
||||
|
||||
commit("setStatistics", results);
|
||||
},
|
||||
loadRunningRecords({ commit, getters }) {
|
||||
return jugglService.getRunningRecords().then(r => {
|
||||
var allRecords = {
|
||||
|
|
82
src/views/Changelog.vue
Normal file
82
src/views/Changelog.vue
Normal file
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<LayoutMinimal title="Changelog">
|
||||
<BaseSection>
|
||||
<BaseTitle size="small">
|
||||
11.02.2022
|
||||
</BaseTitle>
|
||||
<ul>
|
||||
<li>Implemented daily and monthly statistics</li>
|
||||
<li>Added tool</li>
|
||||
</ul>
|
||||
</BaseSection>
|
||||
|
||||
<BaseSection>
|
||||
<BaseTitle size="small">
|
||||
23.11.2021
|
||||
</BaseTitle>
|
||||
<ul>
|
||||
<li>Added tools page</li>
|
||||
<li>Visual tweaks</li>
|
||||
</ul>
|
||||
</BaseSection>
|
||||
|
||||
<BaseSection>
|
||||
<BaseTitle size="small">
|
||||
07.11.2021
|
||||
</BaseTitle>
|
||||
<ul>
|
||||
<li>Added credits page</li>
|
||||
<li>Created basic footer</li>
|
||||
<li>Added live record timer</li>
|
||||
</ul>
|
||||
</BaseSection>
|
||||
|
||||
<BaseSection>
|
||||
<BaseTitle size="small">
|
||||
27.07.2021
|
||||
</BaseTitle>
|
||||
<ul>
|
||||
<li>Added simple statistics</li>
|
||||
</ul>
|
||||
</BaseSection>
|
||||
|
||||
<BaseSection>
|
||||
<BaseTitle size="small">
|
||||
21.05.2021
|
||||
</BaseTitle>
|
||||
<ul>
|
||||
<li>Visual tweaks</li>
|
||||
</ul>
|
||||
</BaseSection>
|
||||
|
||||
<BaseSection>
|
||||
<BaseTitle size="small">
|
||||
12.04.2021
|
||||
</BaseTitle>
|
||||
<ul>
|
||||
<li>Added toggle to change visibility of single projects and tags</li>
|
||||
</ul>
|
||||
</BaseSection>
|
||||
</LayoutMinimal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import LayoutMinimal from "@/components/layout/LayoutMinimal";
|
||||
import BaseTitle from "@/components/base/BaseTitle";
|
||||
import BaseSection from "../components/base/BaseSection.vue";
|
||||
|
||||
export default {
|
||||
name: "Changelog",
|
||||
components: {
|
||||
LayoutMinimal,
|
||||
BaseTitle,
|
||||
BaseSection
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
ul
|
||||
list-style-type: '+ '
|
||||
</style>
|
51
src/views/Credits.vue
Normal file
51
src/views/Credits.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<LayoutMinimal title="Credits" >
|
||||
<BaseSection>
|
||||
<BaseTitle>
|
||||
vue-timers
|
||||
</BaseTitle>
|
||||
<BaseTitle size="tiny">
|
||||
<b-link href="https://github.com/Kelin2025/vue-timers">
|
||||
<b-icon-github></b-icon-github>
|
||||
GitHub
|
||||
</b-link>
|
||||
</BaseTitle>
|
||||
<p class="monospace">
|
||||
MIT License
|
||||
<br/>
|
||||
<br/>
|
||||
Copyright (c) 2017 Anton Kosykh
|
||||
<br/>
|
||||
<br/>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
<br/>
|
||||
<br/>
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
<br/>
|
||||
<br/>
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
</p>
|
||||
</BaseSection>
|
||||
</LayoutMinimal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import LayoutMinimal from "@/components/layout/LayoutMinimal";
|
||||
import BaseTitle from "@/components/base/BaseTitle";
|
||||
import BaseSection from "@/components/base/BaseSection";
|
||||
|
||||
export default {
|
||||
name: "Credits",
|
||||
components: {
|
||||
LayoutMinimal,
|
||||
BaseTitle,
|
||||
BaseSection
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.monospace
|
||||
font-family: Courier New, Courier, monospace
|
||||
</style>
|
|
@ -26,7 +26,7 @@ import { helperService } from "@/services/helper.service.js";
|
|||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
name: "History",
|
||||
data: () => {
|
||||
return {
|
||||
working: true
|
||||
|
@ -60,7 +60,7 @@ export default {
|
|||
store.dispatch("loadTags");
|
||||
store.dispatch("loadProjects");
|
||||
store
|
||||
.dispatch("loadRecords", { limit: 0, finished: true })
|
||||
.dispatch("loadRecords", { limit: 0, finished: true, visible: true })
|
||||
.then(() => {
|
||||
this.working = false;
|
||||
})
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<JugglRecordsList :records="runningRecords" running />
|
||||
</BaseSection>
|
||||
<BaseSection title="Projects">
|
||||
<div v-if="finishedProjects.length > 0">
|
||||
<JugglProjectsPanel :projects="finishedProjects" />
|
||||
<div v-if="availableProjects.length > 0">
|
||||
<JugglProjectsPanel :projects="availableProjects" />
|
||||
</div>
|
||||
<div id="add-project-form">
|
||||
<FormProjectAdd />
|
||||
|
@ -43,21 +43,30 @@ export default {
|
|||
BaseSection
|
||||
},
|
||||
computed: {
|
||||
finishedProjects: () => {
|
||||
return store.getters.finishedProjects;
|
||||
availableProjects: () => {
|
||||
return store.getters.getFilteredProjects({
|
||||
finished: true,
|
||||
visible: true
|
||||
});
|
||||
},
|
||||
finishedRecords: () => {
|
||||
return store.getters.finishedRecords;
|
||||
return store.getters.getFilteredRecords({
|
||||
running: false,
|
||||
projectVisible: true
|
||||
});
|
||||
},
|
||||
runningRecords: () => {
|
||||
return store.getters.runningRecords;
|
||||
return store.getters.getFilteredRecords({
|
||||
running: true,
|
||||
projectVisible: true
|
||||
});
|
||||
}
|
||||
},
|
||||
created: () => {
|
||||
store.dispatch("loadProjects");
|
||||
store.dispatch("loadTags");
|
||||
store.dispatch("loadRunningRecords");
|
||||
store.dispatch("loadRecords", { limit: 10, finished: true });
|
||||
store.dispatch("loadRecords", { limit: 10, finished: true, visible: true });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -97,7 +97,7 @@ import FormTagAdd from "@/components/forms/FormTagAdd";
|
|||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
name: "Manage",
|
||||
components: {
|
||||
LayoutNavbarPrivate,
|
||||
FormProjectDetails,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<LayoutMinimal center title="Couldn't find what you were looking for :(">
|
||||
404
|
||||
<LayoutMinimal center title="404">
|
||||
Couldn't find what you were looking for :(
|
||||
</LayoutMinimal>
|
||||
</template>
|
||||
|
||||
|
|
180
src/views/Statistics.vue
Normal file
180
src/views/Statistics.vue
Normal file
|
@ -0,0 +1,180 @@
|
|||
<template>
|
||||
<LayoutNavbarPrivate title="Statistics">
|
||||
<BaseContainer class="centered mb-5">
|
||||
<b-form-radio-group
|
||||
id="mode-radio"
|
||||
v-model="mode"
|
||||
:options="options"
|
||||
button-variant="outline-primary"
|
||||
name="radio-btn-outline"
|
||||
buttons
|
||||
size="sm"
|
||||
class="mb-3"
|
||||
@input="updateStatistics"
|
||||
></b-form-radio-group>
|
||||
<b-form id="form-daily" v-if="mode == 'daily'" inline>
|
||||
<b-form-datepicker
|
||||
id="startdate"
|
||||
v-model="daily.startDate"
|
||||
required
|
||||
placeholder="Choose a start date"
|
||||
:max="daily.endDate"
|
||||
dark
|
||||
@input="updateStatistics"
|
||||
>
|
||||
</b-form-datepicker>
|
||||
→
|
||||
<b-form-datepicker
|
||||
id="enddate"
|
||||
v-model="daily.endDate"
|
||||
required
|
||||
placeholder="Choose an end date"
|
||||
:min="daily.startDate"
|
||||
dark
|
||||
@input="updateStatistics"
|
||||
>
|
||||
</b-form-datepicker>
|
||||
</b-form>
|
||||
<b-form id="form-monthly" v-if="mode == 'monthly'" inline>
|
||||
<b-input-group>
|
||||
<b-input
|
||||
id="start-year"
|
||||
type="number"
|
||||
placeholder="Start year"
|
||||
v-model="monthly.startYear"
|
||||
required
|
||||
trim
|
||||
@input="updateStatistics"
|
||||
class="slim"
|
||||
>
|
||||
</b-input>
|
||||
<b-input
|
||||
id="start-month"
|
||||
type="number"
|
||||
placeholder="Start month"
|
||||
v-model="monthly.startMonth"
|
||||
required
|
||||
trim
|
||||
@input="updateStatistics"
|
||||
class="slim"
|
||||
>
|
||||
</b-input>
|
||||
</b-input-group>
|
||||
→
|
||||
<b-input-group>
|
||||
<b-input
|
||||
id="end-year"
|
||||
type="number"
|
||||
placeholder="End year"
|
||||
v-model="monthly.endYear"
|
||||
required
|
||||
trim
|
||||
@input="updateStatistics"
|
||||
class="slim"
|
||||
>
|
||||
</b-input>
|
||||
<b-input
|
||||
id="end-month"
|
||||
type="number"
|
||||
placeholder="End month"
|
||||
v-model="monthly.endMonth"
|
||||
required
|
||||
trim
|
||||
@input="updateStatistics"
|
||||
class="slim"
|
||||
>
|
||||
</b-input>
|
||||
</b-input-group>
|
||||
</b-form>
|
||||
</BaseContainer>
|
||||
|
||||
<BaseSection id="projects" :title="modeTitle">
|
||||
<JugglDailyStatisticsList
|
||||
:statistics="visibleStatistics"
|
||||
v-if="mode == 'daily'"
|
||||
/>
|
||||
<JugglMonthlyStatisticsList
|
||||
:statistics="visibleStatistics"
|
||||
v-if="mode == 'monthly'"
|
||||
/>
|
||||
</BaseSection>
|
||||
</LayoutNavbarPrivate>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutNavbarPrivate from "@/components/layout/LayoutNavbarPrivate";
|
||||
import JugglDailyStatisticsList from "@/components/juggl/JugglDailyStatisticsList";
|
||||
import JugglMonthlyStatisticsList from "@/components/juggl/JugglMonthlyStatisticsList";
|
||||
import { helperService } from "@/services/helper.service.js";
|
||||
import BaseSection from "@/components/base/BaseSection";
|
||||
import BaseContainer from "@/components/base/BaseContainer";
|
||||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "Statistics",
|
||||
components: {
|
||||
LayoutNavbarPrivate,
|
||||
JugglMonthlyStatisticsList,
|
||||
JugglDailyStatisticsList,
|
||||
BaseSection,
|
||||
BaseContainer
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
working: true,
|
||||
mode: "daily",
|
||||
options: [
|
||||
{ text: "Daily", value: "daily" },
|
||||
{ text: "Monthly", value: "monthly" }
|
||||
],
|
||||
daily: {
|
||||
startDate: new Date(),
|
||||
endDate: new Date()
|
||||
},
|
||||
monthly: {
|
||||
startYear: new Date().getFullYear(),
|
||||
startMonth: new Date().getMonth() + 1,
|
||||
endYear: new Date().getFullYear(),
|
||||
endMonth: new Date().getMonth() + 1
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted: function() {
|
||||
this.updateStatistics();
|
||||
},
|
||||
computed: {
|
||||
visibleStatistics: () => {
|
||||
return store.getters.getFilteredStatistics({
|
||||
projectVisible: true
|
||||
});
|
||||
},
|
||||
modeTitle: function() {
|
||||
return this.mode[0].toUpperCase() + this.mode.substring(1);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: helperService.getDurationTimestamp,
|
||||
updateStatistics: function() {
|
||||
if (this.mode == "daily") {
|
||||
store.dispatch("loadStatistics", {
|
||||
from: new Date(this.daily.startDate),
|
||||
until: new Date(this.daily.endDate)
|
||||
});
|
||||
} else if (this.mode == "monthly") {
|
||||
store.dispatch("loadMonthlyStatistics", this.monthly);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
.centered
|
||||
text-align: center
|
||||
|
||||
#form-daily *, #form-monthly *
|
||||
margin: auto
|
||||
|
||||
.slim
|
||||
max-width: 6rem
|
||||
</style>
|
61
src/views/ToolsDocumentation.vue
Normal file
61
src/views/ToolsDocumentation.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<LayoutMinimal title="Tools">
|
||||
<BaseTitle>
|
||||
Graph Day Distribution
|
||||
<br />
|
||||
<small>by Linus Kämmerer</small>
|
||||
</BaseTitle>
|
||||
<BaseSection>
|
||||
<BaseTitle size="tiny">
|
||||
<b-link
|
||||
href="https://gist.github.com/linuskmr/f89ac638da036e25d67e147ece67577c"
|
||||
>
|
||||
<b-icon-github></b-icon-github>
|
||||
GitHub
|
||||
</b-link>
|
||||
</BaseTitle>
|
||||
<p class="monospace">
|
||||
Python script to generate a time distribution graph from the exported
|
||||
Juggl data. It accumulates the tracked record and shows the total
|
||||
working hours for different times for each weekday.
|
||||
</p>
|
||||
</BaseSection>
|
||||
|
||||
<BaseSection>
|
||||
<BaseTitle>
|
||||
JSON to CSV
|
||||
<br />
|
||||
<small>by Linus Kämmerer</small>
|
||||
</BaseTitle>
|
||||
<BaseTitle size="tiny">
|
||||
<b-link
|
||||
href="https://gist.github.com/linuskmr/856459cdf6c7ea460503b92e00b2aa26"
|
||||
>
|
||||
<b-icon-github></b-icon-github>
|
||||
GitHub
|
||||
</b-link>
|
||||
</BaseTitle>
|
||||
<p class="monospace">
|
||||
Python script to convert exported Juggl data from a JSON to a CSV file.
|
||||
</p>
|
||||
</BaseSection>
|
||||
</LayoutMinimal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import LayoutMinimal from "@/components/layout/LayoutMinimal";
|
||||
import BaseTitle from "@/components/base/BaseTitle";
|
||||
import BaseSection from "../components/base/BaseSection.vue";
|
||||
|
||||
export default {
|
||||
name: "ToolsDocumentation",
|
||||
components: {
|
||||
LayoutMinimal,
|
||||
BaseTitle,
|
||||
BaseSection
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped></style>
|
Loading…
Reference in a new issue