Frontend revamp with Vue

[frontend] Activity view map, stats and graph completed, activity gear missing
[frontend] Home page shows banner displaying activity upload status when uploading .gpx file
[frontend] Home page updates week and month status on activity upload
[frontend] Fixed minor UI issues
This commit is contained in:
João Vitória Silva
2024-04-03 15:48:12 +01:00
parent bc55e7346d
commit d685e69a1f
15 changed files with 740 additions and 379 deletions

View File

@@ -1,4 +1,5 @@
<div align="center">
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/endurain"><img src="https://badges.crowdin.net/endurain/localized.svg"></a>
<img src="frontend/img/logo/logo.png" width="128" height="128">
# Endurain

View File

@@ -14,6 +14,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6",
"bootstrap": "^5.3.2",
"chart.js": "^4.4.1",
"crypto-js": "^4.2.0",
"leaflet": "^1.9.4",
"pinia": "^2.1.7",
@@ -45,9 +46,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
"integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -471,9 +472,9 @@
}
},
"node_modules/@eslint/js": {
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
"integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -579,12 +580,12 @@
"dev": true
},
"node_modules/@intlify/core-base": {
"version": "9.9.1",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.9.1.tgz",
"integrity": "sha512-qsV15dg7jNX2faBRyKMgZS8UcFJViWEUPLdzZ9UR0kQZpFVeIpc0AG7ZOfeP7pX2T9SQ5jSiorq/tii9nkkafA==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.10.1.tgz",
"integrity": "sha512-0+Wtjj04GIyglh5KKiNjRwgjpHrhqqGZhaKY/QVjjogWKZq5WHROrTi84pNVsRN18QynyPmjtsVUWqFKPQ45xQ==",
"dependencies": {
"@intlify/message-compiler": "9.9.1",
"@intlify/shared": "9.9.1"
"@intlify/message-compiler": "9.10.1",
"@intlify/shared": "9.10.1"
},
"engines": {
"node": ">= 16"
@@ -594,11 +595,11 @@
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.9.1",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.9.1.tgz",
"integrity": "sha512-zTvP6X6HeumHOXuAE1CMMsV6tTX+opKMOxO1OHTCg5N5Sm/F7d8o2jdT6W6L5oHUsJ/vvkGefHIs7Q3hfowmsA==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.10.1.tgz",
"integrity": "sha512-b68UTmRhgZfswJZI7VAgW6BXZK5JOpoi5swMLGr4j6ss2XbFY13kiw+Hu+xYAfulMPSapcHzdWHnq21VGnMCnA==",
"dependencies": {
"@intlify/shared": "9.9.1",
"@intlify/shared": "9.10.1",
"source-map-js": "^1.0.2"
},
"engines": {
@@ -609,9 +610,9 @@
}
},
"node_modules/@intlify/shared": {
"version": "9.9.1",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.9.1.tgz",
"integrity": "sha512-b3Pta1nwkz5rGq434v0psHwEwHGy1pYCttfcM22IE//K9owbpkEvFptx9VcuRAxjQdrO2If249cmDDjBu5wMDA==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.10.1.tgz",
"integrity": "sha512-liyH3UMoglHBUn70iCYcy9CQlInx/lp50W2aeSxqqrvmG+LDj/Jj7tBJhBoQL4fECkldGhbmW0g2ommHfL6Wmw==",
"engines": {
"node": ">= 16"
},
@@ -680,6 +681,11 @@
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -754,9 +760,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz",
"integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
"cpu": [
"arm"
],
@@ -767,9 +773,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz",
"integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
"cpu": [
"arm64"
],
@@ -780,9 +786,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz",
"integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
"cpu": [
"arm64"
],
@@ -793,9 +799,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz",
"integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
"cpu": [
"x64"
],
@@ -806,9 +812,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz",
"integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
"cpu": [
"arm"
],
@@ -819,9 +825,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz",
"integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
"cpu": [
"arm64"
],
@@ -832,9 +838,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz",
"integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
"cpu": [
"arm64"
],
@@ -845,9 +851,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz",
"integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
"cpu": [
"riscv64"
],
@@ -858,9 +864,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz",
"integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
"cpu": [
"x64"
],
@@ -871,9 +877,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz",
"integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
"cpu": [
"x64"
],
@@ -884,9 +890,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz",
"integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
"cpu": [
"arm64"
],
@@ -897,9 +903,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz",
"integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
"cpu": [
"ia32"
],
@@ -910,9 +916,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz",
"integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
"cpu": [
"x64"
],
@@ -975,13 +981,13 @@
}
},
"node_modules/@vitest/expect": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz",
"integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz",
"integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==",
"dev": true,
"dependencies": {
"@vitest/spy": "1.2.2",
"@vitest/utils": "1.2.2",
"@vitest/spy": "1.3.1",
"@vitest/utils": "1.3.1",
"chai": "^4.3.10"
},
"funding": {
@@ -989,12 +995,12 @@
}
},
"node_modules/@vitest/runner": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz",
"integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz",
"integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==",
"dev": true,
"dependencies": {
"@vitest/utils": "1.2.2",
"@vitest/utils": "1.3.1",
"p-limit": "^5.0.0",
"pathe": "^1.1.1"
},
@@ -1030,9 +1036,9 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz",
"integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz",
"integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==",
"dev": true,
"dependencies": {
"magic-string": "^0.30.5",
@@ -1044,9 +1050,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz",
"integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz",
"integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==",
"dev": true,
"dependencies": {
"tinyspy": "^2.2.0"
@@ -1056,9 +1062,9 @@
}
},
"node_modules/@vitest/utils": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz",
"integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz",
"integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==",
"dev": true,
"dependencies": {
"diff-sequences": "^29.6.3",
@@ -1071,12 +1077,12 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
"integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
"integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/shared": "3.4.19",
"@vue/shared": "3.4.21",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
@@ -1088,27 +1094,27 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@vue/compiler-dom": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
"integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
"integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
"dependencies": {
"@vue/compiler-core": "3.4.19",
"@vue/shared": "3.4.19"
"@vue/compiler-core": "3.4.21",
"@vue/shared": "3.4.21"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz",
"integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
"integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/compiler-core": "3.4.19",
"@vue/compiler-dom": "3.4.19",
"@vue/compiler-ssr": "3.4.19",
"@vue/shared": "3.4.19",
"@vue/compiler-core": "3.4.21",
"@vue/compiler-dom": "3.4.21",
"@vue/compiler-ssr": "3.4.21",
"@vue/shared": "3.4.21",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.6",
"postcss": "^8.4.33",
"magic-string": "^0.30.7",
"postcss": "^8.4.35",
"source-map-js": "^1.0.2"
}
},
@@ -1118,18 +1124,18 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@vue/compiler-ssr": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz",
"integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
"integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
"dependencies": {
"@vue/compiler-dom": "3.4.19",
"@vue/shared": "3.4.19"
"@vue/compiler-dom": "3.4.21",
"@vue/shared": "3.4.21"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
"integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
"integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
},
"node_modules/@vue/eslint-config-prettier": {
"version": "8.0.0",
@@ -1146,48 +1152,48 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz",
"integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
"integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
"dependencies": {
"@vue/shared": "3.4.19"
"@vue/shared": "3.4.21"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz",
"integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
"integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
"dependencies": {
"@vue/reactivity": "3.4.19",
"@vue/shared": "3.4.19"
"@vue/reactivity": "3.4.21",
"@vue/shared": "3.4.21"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz",
"integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
"integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
"dependencies": {
"@vue/runtime-core": "3.4.19",
"@vue/shared": "3.4.19",
"@vue/runtime-core": "3.4.21",
"@vue/shared": "3.4.21",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz",
"integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
"integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
"dependencies": {
"@vue/compiler-ssr": "3.4.19",
"@vue/shared": "3.4.19"
"@vue/compiler-ssr": "3.4.21",
"@vue/shared": "3.4.21"
},
"peerDependencies": {
"vue": "3.4.19"
"vue": "3.4.21"
}
},
"node_modules/@vue/shared": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw=="
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
},
"node_modules/@vue/test-utils": {
"version": "2.4.4",
@@ -1333,9 +1339,9 @@
"dev": true
},
"node_modules/bootstrap": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz",
"integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"funding": [
{
"type": "github",
@@ -1412,6 +1418,17 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chart.js": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz",
"integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/check-error": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
@@ -1727,16 +1744,16 @@
}
},
"node_modules/eslint": {
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.56.0",
"@humanwhocodes/config-array": "^0.11.13",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
@@ -1824,16 +1841,16 @@
}
},
"node_modules/eslint-plugin-vue": {
"version": "9.21.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.21.1.tgz",
"integrity": "sha512-XVtI7z39yOVBFJyi8Ljbn7kY9yHzznKXL02qQYn+ta63Iy4A9JFBw6o4OSB9hyD2++tVT+su9kQqetUyCCwhjw==",
"version": "9.22.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.22.0.tgz",
"integrity": "sha512-7wCXv5zuVnBtZE/74z4yZ0CM8AjH6bk4MQGm7hZjUC2DBppKU5ioeOk5LGSg/s9a1ZJnIsdPLJpXnu1Rc+cVHg==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.13",
"semver": "^7.5.4",
"postcss-selector-parser": "^6.0.15",
"semver": "^7.6.0",
"vue-eslint-parser": "^9.4.2",
"xml-name-validator": "^4.0.0"
},
@@ -2039,9 +2056,9 @@
}
},
"node_modules/flatted": {
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/foreground-child": {
@@ -2216,9 +2233,9 @@
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.1.tgz",
"integrity": "sha512-My1KCEPs6A0hb4qCVzYp8iEvA8j8YqcvXLZZH8C9OFuTYpYjHE7N2dtG3mRl1HMD4+VGXpF3XcDVcxGBT7yDZQ==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"dependencies": {
"agent-base": "^7.1.0",
@@ -2229,9 +2246,9 @@
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.3.tgz",
"integrity": "sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==",
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
"integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
"dev": true,
"dependencies": {
"agent-base": "^7.0.2",
@@ -2400,14 +2417,15 @@
}
},
"node_modules/js-beautify": {
"version": "1.14.11",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz",
"integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==",
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
"integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
"dev": true,
"dependencies": {
"config-chain": "^1.1.13",
"editorconfig": "^1.0.3",
"editorconfig": "^1.0.4",
"glob": "^10.3.3",
"js-cookie": "^3.0.5",
"nopt": "^7.2.0"
},
"bin": {
@@ -2419,6 +2437,21 @@
"node": ">=14"
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz",
"integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==",
"dev": true
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2593,9 +2626,9 @@
}
},
"node_modules/magic-string": {
"version": "0.30.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
"integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"version": "0.30.8",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
"integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@@ -2664,9 +2697,9 @@
}
},
"node_modules/mlly": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz",
"integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
"integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==",
"dev": true,
"dependencies": {
"acorn": "^8.11.3",
@@ -2720,9 +2753,9 @@
}
},
"node_modules/npm-run-path": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz",
"integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
"dev": true,
"dependencies": {
"path-key": "^4.0.0"
@@ -3199,9 +3232,9 @@
}
},
"node_modules/rollup": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz",
"integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -3214,19 +3247,19 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.10.0",
"@rollup/rollup-android-arm64": "4.10.0",
"@rollup/rollup-darwin-arm64": "4.10.0",
"@rollup/rollup-darwin-x64": "4.10.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.10.0",
"@rollup/rollup-linux-arm64-gnu": "4.10.0",
"@rollup/rollup-linux-arm64-musl": "4.10.0",
"@rollup/rollup-linux-riscv64-gnu": "4.10.0",
"@rollup/rollup-linux-x64-gnu": "4.10.0",
"@rollup/rollup-linux-x64-musl": "4.10.0",
"@rollup/rollup-win32-arm64-msvc": "4.10.0",
"@rollup/rollup-win32-ia32-msvc": "4.10.0",
"@rollup/rollup-win32-x64-msvc": "4.10.0",
"@rollup/rollup-android-arm-eabi": "4.12.0",
"@rollup/rollup-android-arm64": "4.12.0",
"@rollup/rollup-darwin-arm64": "4.12.0",
"@rollup/rollup-darwin-x64": "4.12.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
"@rollup/rollup-linux-arm64-gnu": "4.12.0",
"@rollup/rollup-linux-arm64-musl": "4.12.0",
"@rollup/rollup-linux-riscv64-gnu": "4.12.0",
"@rollup/rollup-linux-x64-gnu": "4.12.0",
"@rollup/rollup-linux-x64-musl": "4.12.0",
"@rollup/rollup-win32-arm64-msvc": "4.12.0",
"@rollup/rollup-win32-ia32-msvc": "4.12.0",
"@rollup/rollup-win32-x64-msvc": "4.12.0",
"fsevents": "~2.3.2"
}
},
@@ -3478,12 +3511,12 @@
}
},
"node_modules/strip-literal": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz",
"integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz",
"integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==",
"dev": true,
"dependencies": {
"acorn": "^8.10.0"
"js-tokens": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
@@ -3660,9 +3693,9 @@
"dev": true
},
"node_modules/vite": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.2.tgz",
"integrity": "sha512-uwiFebQbTWRIGbCaTEBVAfKqgqKNKMJ2uPXsXeLIZxM8MVMjoS3j0cG8NrPxdDIadaWnPSjrkLWffLSC+uiP3Q==",
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz",
"integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==",
"dev": true,
"dependencies": {
"esbuild": "^0.19.3",
@@ -3715,9 +3748,9 @@
}
},
"node_modules/vite-node": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz",
"integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz",
"integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==",
"dev": true,
"dependencies": {
"cac": "^6.7.14",
@@ -3737,18 +3770,17 @@
}
},
"node_modules/vitest": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz",
"integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz",
"integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==",
"dev": true,
"dependencies": {
"@vitest/expect": "1.2.2",
"@vitest/runner": "1.2.2",
"@vitest/snapshot": "1.2.2",
"@vitest/spy": "1.2.2",
"@vitest/utils": "1.2.2",
"@vitest/expect": "1.3.1",
"@vitest/runner": "1.3.1",
"@vitest/snapshot": "1.3.1",
"@vitest/spy": "1.3.1",
"@vitest/utils": "1.3.1",
"acorn-walk": "^8.3.2",
"cac": "^6.7.14",
"chai": "^4.3.10",
"debug": "^4.3.4",
"execa": "^8.0.1",
@@ -3757,11 +3789,11 @@
"pathe": "^1.1.1",
"picocolors": "^1.0.0",
"std-env": "^3.5.0",
"strip-literal": "^1.3.0",
"strip-literal": "^2.0.0",
"tinybench": "^2.5.1",
"tinypool": "^0.8.2",
"vite": "^5.0.0",
"vite-node": "1.2.2",
"vite-node": "1.3.1",
"why-is-node-running": "^2.2.2"
},
"bin": {
@@ -3776,8 +3808,8 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0",
"@vitest/browser": "^1.0.0",
"@vitest/ui": "^1.0.0",
"@vitest/browser": "1.3.1",
"@vitest/ui": "1.3.1",
"happy-dom": "*",
"jsdom": "*"
},
@@ -3803,15 +3835,15 @@
}
},
"node_modules/vue": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz",
"integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==",
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
"integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
"dependencies": {
"@vue/compiler-dom": "3.4.19",
"@vue/compiler-sfc": "3.4.19",
"@vue/runtime-dom": "3.4.19",
"@vue/server-renderer": "3.4.19",
"@vue/shared": "3.4.19"
"@vue/compiler-dom": "3.4.21",
"@vue/compiler-sfc": "3.4.21",
"@vue/runtime-dom": "3.4.21",
"@vue/server-renderer": "3.4.21",
"@vue/shared": "3.4.21"
},
"peerDependencies": {
"typescript": "*"
@@ -3853,12 +3885,12 @@
}
},
"node_modules/vue-i18n": {
"version": "9.9.1",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.1.tgz",
"integrity": "sha512-xyQ4VspLdNSPTKBFBPWa1tvtj+9HuockZwgFeD2OhxxXuC2CWeNvV4seu2o9+vbQOyQbhAM5Ez56oxUrrnTWdw==",
"version": "9.10.1",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.10.1.tgz",
"integrity": "sha512-37HVJQZ/pZaRXGzFmmMomM1u1k7kndv3xCBPYHKEVfv5W3UVK67U/TpBug71ILYLNmjHLHdvTUPRF81pFT5fFg==",
"dependencies": {
"@intlify/core-base": "9.9.1",
"@intlify/shared": "9.9.1",
"@intlify/core-base": "9.10.1",
"@intlify/shared": "9.10.1",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
@@ -3872,11 +3904,11 @@
}
},
"node_modules/vue-router": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
"integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
"integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0"
"@vue/devtools-api": "^6.5.1"
},
"funding": {
"url": "https://github.com/sponsors/posva"

View File

@@ -18,6 +18,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6",
"bootstrap": "^5.3.2",
"chart.js": "^4.4.1",
"crypto-js": "^4.2.0",
"leaflet": "^1.9.4",
"pinia": "^2.1.7",

View File

@@ -4,8 +4,8 @@
<LoadingComponent />
</div>
<div v-else>
<div ref="activityMap" class="map" style="height: 300px;" v-if="source === 'home'"></div>
<div ref="activityMap" class="map" style="height: 500px;" v-if="source === 'activity'"></div>
<div ref="activityMap" class="map" style="height: 300px;" v-if="sourceProp === 'home'"></div>
<div ref="activityMap" class="map" style="height: 500px;" v-if="sourceProp === 'activity'"></div>
</div>
</template>
@@ -33,7 +33,7 @@ export default {
const isLoading = ref(true);
const activityStreamLatLng = ref(null);
const activityMap = ref(null);
const source = ref(props.source);
const sourceProp = ref(props.source);
onMounted(async () => {
try {
@@ -95,7 +95,7 @@ export default {
isLoading,
activityStreamLatLng,
activityMap,
source,
sourceProp,
};
},
};

View File

@@ -0,0 +1,168 @@
<template>
<canvas ref="chartCanvas"></canvas>
</template>
<script>
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
export default {
props: {
activity: {
type: Object,
required: true,
},
graphSelection: {
type: String,
required: true,
},
activityStreams: {
type: Array,
required: true,
}
},
setup(props) {
const chartCanvas = ref(null);
let myChart = null;
const computedChartData = computed(() => {
const data = [];
let label = "";
const labels = [];
let roundValues = true;
props.activityStreams.forEach((stream) => {
if (stream.stream_type == 1 && props.graphSelection == 'hr') {
for (const streamPoint of stream.stream_waypoints) {
data.push(parseInt(streamPoint.hr));
label = "Heart Rate (bpm)";
}
} else if (stream.stream_type == 2 && props.graphSelection == 'power') {
for (const streamPoint of stream.stream_waypoints) {
data.push(parseInt(streamPoint.power));
label = "Power (Watts)";
}
} else if (stream.stream_type == 3 && props.graphSelection == 'cad') {
for (const streamPoint of stream.stream_waypoints) {
data.push(parseInt(streamPoint.cad));
label = "Cadence (rpm)";
}
} else if (stream.stream_type == 4 && props.graphSelection == 'ele') {
for (const streamPoint of stream.stream_waypoints) {
data.push(parseFloat(streamPoint.ele));
label = "Elevation (m)";
}
} else if (stream.stream_type == 5 && props.graphSelection == 'vel') {
data.push(...stream.stream_waypoints.map(velData => parseFloat((velData.vel * 3.6).toFixed(0))));
label = "Velocity (km/h)";
} else if (stream.stream_type == 6 && props.graphSelection == 'pace') {
roundValues = false;
stream.stream_waypoints.forEach(paceData => {
if (paceData.pace == 0 || paceData.pace === null) {
data.push(0);
} else {
if (props.activity.activity_type == 1 || props.activity.activity_type == 2 || props.activity.activity_type == 3) {
data.push((paceData.pace * 1000) / 60);
} else if (props.activity.activity_type == 8 || props.activity.activity_type == 9) {
data.push((paceData.pace * 100) / 60);
}
}
});
if (props.activity.activity_type == 1 || props.activity.activity_type == 2 || props.activity.activity_type == 3) {
label = "Pace (min/km)";
} else if (props.activity.activity_type == 8 || props.activity.activity_type == 9) {
label = "Pace (min/100m)";
}
}
});
const dataDS = downsampleData(data, 200, roundValues);
const totalDistance = props.activity.distance / 1000;
const numberOfDataPoints = dataDS.length;
const distanceInterval = totalDistance / numberOfDataPoints;
for (let i = 0; i < numberOfDataPoints; i++) {
labels.push((i * distanceInterval).toFixed(0) + "km");
/* if (props.graphSelection == 'pace') {
let paceCalculated = 0;
if (props.activity.activity_type == 1 || props.activity.activity_type == 2 || props.activity.activity_type == 3) {
paceCalculated = (dataDS[i] * 1000) / 60;
} else if (props.activity.activity_type == 8 || props.activity.activity_type == 9) {
paceCalculated = (dataDS[i] * 100) / 60;
}
const minutes = Math.floor(paceCalculated);
const seconds = Math.round((paceCalculated - minutes) * 60);
dataDS[i] = `${minutes}:${seconds.toString().padStart(2, '0')}`;
} */
}
return {
datasets: [{
label: label,
data: dataDS,
}],
labels: labels,
};
});
watch(computedChartData, (newChartData) => {
if (myChart.value) {
myChart.value.data.datasets = newChartData.datasets;
myChart.data.labels = newChartData.labels;
myChart.value.update();
}
console.log(computedChartData.value)
}, { deep: true });
function downsampleData(data, threshold, roundValues) {
if (data.length <= threshold) {
return data;
}
const factor = Math.ceil(data.length / threshold);
const downsampledData = [];
for (let i = 0; i < data.length; i += factor) {
const chunk = data.slice(i, i + factor);
const average = chunk.reduce((a, b) => a + b) / chunk.length;
if (roundValues) {
downsampledData.push(parseInt(average));
}else{
downsampledData.push(average);
}
}
return downsampledData;
}
onMounted(() => {
myChart = new Chart(chartCanvas.value.getContext('2d'), {
type: 'line',
data: computedChartData.value,
options: {
responsive: true,
scales: {
y: {
beginAtZero: false
},
x: {
autoSkip: true
}
}
},
});
});
onUnmounted(() => {
if (myChart.value) {
myChart.value.destroy();
}
});
return {
chartCanvas
};
}
}
</script>

View File

@@ -11,10 +11,10 @@
<img src="/src/assets/avatar/female1.png" alt="Default Female Avatar" width="55" height="55" class="rounded-circle" v-else>
<div class="ms-3 me-3">
<div class="fw-bold">
<router-link :to="{ name: 'activity', params: { id: activity.id }}" class="link-underline-opacity-25 link-underline-opacity-100-hover" v-if="source === 'home'">
<router-link :to="{ name: 'activity', params: { id: activity.id }}" class="link-underline-opacity-25 link-underline-opacity-100-hover" v-if="sourceProp === 'home'">
{{ activity.name}}
</router-link>
<router-link :to="{ name: 'user', params: { id: userActivity.id }}" class="link-underline-opacity-25 link-underline-opacity-100-hover" v-if="source === 'activity'">
<router-link :to="{ name: 'user', params: { id: userActivity.id }}" class="link-underline-opacity-25 link-underline-opacity-100-hover" v-if="sourceProp === 'activity'">
{{ userActivity.name}}
</router-link>
</div>
@@ -52,7 +52,7 @@
<a class="btn btn-link btn-lg mt-1" :href="`https://www.strava.com/activities/${activity.strava_activity_id}`" role="button" v-if="activity.strava_activity_id">
<font-awesome-icon :icon="['fab', 'fa-strava']" />
</a>
<div v-if="source === 'activity'">
<div v-if="sourceProp === 'activity'">
<button class="btn btn-link btn-lg" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<font-awesome-icon :icon="['fas', 'fa-ellipsis-vertical']" />
</button>
@@ -96,7 +96,7 @@
</div>
<!-- Activity title -->
<h1 class="mt-3" v-if="source === 'activity'">
<h1 class="mt-3" v-if="sourceProp === 'activity'">
{{ activity.name }}
</h1>
<!-- Activity summary -->
@@ -137,8 +137,7 @@
</div>
</div>
</div>
<div class="row d-flex mt-3" v-if="source === 'activity'">
<!-- activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3 -->
<div class="row d-flex mt-3" v-if="sourceProp === 'activity'">
<div class="col" v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3">
<span class="fw-lighter">
{{ $t("activitySummary.activityAvgPower") }}
@@ -159,13 +158,12 @@
<br>
<span>{{ activity.elevation_loss }} m</span>
</div>
<!-- activity.activity_type != 9 || activity.activity_type != 1 -->
<div class="col">
<div class="col" v-if="activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6">
<span class="fw-lighter">{{ $t("activitySummary.activityAvgSpeed") }}</span>
<br>
<span>{{ (activity.average_speed * 3.6).toFixed(0) }} km/h</span>
</div>
<div class="col border-start border-opacity-50">
<div class="col border-start border-opacity-50" v-if="activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6">
<span class="fw-lighter">
{{ $t("activitySummary.activityAvgPower") }}
</span>
@@ -173,7 +171,7 @@
<span v-if="activity.average_power">{{ activity.average_power }} W</span>
<span v-else>{{ $t("activitySummary.activityNoData") }}</span>
</div>
<div class="col border-start border-opacity-50">
<div class="col border-start border-opacity-50" v-if="activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6">
<span class="fw-lighter">{{ $t("activitySummary.activityEleLoss") }}</span>
<br>
<span>{{ activity.elevation_loss }} m</span>
@@ -183,7 +181,7 @@
</template>
<script>
import { ref, onMounted, watchEffect, computed, nextTick } from 'vue';
import { ref, onMounted, watchEffect, computed } from 'vue';
import { users } from '@/services/user';
import LoadingComponent from '@/components/LoadingComponent.vue';
import { formatDate, formatTime, calculateTimeDifference } from '@/utils/dateTimeUtils';
@@ -207,7 +205,7 @@ export default {
const isLoading = ref(true);
const userActivity = ref(null);
const formattedPace = computed(() => formatPace(props.activity.pace));
const source = ref(props.source);
const sourceProp = ref(props.source);
onMounted(async () => {
try {
@@ -230,7 +228,7 @@ export default {
formatTime,
calculateTimeDifference,
formattedPace,
source,
sourceProp,
};
},
};

View File

@@ -1,4 +1,9 @@
{
"labelGear": "Gear",
"labelGearNotSet": "Not set"
"labelGearNotSet": "Not set",
"modalLabelAddGear": "Add gear to activity",
"modalLabelSelectGear": "Select gear",
"modalButtonAddGear": "Add gear",
"labelGraph": "Activity data graphs",
"labelDownsampling": "Data downsampled to ~200 points"
}

View File

@@ -6,5 +6,6 @@
"radioFollowerActivities": "Followers activities",
"successActivityAdded": "Activity added successfully",
"errorActivityAdded": "Error adding activity",
"errorActivityNotFound": "Activity not found"
"errorActivityNotFound": "Activity not found",
"processingActivity": "Processing activity"
}

View File

@@ -1,6 +1,9 @@
import { fetchGetRequest } from '@/utils/serviceUtils';
export const activityStreams = {
async getActivitySteamsByActivityId(activityId) {
return fetchGetRequest(`activities/streams/activity_id/${activityId}/all`);
},
async getActivitySteamByStreamTypeByActivityId(activityId, streamType) {
return fetchGetRequest(`activities/streams/activity_id/${activityId}/stream_type/${streamType}`);
}

View File

@@ -4,6 +4,9 @@ export const gears = {
getGearById(gearId) {
return fetchGetRequest(`gear/id/${gearId}`);
},
getGearFromType(gearType) {
return fetchGetRequest(`gear/type/${gearType}`);
},
getGearByNickname(nickname) {
return fetchGetRequest(`gear/nickname/${nickname}`);
},

View File

@@ -14,6 +14,25 @@ export function formatPace(pace) {
// Format the seconds
const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds;
// Return the formatted pace
return `${minutes}:${formattedSeconds} min/km`;
}
/**
* Formats the pace for swimming activities.
* @param {number} pace - The pace in minutes per 100 meters.
* @returns {string} The formatted pace in the format "minutes:seconds min/km".
*/
export function formatPaceSwim(pace) {
// Convert pace to seconds per 100 meters
const pacePerKm = pace * 1000 / 60;
// Calculate minutes and seconds
const minutes = Math.floor(pacePerKm);
const seconds = Math.round((pacePerKm - minutes) * 60);
// Format the seconds
const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds;
// Return the formatted pace
return `${minutes}:${formattedSeconds} min/km`;
}

View File

@@ -8,7 +8,7 @@ const API_URL = 'http://localhost:98/';
* @returns {Promise<Object>} - A promise that resolves to the JSON response from the server.
* @throws {Error} - If the response status is not ok.
*/
export async function fetchGetRequest(url, headers = {}) {
export async function fetchGetRequest(url) {
// Create the full URL by combining the API URL with the provided URL
const fullUrl = `${API_URL}${url}`;
// Send the GET request
@@ -36,7 +36,7 @@ export async function fetchGetRequest(url, headers = {}) {
* @returns {Promise<Object>} - A promise that resolves to the JSON response.
* @throws {Error} - If the response status is not ok.
*/
export async function fetchGetRequestTokenAsParameter(url, token, headers = {}) {
export async function fetchGetRequestTokenAsParameter(url, token) {
// Check if a token is provided
if (!token) {
throw new Error('No token provided');
@@ -67,7 +67,7 @@ export async function fetchGetRequestTokenAsParameter(url, token, headers = {})
* @returns {Promise<Object>} - A promise that resolves to the JSON response from the server.
* @throws {Error} - If the response status is not ok.
*/
export async function fetchPostFileRequest(url, formData, headers = {}) {
export async function fetchPostFileRequest(url, formData) {
// Create the full URL by combining the API URL with the provided URL
const fullUrl = `${API_URL}${url}`;
// Send the POST request
@@ -94,7 +94,7 @@ export async function fetchPostFileRequest(url, formData, headers = {}) {
* @returns {Promise<Object>} - A promise that resolves to the JSON response from the server.
* @throws {Error} - If the response status is not ok.
*/
export async function fetchPostFormUrlEncoded(url, formData, headers = {}) {
export async function fetchPostFormUrlEncoded(url, formData) {
// Create the full URL by combining the API URL with the provided URL
const fullUrl = `${API_URL}${url}`;
// Send the POST request
@@ -121,7 +121,7 @@ export async function fetchPostFormUrlEncoded(url, formData, headers = {}) {
* @returns {Promise<Object>} - A promise that resolves to the JSON response.
* @throws {Error} - If the response status is not ok.
*/
export async function fetchPostRequest(url, data, headers = {}) {
export async function fetchPostRequest(url, data) {
// Create the full URL by combining the API URL with the provided URL
const fullUrl = `${API_URL}${url}`;
// Send the POST request
@@ -149,7 +149,7 @@ export async function fetchPostRequest(url, data, headers = {}) {
* @returns {Promise<Object>} - A promise that resolves to the JSON response from the server.
* @throws {Error} - If the response status is not ok.
*/
export async function fetchPutRequest(url, data, headers = {}) {
export async function fetchPutRequest(url, data) {
// Create the full URL by combining the API URL with the provided URL
const fullUrl = `${API_URL}${url}`;
// Send the PUT request
@@ -176,7 +176,7 @@ export async function fetchPutRequest(url, data, headers = {}) {
* @returns {Promise<Object>} - A promise that resolves to the JSON response from the server.
* @throws {Error} - If the response status is not ok.
*/
export async function fetchDeleteRequest(url, headers = {}) {
export async function fetchDeleteRequest(url) {
// Create the full URL by combining the API URL with the provided URL
const fullUrl = `${API_URL}${url}`;
// Send the DELETE request

View File

@@ -1,143 +1,252 @@
<template>
<LoadingComponent v-if="isLoading"/>
<div v-else>
<ActivitySummaryComponent v-if="activity" :activity="activity" :source="'activity'" />
</div>
<!-- map zone -->
<div class="mt-3 mb-3" v-if="isLoading">
<LoadingComponent />
</div>
<div class="mt-3 mb-3" v-else>
<ActivityMapComponent :activity="activity" :source="'activity'"/>
</div>
<!-- gear zone -->
<hr class="mb-2 mt-2">
<div class="mt-3 mb-3" v-if="isLoading">
<LoadingComponent />
</div>
<div class="d-flex justify-content-between align-items-center" v-else>
<p class="pt-2">
<span class="fw-lighter">
{{ $t("activity.labelGear") }}
</span>
<br>
<span v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3">
<font-awesome-icon :icon="['fas', 'person-running']" />
</span>
<span v-else-if="activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6 || activity.activity_type == 7">
<font-awesome-icon :icon="['fas', 'fa-person-biking']" />
</span>
<span v-else-if="activity.activity_type == 8 || activity.activity_type == 9">
<font-awesome-icon :icon="['fas', 'fa-person-swimming']" />
</span>
<span class="ms-2" v-if="activity.gear_id">{{ gear.nickname }}</span>
<span class="ms-2" v-else>{{ $t("activity.labelGearNotSet") }}</span>
</p>
<div class="justify-content-end">
<!-- add gear button -->
<a class="btn btn-link btn-lg" href="#" role="button" data-bs-toggle="modal" data-bs-target="#addGearToActivityModal" v-if="!activity.gear_id">
<font-awesome-icon :icon="['fas', 'fa-plus']" />
</a>
<!-- edit gear button -->
<a class="btn btn-link btn-lg" href="#" role="button" data-bs-toggle="modal" data-bs-target="#editGearActivityModal" v-if="activity.gear_id">
<font-awesome-icon :icon="['far', 'fa-pen-to-square']" />
</a>
<!-- Delete zone -->
<a class="btn btn-link btn-lg" href="#" role="button" data-bs-toggle="modal" data-bs-target="#deleteGearActivityModal" v-if="activity.gear_id">
<font-awesome-icon :icon="['fas', 'fa-trash']" />
</a>
<template>
<LoadingComponent v-if="isLoading"/>
<div v-else>
<ActivitySummaryComponent v-if="activity" :activity="activity" :source="'activity'" />
</div>
</div>
<!-- graphs -->
<hr class="mb-2 mt-2">
<!-- map zone -->
<div class="mt-3 mb-3" v-if="isLoading">
<LoadingComponent />
</div>
<div class="mt-3 mb-3" v-else>
<ActivityMapComponent :activity="activity" :source="'activity'"/>
</div>
<!-- gear zone -->
<hr class="mb-2 mt-2">
<div class="mt-3 mb-3" v-if="isLoading">
<LoadingComponent />
</div>
<div class="d-flex justify-content-between align-items-center" v-else>
<p class="pt-2">
<span class="fw-lighter">
{{ $t("activity.labelGear") }}
</span>
<br>
<span v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3">
<font-awesome-icon :icon="['fas', 'person-running']" />
</span>
<span v-else-if="activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6 || activity.activity_type == 7">
<font-awesome-icon :icon="['fas', 'fa-person-biking']" />
</span>
<span v-else-if="activity.activity_type == 8 || activity.activity_type == 9">
<font-awesome-icon :icon="['fas', 'fa-person-swimming']" />
</span>
<span class="ms-2" v-if="activity.gear_id">{{ gear.nickname }}</span>
<span class="ms-2" v-else>{{ $t("activity.labelGearNotSet") }}</span>
</p>
<div class="justify-content-end">
<!-- add gear button -->
<a class="btn btn-link btn-lg" href="#" role="button" data-bs-toggle="modal" data-bs-target="#addGearToActivityModal" v-if="!activity.gear_id">
<font-awesome-icon :icon="['fas', 'fa-plus']" />
</a>
<div>
<br>
<button @click="goBack" type="button" class="w-100 btn btn-primary d-lg-none">{{ $t("generalItens.buttonBack") }}</button>
</div>
</template>
<!-- modal -->
<div class="modal fade" id="addGearToActivityModal" tabindex="-1" aria-labelledby="addGearToActivityModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addGearToActivityModal">
{{ $t("activity.modalLabelAddGear") }}
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<form @submit.prevent="submitAddGearToActivityForm">
<div class="modal-body">
<!-- gear type fields -->
<label for="gearIDAdd"><b>* {{ $t("activity.modalLabelSelectGear") }}</b></label>
<select class="form-control" name="gearIDAdd" required>
<option v-for="gear in gearsByType" :key="gear.id" :value="gear.id">
{{ gear.nickname }}
</option>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{{ $t("generalItens.buttonClose") }}
</button>
<button type="submit" class="btn btn-success" data-bs-dismiss="modal" name="addGearToActivity">
{{ $t("activity.modalButtonAddGear") }}
</button>
</div>
</form>
</div>
</div>
</div>
<script>
import { ref, onMounted, onUnmounted, watchEffect, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
// Importing the stores
import { useSuccessAlertStore } from '@/stores/Alerts/successAlert';
import { useErrorAlertStore } from '@/stores/Alerts/errorAlert';
// Importing the components
import ActivitySummaryComponent from '@/components/Activities/ActivitySummaryComponent.vue';
import ActivityMapComponent from '@/components/Activities/ActivityMapComponent.vue';
import NoItemsFoundComponent from '@/components/NoItemsFoundComponents.vue';
import ErrorAlertComponent from '@/components/Alerts/ErrorAlertComponent.vue';
import SuccessAlertComponent from '@/components/Alerts/SuccessAlertComponent.vue';
import LoadingComponent from '@/components/LoadingComponent.vue';
// Importing the services
import { gears } from '@/services/gears';
import { activities } from '@/services/activities';
export default {
components: {
NoItemsFoundComponent,
ActivitySummaryComponent,
ActivityMapComponent,
LoadingComponent,
ErrorAlertComponent,
SuccessAlertComponent,
},
setup (){
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const errorAlertStore = useErrorAlertStore();
const successAlertStore = useSuccessAlertStore();
const isLoading = ref(true);
const errorMessage = ref('');
const successMessage = ref('');
const activity = ref(null);
const gear = ref(null);
<!-- edit gear button -->
<a class="btn btn-link btn-lg" href="#" role="button" data-bs-toggle="modal" data-bs-target="#editGearActivityModal" v-if="activity.gear_id">
<font-awesome-icon :icon="['far', 'fa-pen-to-square']" />
</a>
/**
* Function to navigate back to the previous page.
*/
function goBack() {
route.go(-1);
}
<!-- Delete zone -->
<a class="btn btn-link btn-lg" href="#" role="button" data-bs-toggle="modal" data-bs-target="#deleteGearActivityModal" v-if="activity.gear_id">
<font-awesome-icon :icon="['fas', 'fa-trash']" />
</a>
</div>
</div>
onMounted(async () => {
try{
activity.value = await activities.getActivityById(route.params.id);
if (!activity.value) {
router.push({ path: '/', query: { activityFound: 'false' } });
}
if (activity.value.gear_id) {
gear.value = await gears.getGearById(activity.value.gear_id);
}
} catch (error) {
if (error.toString().includes('422')) {
router.push({ path: '/', query: { activityFound: 'false' } });
}
// If there is an error, set the error message and show the error alert.
errorMessage.value = t('generalItens.errorFetchingInfo') + " - " + error.toString();
errorAlertStore.setAlertMessage(errorMessage.value);
<!-- graphs -->
<hr class="mb-2 mt-2">
<div class="row">
<div class="col-md-2">
<p>{{ $t("activity.labelGraph") }}</p>
<ul class="nav nav-pills flex-column mb-auto" id="sidebarLineGraph">
<li class="nav-item" v-for="item in graphItems" :key="item.type">
<a href="javascript:void(0);" class="nav-link text-secondary"
:class="{ 'active text-white': graphSelection === item.type }"
@click="selectGraph(item.type)">
{{ item.label }}
</a>
</li>
</ul>
<p class="mt-2">{{ $t("activity.labelDownsampling") }}</p>
</div>
<div class="col">
<LoadingComponent v-if="isLoading"/>
<div v-else>
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'hr'"/>
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'power'"/>
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'cad'"/>
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'ele'"/>
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'vel'"/>
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'pace'"/>
</div>
</div>
</div>
<div>
<br>
<button @click="goBack" type="button" class="w-100 btn btn-primary d-lg-none">{{ $t("generalItens.buttonBack") }}</button>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
// Importing the stores
import { useSuccessAlertStore } from '@/stores/Alerts/successAlert';
import { useErrorAlertStore } from '@/stores/Alerts/errorAlert';
// Importing the components
import ActivitySummaryComponent from '@/components/Activities/ActivitySummaryComponent.vue';
import ActivityMapComponent from '@/components/Activities/ActivityMapComponent.vue';
import ActivityStreamsLineChartComponent from '@/components/Activities/ActivityStreamsLineChartComponent.vue';
import NoItemsFoundComponent from '@/components/NoItemsFoundComponents.vue';
import ErrorAlertComponent from '@/components/Alerts/ErrorAlertComponent.vue';
import SuccessAlertComponent from '@/components/Alerts/SuccessAlertComponent.vue';
import LoadingComponent from '@/components/LoadingComponent.vue';
// Importing the services
import { gears } from '@/services/gears';
import { activities } from '@/services/activities';
import { activityStreams } from '@/services/activityStreams';
export default {
components: {
NoItemsFoundComponent,
ActivitySummaryComponent,
ActivityMapComponent,
ActivityStreamsLineChartComponent,
LoadingComponent,
ErrorAlertComponent,
SuccessAlertComponent,
},
setup (){
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const errorAlertStore = useErrorAlertStore();
const successAlertStore = useSuccessAlertStore();
const isLoading = ref(true);
const errorMessage = ref('');
const successMessage = ref('');
const activity = ref(null);
const gear = ref(null);
const gearsByType = ref([]);
const activityActivityStreams = ref([]);
const graphSelection = ref('hr');
const graphItems = ref([
{ type: 'hr', label: 'HR' },
{ type: 'power', label: 'Power' },
{ type: 'ele', label: 'Elevation' },
{ type: 'cad', label: 'Cadence' },
{ type: 'pace', label: 'Pace' },
]);
function selectGraph(type) {
graphSelection.value = type;
}
isLoading.value = false;
});
/**
* Function to navigate back to the previous page.
*/
function goBack() {
route.go(-1);
}
return {
isLoading,
activity,
gear,
errorMessage,
successMessage,
goBack,
};
}
};
</script>
function submitAddGearToActivityForm() {
}
onMounted(async () => {
try{
activity.value = await activities.getActivityById(route.params.id);
if (!activity.value) {
router.push({ path: '/', query: { activityFound: 'false' } });
}
if (activity.value.gear_id) {
gear.value = await gears.getGearById(activity.value.gear_id);
}
if (activity.value.activity_type == 1 || activity.value.activity_type == 2 || activity.value.activity_type == 3) {
gearsByType.value = await gears.getGearFromType(2);
} else {
if (activity.value.activity_type == 4 || activity.value.activity_type == 5 || activity.value.activity_type == 6 || activity.value.activity_type == 7) {
gearsByType.value = await gears.getGearFromType(1);
graphItems.value = [
{ type: 'hr', label: 'HR' },
{ type: 'power', label: 'Power' },
{ type: 'ele', label: 'Elevation' },
{ type: 'cad', label: 'Cadence' },
{ type: 'vel', label: 'Velocity' },
]
} else {
if (activity.value.activity_type == 8 || activity.value.activity_type == 9) {
gearsByType.value = await gears.getGearFromType(3);
}
}
}
activityActivityStreams.value = await activityStreams.getActivitySteamsByActivityId(route.params.id);
} catch (error) {
if (error.toString().includes('422')) {
router.push({ path: '/', query: { activityFound: 'false' } });
}
// If there is an error, set the error message and show the error alert.
errorMessage.value = t('generalItens.errorFetchingInfo') + " - " + error.toString();
errorAlertStore.setAlertMessage(errorMessage.value);
}
isLoading.value = false;
});
return {
isLoading,
activity,
gear,
activityActivityStreams,
errorMessage,
successMessage,
goBack,
gearsByType,
submitAddGearToActivityForm,
graphSelection,
graphItems,
selectGraph
};
}
};
</script>

View File

@@ -177,7 +177,7 @@
<script>
// Importing the vue composition API
import { ref, onMounted, onUnmounted, watchEffect, watch } from 'vue';
import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
// Importing the stores
@@ -249,7 +249,7 @@ export default {
successMessage.value = t('gear.successGearEdited');
successAlertStore.setAlertMessage(successMessage.value);
successAlertStore.setClosableState(true);
} catch {
} catch (error) {
// If there is an error, set the error message and show the error alert.
errorMessage.value = t('generalItens.errorEditingInfo') + " - " + error.toString();
errorAlertStore.setAlertMessage(errorMessage.value);

View File

@@ -63,6 +63,17 @@
<!-- Success banners -->
<SuccessAlertComponent v-if="successMessage"/>
<div v-if="isLoadingUploadActivity">
<div class="alert alert-primary d-flex align-items-center" role="alert">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="ms-1">
<span>{{ $t("home.processingActivity") }}</span>
</div>
</div>
</div>
<!-- radio button -->
<div class="btn-group mb-3 d-flex" role="group" aria-label="Activities radio toggle button group">
<!-- user activities -->
@@ -114,7 +125,7 @@
<script>
import { ref, onMounted, onUnmounted, watchEffect, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useRoute } from 'vue-router';
import { useUserStore } from '@/stores/user';
import { activities } from '@/services/activities';
// Importing the stores
@@ -143,12 +154,12 @@ export default {
},
setup() {
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const successAlertStore = useSuccessAlertStore();
const errorAlertStore = useErrorAlertStore();
const selectedActivityView = ref('userActivities');
const isLoading = ref(true);
const isLoadingUploadActivity = ref(false);
const activityFound = ref(true);
const userMe = computed(() => userStore.userMe);
const thisWeekDistances = computed(() => userStore.thisWeekDistances);
@@ -194,6 +205,8 @@ export default {
};
const submitUploadFileForm = async () => {
// Set the loading state
isLoadingUploadActivity.value = true;
// Get the file input
const fileInput = document.querySelector('input[type="file"]');
@@ -220,10 +233,17 @@ export default {
if (modalInstance) {
modalInstance.hide();
} */
}catch (error) {
// Fetch the user stats
await userStore.fetchUserStats();
// Fetch the user activities and user activities number
await userStore.fetchUserActivitiesNumber();
} catch (error) {
// Set the error message
errorMessage.value = t('generalItens.errorFetchingInfo') + " - " + error.toString();
errorAlertStore.setAlertMessage(errorMessage.value);
} finally {
// Set the loading state
isLoadingUploadActivity.value = false;
}
}
};
@@ -272,6 +292,7 @@ export default {
return {
selectedActivityView,
isLoading,
isLoadingUploadActivity,
userMe,
thisWeekDistances,
thisMonthDistances,