Client - use <script setup> in components
This commit is contained in:
parent
857c0ecd2d
commit
1bede62d80
2
fittrackee/dist/index.html
vendored
2
fittrackee/dist/index.html
vendored
@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"><link rel="stylesheet" href="/static/css/leaflet.css"><title>FitTrackee</title><link href="/static/css/admin.2e1912ed.css" rel="prefetch"><link href="/static/css/main.0baa26a6.css" rel="prefetch"><link href="/static/css/main~workouts.2563ccfd.css" rel="prefetch"><link href="/static/css/profile.14a2947f.css" rel="prefetch"><link href="/static/css/reset.3e6931c7.css" rel="prefetch"><link href="/static/css/workouts.d952f3cf.css" rel="prefetch"><link href="/static/js/admin.4047df15.js" rel="prefetch"><link href="/static/js/chunk-2d0c9189.c81458cc.js" rel="prefetch"><link href="/static/js/chunk-2d0cf391.020c75ea.js" rel="prefetch"><link href="/static/js/chunk-2d0da8f3.c8c3e7e8.js" rel="prefetch"><link href="/static/js/chunk-2d2248b6.d84473c1.js" rel="prefetch"><link href="/static/js/chunk-2d22523a.4b710d99.js" rel="prefetch"><link href="/static/js/main.265d6693.js" rel="prefetch"><link href="/static/js/main~workouts.aa540c70.js" rel="prefetch"><link href="/static/js/profile.7e87449f.js" rel="prefetch"><link href="/static/js/reset.98679f6c.js" rel="prefetch"><link href="/static/js/workouts.52ba33b8.js" rel="prefetch"><link href="/static/css/app.88c1cb13.css" rel="preload" as="style"><link href="/static/js/app.e01bf3f7.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.5928fb7f.js" rel="preload" as="script"><link href="/static/css/app.88c1cb13.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.5928fb7f.js"></script><script src="/static/js/app.e01bf3f7.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"><link rel="stylesheet" href="/static/css/leaflet.css"><title>FitTrackee</title><link href="/static/css/admin.e96a4210.css" rel="prefetch"><link href="/static/css/main.2ee3c9ac.css" rel="prefetch"><link href="/static/css/main~workouts.ca97ac72.css" rel="prefetch"><link href="/static/css/profile.b792256b.css" rel="prefetch"><link href="/static/css/reset.dc50c1a7.css" rel="prefetch"><link href="/static/css/workouts.9c79c3ad.css" rel="prefetch"><link href="/static/js/admin.2f1d393d.js" rel="prefetch"><link href="/static/js/chunk-2d0c9189.c81458cc.js" rel="prefetch"><link href="/static/js/chunk-2d0cf391.020c75ea.js" rel="prefetch"><link href="/static/js/chunk-2d0da8f3.c8c3e7e8.js" rel="prefetch"><link href="/static/js/chunk-2d2248b6.d84473c1.js" rel="prefetch"><link href="/static/js/chunk-2d22523a.4b710d99.js" rel="prefetch"><link href="/static/js/main.c3f36893.js" rel="prefetch"><link href="/static/js/main~workouts.a74990d7.js" rel="prefetch"><link href="/static/js/profile.6a786c1d.js" rel="prefetch"><link href="/static/js/reset.6f6516bc.js" rel="prefetch"><link href="/static/js/workouts.ed2b92d6.js" rel="prefetch"><link href="/static/css/app.1b990916.css" rel="preload" as="style"><link href="/static/js/app.47532afb.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.71654064.js" rel="preload" as="script"><link href="/static/css/app.1b990916.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.71654064.js"></script><script src="/static/js/app.47532afb.js"></script></body></html>
|
@ -64,7 +64,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
"url": "/img/workouts/mountains.svg"
|
||||
},
|
||||
{
|
||||
"revision": "ac27cacdce535196c127b4f950b04fcf",
|
||||
"revision": "2d61b8f556cfd6b2fd8cb6bb5d04b8e3",
|
||||
"url": "/index.html"
|
||||
},
|
||||
{
|
||||
@ -76,12 +76,12 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
"url": "/robots.txt"
|
||||
},
|
||||
{
|
||||
"revision": "2a55f1c804bfc2c51c03",
|
||||
"url": "/static/css/admin.2e1912ed.css"
|
||||
"revision": "a1d50ec70acd25b1b70a",
|
||||
"url": "/static/css/admin.e96a4210.css"
|
||||
},
|
||||
{
|
||||
"revision": "45aeb8689961e0a5de78",
|
||||
"url": "/static/css/app.88c1cb13.css"
|
||||
"revision": "abfdfe6ff6e351d4cef8",
|
||||
"url": "/static/css/app.1b990916.css"
|
||||
},
|
||||
{
|
||||
"revision": "82c1118c918377daaa71a320ab8eea42",
|
||||
@ -92,24 +92,24 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
"url": "/static/css/leaflet.css"
|
||||
},
|
||||
{
|
||||
"revision": "4f21a8566a32a3eaa6cc",
|
||||
"url": "/static/css/main.0baa26a6.css"
|
||||
"revision": "2afbe9449a719e10f79a",
|
||||
"url": "/static/css/main.2ee3c9ac.css"
|
||||
},
|
||||
{
|
||||
"revision": "44ec9db1f0dd0bd662ad",
|
||||
"url": "/static/css/main~workouts.2563ccfd.css"
|
||||
"revision": "d37b7cc3f4be1d095e98",
|
||||
"url": "/static/css/main~workouts.ca97ac72.css"
|
||||
},
|
||||
{
|
||||
"revision": "d13a074cdc41830448c5",
|
||||
"url": "/static/css/profile.14a2947f.css"
|
||||
"revision": "46a831b0d7a94cbae773",
|
||||
"url": "/static/css/profile.b792256b.css"
|
||||
},
|
||||
{
|
||||
"revision": "eaff42a53248eac38103",
|
||||
"url": "/static/css/reset.3e6931c7.css"
|
||||
"revision": "9ef2fa45c16d6788a6e2",
|
||||
"url": "/static/css/reset.dc50c1a7.css"
|
||||
},
|
||||
{
|
||||
"revision": "c3c3a3c7444bd3448bda",
|
||||
"url": "/static/css/workouts.d952f3cf.css"
|
||||
"revision": "8a364ff438b4214d5d65",
|
||||
"url": "/static/css/workouts.9c79c3ad.css"
|
||||
},
|
||||
{
|
||||
"revision": "e719f9244c69e28e7d00e725ca1e280e",
|
||||
@ -192,12 +192,12 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
"url": "/static/img/pt-sans-v9-latin-regular.f1f73e45.svg"
|
||||
},
|
||||
{
|
||||
"revision": "2a55f1c804bfc2c51c03",
|
||||
"url": "/static/js/admin.4047df15.js"
|
||||
"revision": "a1d50ec70acd25b1b70a",
|
||||
"url": "/static/js/admin.2f1d393d.js"
|
||||
},
|
||||
{
|
||||
"revision": "45aeb8689961e0a5de78",
|
||||
"url": "/static/js/app.e01bf3f7.js"
|
||||
"revision": "abfdfe6ff6e351d4cef8",
|
||||
"url": "/static/js/app.47532afb.js"
|
||||
},
|
||||
{
|
||||
"revision": "bd7d183c9f68e5f4027d",
|
||||
@ -220,27 +220,27 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
"url": "/static/js/chunk-2d22523a.4b710d99.js"
|
||||
},
|
||||
{
|
||||
"revision": "639fd68690c8714c6f5c",
|
||||
"url": "/static/js/chunk-vendors.5928fb7f.js"
|
||||
"revision": "1631aa1204c2ef00fa57",
|
||||
"url": "/static/js/chunk-vendors.71654064.js"
|
||||
},
|
||||
{
|
||||
"revision": "4f21a8566a32a3eaa6cc",
|
||||
"url": "/static/js/main.265d6693.js"
|
||||
"revision": "2afbe9449a719e10f79a",
|
||||
"url": "/static/js/main.c3f36893.js"
|
||||
},
|
||||
{
|
||||
"revision": "44ec9db1f0dd0bd662ad",
|
||||
"url": "/static/js/main~workouts.aa540c70.js"
|
||||
"revision": "d37b7cc3f4be1d095e98",
|
||||
"url": "/static/js/main~workouts.a74990d7.js"
|
||||
},
|
||||
{
|
||||
"revision": "d13a074cdc41830448c5",
|
||||
"url": "/static/js/profile.7e87449f.js"
|
||||
"revision": "46a831b0d7a94cbae773",
|
||||
"url": "/static/js/profile.6a786c1d.js"
|
||||
},
|
||||
{
|
||||
"revision": "eaff42a53248eac38103",
|
||||
"url": "/static/js/reset.98679f6c.js"
|
||||
"revision": "9ef2fa45c16d6788a6e2",
|
||||
"url": "/static/js/reset.6f6516bc.js"
|
||||
},
|
||||
{
|
||||
"revision": "c3c3a3c7444bd3448bda",
|
||||
"url": "/static/js/workouts.52ba33b8.js"
|
||||
"revision": "8a364ff438b4214d5d65",
|
||||
"url": "/static/js/workouts.ed2b92d6.js"
|
||||
}
|
||||
]);
|
2
fittrackee/dist/service-worker.js
vendored
2
fittrackee/dist/service-worker.js
vendored
@ -14,7 +14,7 @@
|
||||
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||
|
||||
importScripts(
|
||||
"/precache-manifest.4d775da2b435165a0140d391654cdbc7.js"
|
||||
"/precache-manifest.2c04e97988213eb1aa1ce038eb4a9e1d.js"
|
||||
);
|
||||
|
||||
workbox.core.setCacheNameDetails({prefix: "fittrackee_client"});
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/main.0baa26a6.css
vendored
1
fittrackee/dist/static/css/main.0baa26a6.css
vendored
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/main.2ee3c9ac.css
vendored
Normal file
1
fittrackee/dist/static/css/main.2ee3c9ac.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/workouts.9c79c3ad.css
vendored
Normal file
1
fittrackee/dist/static/css/workouts.9c79c3ad.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/admin.2f1d393d.js
vendored
Normal file
2
fittrackee/dist/static/js/admin.2f1d393d.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["admin"],{3409:function(e,t,n){"use strict";n("ca8b")},"4d13":function(e,t,n){"use strict";n("4fbb")},"4fbb":function(e,t,n){},"89b3":function(e,t,n){"use strict";n.r(t);var c=n("7a23"),o=n("f7f9"),r=n("dad5"),u=n("2906"),a=function(e){return Object(c["pushScopeId"])("data-v-21f8956c"),e=e(),Object(c["popScopeId"])(),e},b={id:"admin",class:"view"},i={key:0,class:"container"},s=a((function(){return Object(c["createElementVNode"])("div",{id:"bottom"},null,-1)})),O=Object(c["defineComponent"])({setup:function(e){var t=Object(u["a"])(),n=Object(c["computed"])((function(){return t.getters[r["b"].GETTERS.APP_CONFIG]})),a=Object(c["computed"])((function(){return t.getters[r["b"].GETTERS.APP_STATS]})),O=Object(c["computed"])((function(){return t.getters[r["a"].GETTERS.IS_ADMIN]})),l=Object(c["computed"])((function(){return t.getters[r["a"].GETTERS.USER_LOADING]}));return Object(c["onBeforeMount"])((function(){return t.dispatch(r["b"].ACTIONS.GET_APPLICATION_STATS)})),function(e,t){var r=Object(c["resolveComponent"])("router-view");return Object(c["openBlock"])(),Object(c["createElementBlock"])("div",b,[Object(c["unref"])(l)?Object(c["createCommentVNode"])("",!0):(Object(c["openBlock"])(),Object(c["createElementBlock"])("div",i,[Object(c["unref"])(O)?(Object(c["openBlock"])(),Object(c["createBlock"])(r,{key:0,appConfig:Object(c["unref"])(n),appStatistics:Object(c["unref"])(a)},null,8,["appConfig","appStatistics"])):(Object(c["openBlock"])(),Object(c["createBlock"])(o["a"],{key:1})),s]))])}}}),l=(n("4d13"),n("6b0d")),p=n.n(l);const f=p()(O,[["__scopeId","data-v-21f8956c"]]);t["default"]=f},ca8b:function(e,t,n){},f7f9:function(e,t,n){"use strict";var c=n("7a23"),o={id:"error"},r={class:"error-content"},u=Object(c["defineComponent"])({props:{title:null,message:null,buttonText:null,path:{default:"/"}},setup:function(e){var t=e,n=Object(c["toRefs"])(t),u=n.buttonText,a=n.title,b=n.message,i=n.path;return function(e,t){return Object(c["openBlock"])(),Object(c["createElementBlock"])("div",o,[Object(c["createElementVNode"])("div",r,[Object(c["createElementVNode"])("h1",null,Object(c["toDisplayString"])(Object(c["unref"])(a)),1),Object(c["createElementVNode"])("p",null,Object(c["toDisplayString"])(Object(c["unref"])(b)),1),Object(c["unref"])(u)?(Object(c["openBlock"])(),Object(c["createElementBlock"])("button",{key:0,onClick:t[0]||(t[0]=function(t){return e.$router.push(Object(c["unref"])(i))}),class:"upper"},Object(c["toDisplayString"])(Object(c["unref"])(u)),1)):Object(c["createCommentVNode"])("",!0)])])}}}),a=(n("3409"),n("6b0d")),b=n.n(a);const i=b()(u,[["__scopeId","data-v-79ec50fd"]]);var s=i,O=Object(c["defineComponent"])({props:{target:{default:"PAGE"}},setup:function(e){var t=e,n=Object(c["toRefs"])(t),o=n.target;return function(e,t){return Object(c["openBlock"])(),Object(c["createBlock"])(s,{title:"404",message:e.$t("error.NOT_FOUND.".concat(Object(c["unref"])(o))),"button-text":e.$t("common.HOME")},null,8,["message","button-text"])}}});const l=O;t["a"]=l}}]);
|
||||
//# sourceMappingURL=admin.2f1d393d.js.map
|
1
fittrackee/dist/static/js/admin.2f1d393d.js.map
vendored
Normal file
1
fittrackee/dist/static/js/admin.2f1d393d.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/admin.4047df15.js
vendored
2
fittrackee/dist/static/js/admin.4047df15.js
vendored
@ -1,2 +0,0 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["admin"],{"726e":function(e,t,n){},8185:function(e,t,n){"use strict";n("a27b")},"89b3":function(e,t,n){"use strict";n.r(t);var o=n("7a23"),c=function(e){return Object(o["pushScopeId"])("data-v-2fbe41f1"),e=e(),Object(o["popScopeId"])(),e},r={id:"admin",class:"view"},a={key:0,class:"container"},i=c((function(){return Object(o["createElementVNode"])("div",{id:"bottom"},null,-1)}));function u(e,t,n,c,u,p){var s=Object(o["resolveComponent"])("router-view"),b=Object(o["resolveComponent"])("NotFound");return Object(o["openBlock"])(),Object(o["createElementBlock"])("div",r,[e.userLoading?Object(o["createCommentVNode"])("",!0):(Object(o["openBlock"])(),Object(o["createElementBlock"])("div",a,[e.isAuthUserAmin?(Object(o["openBlock"])(),Object(o["createBlock"])(s,{key:0,appConfig:e.appConfig,appStatistics:e.appStatistics},null,8,["appConfig","appStatistics"])):(Object(o["openBlock"])(),Object(o["createBlock"])(b,{key:1})),i]))])}var p=n("f7f9"),s=n("dad5"),b=n("2906"),d=Object(o["defineComponent"])({name:"Admin",components:{NotFound:p["a"]},setup:function(){var e=Object(b["a"])();Object(o["onBeforeMount"])((function(){return e.dispatch(s["b"].ACTIONS.GET_APPLICATION_STATS)}));var t=Object(o["computed"])((function(){return e.getters[s["b"].GETTERS.APP_LOADING]})),n=Object(o["computed"])((function(){return e.getters[s["b"].GETTERS.APP_CONFIG]})),c=Object(o["computed"])((function(){return e.getters[s["b"].GETTERS.APP_STATS]})),r=Object(o["computed"])((function(){return e.getters[s["a"].GETTERS.IS_ADMIN]})),a=Object(o["computed"])((function(){return e.getters[s["a"].GETTERS.USER_LOADING]}));return{appConfig:n,appLoading:t,appStatistics:c,isAuthUserAmin:r,userLoading:a}}}),l=(n("d14e"),n("6b0d")),O=n.n(l);const m=O()(d,[["render",u],["__scopeId","data-v-2fbe41f1"]]);t["default"]=m},a27b:function(e,t,n){},d14e:function(e,t,n){"use strict";n("726e")},f7f9:function(e,t,n){"use strict";var o=n("7a23");function c(e,t,n,c,r,a){var i=Object(o["resolveComponent"])("Error");return Object(o["openBlock"])(),Object(o["createBlock"])(i,{title:"404",message:e.$t("error.NOT_FOUND.".concat(e.target)),"button-text":e.$t("common.HOME")},null,8,["message","button-text"])}var r={id:"error"},a={class:"error-content"};function i(e,t,n,c,i,u){return Object(o["openBlock"])(),Object(o["createElementBlock"])("div",r,[Object(o["createElementVNode"])("div",a,[Object(o["createElementVNode"])("h1",null,Object(o["toDisplayString"])(e.title),1),Object(o["createElementVNode"])("p",null,Object(o["toDisplayString"])(e.message),1),e.buttonText?(Object(o["openBlock"])(),Object(o["createElementBlock"])("button",{key:0,onClick:t[0]||(t[0]=function(t){return e.$router.push(e.path)}),class:"upper"},Object(o["toDisplayString"])(e.buttonText),1)):Object(o["createCommentVNode"])("",!0)])])}var u=Object(o["defineComponent"])({name:"Error",props:{title:{type:String,required:!0},message:{type:String},buttonText:{type:String},path:{type:String,default:"/"}}}),p=(n("8185"),n("6b0d")),s=n.n(p);const b=s()(u,[["render",i],["__scopeId","data-v-58e20d75"]]);var d=b,l=Object(o["defineComponent"])({name:"NotFound",components:{Error:d},props:{target:{type:String,default:"PAGE"}}});const O=s()(l,[["render",c]]);t["a"]=O}}]);
|
||||
//# sourceMappingURL=admin.4047df15.js.map
|
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/app.47532afb.js
vendored
Normal file
2
fittrackee/dist/static/js/app.47532afb.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/app.47532afb.js.map
vendored
Normal file
1
fittrackee/dist/static/js/app.47532afb.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/app.e01bf3f7.js
vendored
2
fittrackee/dist/static/js/app.e01bf3f7.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/chunk-vendors.71654064.js.map
vendored
Normal file
1
fittrackee/dist/static/js/chunk-vendors.71654064.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/main.265d6693.js
vendored
2
fittrackee/dist/static/js/main.265d6693.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/main.c3f36893.js
vendored
Normal file
2
fittrackee/dist/static/js/main.c3f36893.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/main.c3f36893.js.map
vendored
Normal file
1
fittrackee/dist/static/js/main.c3f36893.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/main~workouts.a74990d7.js
vendored
Normal file
2
fittrackee/dist/static/js/main~workouts.a74990d7.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/main~workouts.a74990d7.js.map
vendored
Normal file
1
fittrackee/dist/static/js/main~workouts.a74990d7.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/profile.6a786c1d.js
vendored
Normal file
2
fittrackee/dist/static/js/profile.6a786c1d.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["profile"],{"36e8":function(e,t,n){"use strict";n.r(t);var c=n("7a23"),r=n("dad5"),o=n("2906"),u={key:0,id:"profile",class:"container view"},a=Object(c["defineComponent"])({setup:function(e){var t=Object(o["a"])(),n=Object(c["computed"])((function(){return t.getters[r["a"].GETTERS.AUTH_USER_PROFILE]}));return function(e,t){var r=Object(c["resolveComponent"])("router-view");return Object(c["unref"])(n).username?(Object(c["openBlock"])(),Object(c["createElementBlock"])("div",u,[Object(c["createVNode"])(r,{user:Object(c["unref"])(n)},null,8,["user"])])):Object(c["createCommentVNode"])("",!0)}}}),s=(n("44ab"),n("6b0d")),b=n.n(s);const i=b()(a,[["__scopeId","data-v-bb090bfa"]]);t["default"]=i},"44ab":function(e,t,n){"use strict";n("4fe6")},"4fe6":function(e,t,n){},"9b98":function(e,t,n){"use strict";n("d332")},ad3d:function(e,t,n){"use strict";n.r(t);var c=n("7a23"),r=n("6c02"),o=n("3c44"),u=n("71a7"),a=n("dad5"),s=n("2906"),b={key:0,id:"user",class:"view"},i={class:"box"},d=Object(c["defineComponent"])({setup:function(e){var t=Object(r["c"])(),n=Object(s["a"])(),d=Object(c["computed"])((function(){return n.getters[a["e"].GETTERS.USER]}));return Object(c["onBeforeMount"])((function(){t.params.username&&"string"===typeof t.params.username&&n.dispatch(a["e"].ACTIONS.GET_USER,t.params.username)})),Object(c["onBeforeUnmount"])((function(){n.dispatch(a["e"].ACTIONS.EMPTY_USER)})),function(e,t){return Object(c["unref"])(d).username?(Object(c["openBlock"])(),Object(c["createElementBlock"])("div",b,[Object(c["createVNode"])(o["a"],{user:Object(c["unref"])(d)},null,8,["user"]),Object(c["createElementVNode"])("div",i,[Object(c["createVNode"])(u["a"],{user:Object(c["unref"])(d),"from-admin":!0},null,8,["user"])])])):Object(c["createCommentVNode"])("",!0)}}}),f=(n("9b98"),n("6b0d")),O=n.n(f);const j=O()(d,[["__scopeId","data-v-218f8f1e"]]);t["default"]=j},d332:function(e,t,n){}}]);
|
||||
//# sourceMappingURL=profile.6a786c1d.js.map
|
1
fittrackee/dist/static/js/profile.6a786c1d.js.map
vendored
Normal file
1
fittrackee/dist/static/js/profile.6a786c1d.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,2 +0,0 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["profile"],{"0ab6":function(e,t,n){},"36e8":function(e,t,n){"use strict";n.r(t);var r=n("7a23"),c={key:0,id:"profile",class:"container view"};function o(e,t,n,o,a,s){var u=Object(r["resolveComponent"])("router-view");return e.authUser.username?(Object(r["openBlock"])(),Object(r["createElementBlock"])("div",c,[Object(r["createVNode"])(u,{user:e.authUser},null,8,["user"])])):Object(r["createCommentVNode"])("",!0)}var a=n("dad5"),s=n("2906"),u=Object(r["defineComponent"])({name:"ProfileView",setup:function(){var e=Object(s["a"])(),t=Object(r["computed"])((function(){return e.getters[a["a"].GETTERS.AUTH_USER_PROFILE]}));return{authUser:t}}}),i=(n("a6f2"),n("6b0d")),d=n.n(i);const f=d()(u,[["render",o],["__scopeId","data-v-37d55f74"]]);t["default"]=f},"7ffc":function(e,t,n){"use strict";n("b288")},a6f2:function(e,t,n){"use strict";n("0ab6")},ad3d:function(e,t,n){"use strict";n.r(t);var r=n("7a23"),c={key:0,id:"user",class:"view"},o={class:"box"};function a(e,t,n,a,s,u){var i=Object(r["resolveComponent"])("UserHeader"),d=Object(r["resolveComponent"])("UserInfos");return e.user.username?(Object(r["openBlock"])(),Object(r["createElementBlock"])("div",c,[Object(r["createVNode"])(i,{user:e.user},null,8,["user"]),Object(r["createElementVNode"])("div",o,[Object(r["createVNode"])(d,{user:e.user,"from-admin":!0},null,8,["user"])])])):Object(r["createCommentVNode"])("",!0)}var s=n("6c02"),u=n("3c44"),i=n("71a7"),d=n("dad5"),f=n("2906"),b=Object(r["defineComponent"])({name:"UserView",components:{UserHeader:u["a"],UserInfos:i["a"]},setup:function(){var e=Object(s["c"])(),t=Object(f["a"])(),n=Object(r["computed"])((function(){return t.getters[d["e"].GETTERS.USER]}));return Object(r["onBeforeMount"])((function(){e.params.username&&"string"===typeof e.params.username&&t.dispatch(d["e"].ACTIONS.GET_USER,e.params.username)})),Object(r["onBeforeUnmount"])((function(){t.dispatch(d["e"].ACTIONS.EMPTY_USER)})),{user:n}}}),m=(n("7ffc"),n("6b0d")),p=n.n(m);const O=p()(b,[["render",a],["__scopeId","data-v-82f4bbf6"]]);t["default"]=O},b288:function(e,t,n){}}]);
|
||||
//# sourceMappingURL=profile.7e87449f.js.map
|
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/reset.6f6516bc.js
vendored
Normal file
2
fittrackee/dist/static/js/reset.6f6516bc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/reset.6f6516bc.js.map
vendored
Normal file
1
fittrackee/dist/static/js/reset.6f6516bc.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/reset.98679f6c.js
vendored
2
fittrackee/dist/static/js/reset.98679f6c.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/workouts.ed2b92d6.js
vendored
Normal file
2
fittrackee/dist/static/js/workouts.ed2b92d6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/workouts.ed2b92d6.js.map
vendored
Normal file
1
fittrackee/dist/static/js/workouts.ed2b92d6.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -70,6 +70,12 @@
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript"
|
||||
],
|
||||
"globals": {
|
||||
"defineProps": "readonly",
|
||||
"defineEmits": "readonly",
|
||||
"defineExpose": "readonly",
|
||||
"withDefaults": "readonly"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": "typescript"
|
||||
},
|
||||
|
@ -22,15 +22,8 @@
|
||||
<Footer v-if="appConfig" :version="appConfig ? appConfig.version : ''" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, ref, onBeforeMount, onMounted } from 'vue'
|
||||
|
||||
import Footer from '@/components/Footer.vue'
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
@ -39,67 +32,44 @@
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
Footer,
|
||||
NavBar,
|
||||
NoConfig,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const store = useStore()
|
||||
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const appLoading: ComputedRef<boolean> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_LOADING]
|
||||
)
|
||||
const hideScrollBar = ref(false)
|
||||
const displayScrollButton = ref(false)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const appLoading: ComputedRef<boolean> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_LOADING]
|
||||
)
|
||||
const hideScrollBar = ref(false)
|
||||
const displayScrollButton = ref(false)
|
||||
|
||||
onBeforeMount(() =>
|
||||
store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG)
|
||||
)
|
||||
onMounted(() => scroll())
|
||||
onBeforeMount(() => store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG))
|
||||
onMounted(() => scroll())
|
||||
|
||||
function updateHideScrollBar(isMenuOpen: boolean) {
|
||||
hideScrollBar.value = isMenuOpen
|
||||
}
|
||||
|
||||
function isScrolledToBottom(element: Element): boolean {
|
||||
return (
|
||||
element.getBoundingClientRect().top < window.innerHeight &&
|
||||
element.getBoundingClientRect().bottom >= 0
|
||||
)
|
||||
}
|
||||
function scroll() {
|
||||
window.onscroll = () => {
|
||||
let bottom = document.querySelector('#bottom')
|
||||
displayScrollButton.value =
|
||||
bottom !== null && isScrolledToBottom(bottom)
|
||||
}
|
||||
}
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
setTimeout(() => {
|
||||
displayScrollButton.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
appLoading,
|
||||
hideScrollBar,
|
||||
displayScrollButton,
|
||||
scrollToTop,
|
||||
updateHideScrollBar,
|
||||
}
|
||||
},
|
||||
})
|
||||
function updateHideScrollBar(isMenuOpen: boolean) {
|
||||
hideScrollBar.value = isMenuOpen
|
||||
}
|
||||
function isScrolledToBottom(element: Element): boolean {
|
||||
return (
|
||||
element.getBoundingClientRect().top < window.innerHeight &&
|
||||
element.getBoundingClientRect().bottom >= 0
|
||||
)
|
||||
}
|
||||
function scroll() {
|
||||
window.onscroll = () => {
|
||||
let bottom = document.querySelector('#bottom')
|
||||
displayScrollButton.value = bottom !== null && isScrolledToBottom(bottom)
|
||||
}
|
||||
}
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
setTimeout(() => {
|
||||
displayScrollButton.value = false
|
||||
}, 300)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -82,13 +82,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
withDefaults,
|
||||
onBeforeMount,
|
||||
} from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -98,64 +97,55 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getFileSizeInMB } from '@/utils/files'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminApplication',
|
||||
props: {
|
||||
appConfig: {
|
||||
type: Object as PropType<TAppConfig>,
|
||||
required: true,
|
||||
},
|
||||
edition: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const appData: TAppConfigForm = reactive({
|
||||
max_users: 0,
|
||||
max_single_file_size: 0,
|
||||
max_zip_file_size: 0,
|
||||
gpx_limit_import: 0,
|
||||
})
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.appConfig) {
|
||||
updateForm(props.appConfig)
|
||||
}
|
||||
})
|
||||
|
||||
function updateForm(appConfig: TAppConfig) {
|
||||
Object.keys(appData).map((key) => {
|
||||
;['max_single_file_size', 'max_zip_file_size'].includes(key)
|
||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = getFileSizeInMB(appConfig[key]))
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = appConfig[key])
|
||||
})
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
updateForm(props.appConfig)
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
router.push('/admin/application')
|
||||
}
|
||||
function onSubmit() {
|
||||
const formData: TAppConfigForm = Object.assign({}, appData)
|
||||
formData.max_single_file_size *= 1048576
|
||||
formData.max_zip_file_size *= 1048576
|
||||
store.dispatch(ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG, formData)
|
||||
}
|
||||
|
||||
return { appData, errorMessages, onCancel, onSubmit }
|
||||
},
|
||||
interface Props {
|
||||
appConfig: TAppConfig
|
||||
edition?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
edition: false,
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const appData: TAppConfigForm = reactive({
|
||||
max_users: 0,
|
||||
max_single_file_size: 0,
|
||||
max_zip_file_size: 0,
|
||||
gpx_limit_import: 0,
|
||||
})
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.appConfig) {
|
||||
updateForm(props.appConfig)
|
||||
}
|
||||
})
|
||||
|
||||
function updateForm(appConfig: TAppConfig) {
|
||||
Object.keys(appData).map((key) => {
|
||||
;['max_single_file_size', 'max_zip_file_size'].includes(key)
|
||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = getFileSizeInMB(appConfig[key]))
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = appConfig[key])
|
||||
})
|
||||
}
|
||||
function onCancel() {
|
||||
updateForm(props.appConfig)
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
router.push('/admin/application')
|
||||
}
|
||||
function onSubmit() {
|
||||
const formData: TAppConfigForm = Object.assign({}, appData)
|
||||
formData.max_single_file_size *= 1048576
|
||||
formData.max_zip_file_size *= 1048576
|
||||
store.dispatch(ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG, formData)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<Card>
|
||||
<template #title>{{ $t('admin.ADMINISTRATION') }}</template>
|
||||
<template #content>
|
||||
<AppStatsCards :app-statistics="appStatistics" />
|
||||
<AppStatsCards :appStatistics="appStatistics" />
|
||||
<div class="admin-menu description-list">
|
||||
<dl>
|
||||
<dt>
|
||||
@ -46,32 +46,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, capitalize, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { capitalize, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import AppStatsCards from '@/components/Administration/AppStatsCards.vue'
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
import { IAppStatistics, TAppConfig } from '@/types/application'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminMenu',
|
||||
components: {
|
||||
AppStatsCards,
|
||||
Card,
|
||||
},
|
||||
props: {
|
||||
appConfig: {
|
||||
type: Object as PropType<TAppConfig>,
|
||||
required: true,
|
||||
},
|
||||
appStatistics: {
|
||||
type: Object as PropType<IAppStatistics>,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return { capitalize }
|
||||
},
|
||||
interface Props {
|
||||
appConfig: TAppConfig
|
||||
appStatistics?: IAppStatistics
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
appStatistics: () => ({} as IAppStatistics),
|
||||
})
|
||||
|
||||
const { appConfig, appStatistics } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -82,8 +82,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { ROOT_STORE, SPORTS_STORE } from '@/store/constants'
|
||||
@ -91,28 +91,22 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminSports',
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(store.getters[SPORTS_STORE.GETTERS.SPORTS], t)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
function updateSportStatus(id: number, isActive: boolean) {
|
||||
store.dispatch(SPORTS_STORE.ACTIONS.UPDATE_SPORTS, {
|
||||
id,
|
||||
isActive,
|
||||
})
|
||||
}
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(store.getters[SPORTS_STORE.GETTERS.SPORTS], t)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
return { errorMessages, translatedSports, updateSportStatus }
|
||||
},
|
||||
})
|
||||
function updateSportStatus(id: number, isActive: boolean) {
|
||||
store.dispatch(SPORTS_STORE.ACTIONS.UPDATE_SPORTS, {
|
||||
id,
|
||||
isActive,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -115,12 +115,11 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
watch,
|
||||
capitalize,
|
||||
@ -139,89 +138,63 @@
|
||||
import { getQuery, sortList } from '@/utils/api'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminUsers',
|
||||
components: {
|
||||
FilterSelects,
|
||||
Pagination,
|
||||
UserPicture,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const orderByList: string[] = [
|
||||
'admin',
|
||||
'created_at',
|
||||
'username',
|
||||
'workouts_count',
|
||||
]
|
||||
const defaultOrderBy = 'created_at'
|
||||
let query: TPaginationPayload = reactive(
|
||||
getQuery(route.query, orderByList, defaultOrderBy)
|
||||
)
|
||||
const orderByList: string[] = [
|
||||
'admin',
|
||||
'created_at',
|
||||
'username',
|
||||
'workouts_count',
|
||||
]
|
||||
const defaultOrderBy = 'created_at'
|
||||
let query: TPaginationPayload = reactive(
|
||||
getQuery(route.query, orderByList, defaultOrderBy)
|
||||
)
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const users: ComputedRef<IUserProfile[]> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS]
|
||||
)
|
||||
const pagination: ComputedRef<IPagination> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS_PAGINATION]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const users: ComputedRef<IUserProfile[]> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS]
|
||||
)
|
||||
const pagination: ComputedRef<IPagination> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS_PAGINATION]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
onBeforeMount(() => loadUsers(query))
|
||||
|
||||
function loadUsers(queryParams: TPaginationPayload) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.GET_USERS, queryParams)
|
||||
}
|
||||
function updateUser(username: string, admin: boolean) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
admin,
|
||||
})
|
||||
}
|
||||
function reloadUsers(queryParam: string, queryValue: string) {
|
||||
query[queryParam] = queryValue
|
||||
if (queryParam === 'per_page') {
|
||||
query.page = 1
|
||||
}
|
||||
router.push({ path: '/admin/users', query })
|
||||
}
|
||||
function loadUsers(queryParams: TPaginationPayload) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.GET_USERS, queryParams)
|
||||
}
|
||||
function updateUser(username: string, admin: boolean) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
admin,
|
||||
})
|
||||
}
|
||||
function reloadUsers(queryParam: string, queryValue: string) {
|
||||
query[queryParam] = queryValue
|
||||
if (queryParam === 'per_page') {
|
||||
query.page = 1
|
||||
}
|
||||
router.push({ path: '/admin/users', query })
|
||||
}
|
||||
|
||||
onBeforeMount(() => loadUsers(query))
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery: LocationQuery) => {
|
||||
query = getQuery(newQuery, orderByList, defaultOrderBy, { query })
|
||||
loadUsers(query)
|
||||
}
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USERS)
|
||||
})
|
||||
|
||||
return {
|
||||
authUser,
|
||||
errorMessages,
|
||||
orderByList,
|
||||
pagination,
|
||||
query,
|
||||
sortList,
|
||||
users,
|
||||
capitalize,
|
||||
format,
|
||||
getDateWithTZ,
|
||||
reloadUsers,
|
||||
updateUser,
|
||||
}
|
||||
},
|
||||
onUnmounted(() => {
|
||||
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USERS)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery: LocationQuery) => {
|
||||
query = getQuery(newQuery, orderByList, defaultOrderBy, { query })
|
||||
loadUsers(query)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -23,45 +23,34 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, computed } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, withDefaults } from 'vue'
|
||||
|
||||
import StatCard from '@/components/Common/StatCard.vue'
|
||||
import { IAppStatistics } from '@/types/application'
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserStatsCards',
|
||||
components: {
|
||||
StatCard,
|
||||
},
|
||||
props: {
|
||||
appStatistics: {
|
||||
type: Object as PropType<IAppStatistics>,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
uploadDirSize: computed(() =>
|
||||
props.appStatistics.uploads_dir_size
|
||||
? getReadableFileSize(props.appStatistics.uploads_dir_size, false)
|
||||
: { size: 0, suffix: 'bytes' }
|
||||
),
|
||||
usersCount: computed(() =>
|
||||
props.appStatistics.users ? props.appStatistics.users : 0
|
||||
),
|
||||
sportsCount: computed(() =>
|
||||
props.appStatistics.sports ? props.appStatistics.sports : 0
|
||||
),
|
||||
workoutCount: computed(() =>
|
||||
props.appStatistics.workouts ? props.appStatistics.workouts : 0
|
||||
),
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
appStatistics?: IAppStatistics
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
appStatistics: () => ({} as IAppStatistics),
|
||||
})
|
||||
|
||||
const uploadDirSize = computed(() =>
|
||||
props.appStatistics.uploads_dir_size
|
||||
? getReadableFileSize(props.appStatistics.uploads_dir_size, false)
|
||||
: { size: 0, suffix: 'bytes' }
|
||||
)
|
||||
const usersCount = computed(() =>
|
||||
props.appStatistics.users ? props.appStatistics.users : 0
|
||||
)
|
||||
const sportsCount = computed(() =>
|
||||
props.appStatistics.sports ? props.appStatistics.sports : 0
|
||||
)
|
||||
const workoutCount = computed(() =>
|
||||
props.appStatistics.workouts ? props.appStatistics.workouts : 0
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -1,19 +1,13 @@
|
||||
<template>
|
||||
<div id="about">
|
||||
<div id="bike">
|
||||
<img class="bike-img" :src="'/img/bike.svg'" alt="mountain bike" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'About',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/base';
|
||||
|
||||
#about {
|
||||
#bike {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
|
@ -4,15 +4,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AlertMessage',
|
||||
props: {
|
||||
message: String,
|
||||
},
|
||||
})
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
const props = defineProps<{
|
||||
message: string
|
||||
}>()
|
||||
const { message } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -9,13 +9,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'Card',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/scss/base';
|
||||
|
||||
|
@ -14,47 +14,35 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomTextArea',
|
||||
props: {
|
||||
charLimit: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
input: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateValue'],
|
||||
setup(props, { emit }) {
|
||||
let text = ref('')
|
||||
|
||||
function updateText(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateValue', event.target.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
text.value = value
|
||||
}
|
||||
)
|
||||
|
||||
return { text, updateText }
|
||||
},
|
||||
interface Props {
|
||||
name: string
|
||||
charLimit?: number
|
||||
disabled?: boolean
|
||||
input?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
charLimit: 500,
|
||||
disabled: false,
|
||||
input: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['updateValue'])
|
||||
|
||||
let text = ref('')
|
||||
|
||||
function updateText(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateValue', event.target.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
text.value = value
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -17,53 +17,37 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, ref, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { IDropdownOption, TDropdownOptions } from '@/types/forms'
|
||||
interface Props {
|
||||
options: TDropdownOptions
|
||||
selected: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Dropdown',
|
||||
props: {
|
||||
options: {
|
||||
type: Object as PropType<TDropdownOptions>,
|
||||
required: true,
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
selected: (option: IDropdownOption) => option,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const route = useRoute()
|
||||
let isOpen = ref(false)
|
||||
let dropdownOptions = props.options.map((option) => option)
|
||||
|
||||
function toggleDropdown() {
|
||||
isOpen.value = !isOpen.value
|
||||
}
|
||||
function updateSelected(option: IDropdownOption) {
|
||||
emit('selected', option)
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => (isOpen.value = false)
|
||||
)
|
||||
|
||||
return {
|
||||
dropdownOptions,
|
||||
isOpen,
|
||||
toggleDropdown,
|
||||
updateSelected,
|
||||
}
|
||||
},
|
||||
const emit = defineEmits({
|
||||
selected: (option: IDropdownOption) => option,
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
let isOpen = ref(false)
|
||||
let dropdownOptions = props.options.map((option) => option)
|
||||
|
||||
function toggleDropdown() {
|
||||
isOpen.value = !isOpen.value
|
||||
}
|
||||
function updateSelected(option: IDropdownOption) {
|
||||
emit('selected', option)
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => (isOpen.value = false)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -10,28 +10,19 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Error',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: '/',
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
title: string
|
||||
message: string
|
||||
buttonText: string
|
||||
path?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
path: '/',
|
||||
})
|
||||
const { buttonText, title, message, path } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -9,15 +9,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ErrorMessage',
|
||||
props: {
|
||||
message: [String, Array],
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
message: string | string[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const { message } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -34,7 +34,7 @@
|
||||
:value="query.per_page"
|
||||
@change="onSelectUpdate"
|
||||
>
|
||||
<option v-for="nb in per_page" :value="nb" :key="nb">
|
||||
<option v-for="nb in perPage" :value="nb" :key="nb">
|
||||
{{ nb }}
|
||||
</option>
|
||||
</select>
|
||||
@ -42,43 +42,27 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { TPaginationPayload } from '@/types/api'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FilterSelects',
|
||||
props: {
|
||||
order_by: {
|
||||
type: Object as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
query: {
|
||||
type: Object as PropType<TPaginationPayload>,
|
||||
required: true,
|
||||
},
|
||||
sort: {
|
||||
type: Object as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateSelect'],
|
||||
setup(props, { emit }) {
|
||||
function onSelectUpdate(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateSelect', event.target.id, event.target.value)
|
||||
}
|
||||
interface Props {
|
||||
order_by: string[]
|
||||
query: TPaginationPayload
|
||||
sort: string[]
|
||||
message: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
return {
|
||||
per_page: [10, 25, 50, 100],
|
||||
onSelectUpdate,
|
||||
}
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['updateSelect'])
|
||||
|
||||
const { order_by, query, sort, message } = toRefs(props)
|
||||
const perPage = [10, 25, 50, 100]
|
||||
|
||||
function onSelectUpdate(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateSelect', event.target.id, event.target.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -20,8 +20,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { inject, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import CyclingSport from '@/components/Common/Images/SportImage/CyclingSport.vue'
|
||||
import CyclingTransport from '@/components/Common/Images/SportImage/CyclingTransport.vue'
|
||||
@ -35,33 +35,14 @@
|
||||
import Trail from '@/components/Common/Images/SportImage/Trail.vue'
|
||||
import Walking from '@/components/Common/Images/SportImage/Walking.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SportImage',
|
||||
components: {
|
||||
CyclingSport,
|
||||
CyclingTransport,
|
||||
Hiking,
|
||||
MountainBiking,
|
||||
MountainBikingElectric,
|
||||
Rowing,
|
||||
Running,
|
||||
SkiingAlpine,
|
||||
SkiingCrossCountry,
|
||||
Trail,
|
||||
Walking,
|
||||
},
|
||||
props: {
|
||||
sportLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return { sportColors: inject('sportColors') }
|
||||
},
|
||||
interface Props {
|
||||
sportLabel: string
|
||||
title?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
})
|
||||
|
||||
const { sportLabel, title } = toRefs(props)
|
||||
const sportColors = inject('sportColors')
|
||||
</script>
|
||||
|
@ -2,13 +2,6 @@
|
||||
<div class="loader" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'Loader',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/base';
|
||||
.loader {
|
||||
|
@ -31,38 +31,30 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent, onUnmounted } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, toRefs, withDefaults, onUnmounted } from 'vue'
|
||||
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Modal',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
strongMessage: {
|
||||
type: String || null,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['cancelAction', 'confirmAction'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
return { errorMessages, emit }
|
||||
},
|
||||
interface Props {
|
||||
title: string
|
||||
message: string
|
||||
strongMessage?: string | null
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
strongMessage: () => null,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['cancelAction', 'confirmAction'])
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { title, message, strongMessage } = toRefs(props)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -6,21 +6,15 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
import Error from '@/components/Common/Error.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NotFound',
|
||||
components: {
|
||||
Error,
|
||||
},
|
||||
props: {
|
||||
target: {
|
||||
type: String,
|
||||
default: 'PAGE',
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
target?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
target: 'PAGE',
|
||||
})
|
||||
const { target } = toRefs(props)
|
||||
</script>
|
||||
|
@ -42,37 +42,27 @@
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IPagination, TPaginationPayload } from '@/types/api'
|
||||
import { IPagination } from '@/types/api'
|
||||
import { TWorkoutsPayload } from '@/types/workouts'
|
||||
import { rangePagination } from '@/utils/api'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
pagination: {
|
||||
type: Object as PropType<IPagination>,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
query: {
|
||||
type: Object as PropType<TPaginationPayload>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
function getQuery(page: number, cursor?: number): TPaginationPayload {
|
||||
const newQuery = Object.assign({}, props.query)
|
||||
newQuery.page = cursor ? page + cursor : page
|
||||
return newQuery
|
||||
}
|
||||
return { rangePagination, getQuery }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
pagination: IPagination
|
||||
path: string
|
||||
query: TWorkoutsPayload
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { pagination, path, query } = toRefs(props)
|
||||
|
||||
function getQuery(page: number, cursor?: number): TWorkoutsPayload {
|
||||
const newQuery = Object.assign({}, query.value)
|
||||
newQuery.page = cursor ? page + cursor : page
|
||||
return newQuery
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -12,26 +12,16 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StatCard',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
icon: string
|
||||
text: string
|
||||
value: string | number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const { icon, text, value } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -22,29 +22,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { getApiUrl } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StaticMap',
|
||||
props: {
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: true,
|
||||
},
|
||||
displayHover: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const imageUrl = `${getApiUrl()}workouts/map/${props.workout.map}`
|
||||
return { imageUrl }
|
||||
},
|
||||
interface Props {
|
||||
workout: IWorkout
|
||||
displayHover?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
displayHover: false,
|
||||
})
|
||||
const { displayHover } = toRefs(props)
|
||||
const imageUrl = `${getApiUrl()}workouts/map/${props.workout.map}`
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -30,15 +30,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onBeforeMount,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, ref, onBeforeMount, toRefs } from 'vue'
|
||||
|
||||
import WorkoutCard from '@/components/Workout/WorkoutCard.vue'
|
||||
import NoWorkouts from '@/components/Workouts/NoWorkouts.vue'
|
||||
@ -49,65 +42,44 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { defaultOrder } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Timeline',
|
||||
components: {
|
||||
NoWorkouts,
|
||||
WorkoutCard,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
let page = ref(1)
|
||||
const per_page = 5
|
||||
const initWorkoutsCount =
|
||||
props.user.nb_workouts >= per_page ? per_page : props.user.nb_workouts
|
||||
onBeforeMount(() => loadWorkouts())
|
||||
const store = useStore()
|
||||
|
||||
const workouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.TIMELINE_WORKOUTS]
|
||||
)
|
||||
const moreWorkoutsExist: ComputedRef<boolean> = computed(() =>
|
||||
workouts.value.length > 0
|
||||
? workouts.value[workouts.value.length - 1].previous_workout !== null
|
||||
: false
|
||||
)
|
||||
const { sports, user } = toRefs(props)
|
||||
let page = ref(1)
|
||||
const per_page = 5
|
||||
const initWorkoutsCount =
|
||||
props.user.nb_workouts >= per_page ? per_page : props.user.nb_workouts
|
||||
onBeforeMount(() => loadWorkouts())
|
||||
const workouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.TIMELINE_WORKOUTS]
|
||||
)
|
||||
const moreWorkoutsExist: ComputedRef<boolean> = computed(() =>
|
||||
workouts.value.length > 0
|
||||
? workouts.value[workouts.value.length - 1].previous_workout !== null
|
||||
: false
|
||||
)
|
||||
|
||||
function loadWorkouts() {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
function loadMoreWorkouts() {
|
||||
page.value += 1
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_MORE_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
initWorkoutsCount,
|
||||
moreWorkoutsExist,
|
||||
per_page,
|
||||
workouts,
|
||||
loadMoreWorkouts,
|
||||
}
|
||||
},
|
||||
})
|
||||
function loadWorkouts() {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
function loadMoreWorkouts() {
|
||||
page.value += 1
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_MORE_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -23,105 +23,71 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { addDays, format, isSameDay, isSameMonth, isToday } from 'date-fns'
|
||||
import {
|
||||
PropType,
|
||||
Ref,
|
||||
defineComponent,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
import { Ref, ref, toRefs, watch, onMounted } from 'vue'
|
||||
|
||||
import CalendarWorkouts from '@/components/Dashboard/UserCalendar/CalendarWorkouts.vue'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarCells',
|
||||
components: {
|
||||
CalendarWorkouts,
|
||||
},
|
||||
props: {
|
||||
currentDay: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
endDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
startDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
weekStartingMonday: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
workouts: {
|
||||
type: Object as PropType<IWorkout[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const rows: Ref<Date[][]> = ref([])
|
||||
let { startDate, endDate, weekStartingMonday } = toRefs(props)
|
||||
interface Props {
|
||||
currentDay: Date
|
||||
endDate: Date
|
||||
sports: ISport[]
|
||||
startDate: Date
|
||||
timezone: string
|
||||
weekStartingMonday: boolean
|
||||
workouts: IWorkout[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onMounted(() => getDays())
|
||||
const {
|
||||
currentDay,
|
||||
endDate,
|
||||
sports,
|
||||
startDate,
|
||||
timezone,
|
||||
weekStartingMonday,
|
||||
workouts,
|
||||
} = toRefs(props)
|
||||
const rows: Ref<Date[][]> = ref([])
|
||||
|
||||
function getDays() {
|
||||
rows.value = []
|
||||
let day = startDate.value
|
||||
while (day <= endDate.value) {
|
||||
const days: Date[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(day)
|
||||
day = addDays(day, 1)
|
||||
}
|
||||
rows.value.push(days)
|
||||
}
|
||||
onMounted(() => getDays())
|
||||
|
||||
function getDays() {
|
||||
rows.value = []
|
||||
let day = startDate.value
|
||||
while (day <= endDate.value) {
|
||||
const days: Date[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(day)
|
||||
day = addDays(day, 1)
|
||||
}
|
||||
rows.value.push(days)
|
||||
}
|
||||
}
|
||||
function isWeekEnd(day: number): boolean {
|
||||
return weekStartingMonday.value
|
||||
? [5, 6].includes(day)
|
||||
: [0, 6].includes(day)
|
||||
}
|
||||
function filterWorkouts(day: Date, workouts: IWorkout[]) {
|
||||
if (workouts) {
|
||||
return workouts
|
||||
.filter((workout) =>
|
||||
isSameDay(getDateWithTZ(workout.workout_date, timezone), day)
|
||||
)
|
||||
.reverse()
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function isWeekEnd(day: number): boolean {
|
||||
return weekStartingMonday.value
|
||||
? [5, 6].includes(day)
|
||||
: [0, 6].includes(day)
|
||||
}
|
||||
|
||||
function filterWorkouts(day: Date, workouts: IWorkout[]) {
|
||||
if (workouts) {
|
||||
return workouts
|
||||
.filter((workout) =>
|
||||
isSameDay(
|
||||
getDateWithTZ(workout.workout_date, props.timezone),
|
||||
day
|
||||
)
|
||||
)
|
||||
.reverse()
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentDay,
|
||||
() => getDays()
|
||||
)
|
||||
|
||||
return { rows, format, isSameMonth, isToday, isWeekEnd, filterWorkouts }
|
||||
},
|
||||
})
|
||||
watch(
|
||||
() => props.currentDay,
|
||||
() => getDays()
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -6,30 +6,19 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format, addDays } from 'date-fns'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarDays',
|
||||
props: {
|
||||
startDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
localeOptions: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const days = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(addDays(props.startDate, i))
|
||||
}
|
||||
return { days, addDays, format }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
startDate: Date
|
||||
localeOptions: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const days = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(addDays(props.startDate, i))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -20,27 +20,19 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import { defineComponent } from 'vue'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarHeader',
|
||||
props: {
|
||||
day: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
localeOptions: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['displayNextMonth', 'displayPreviousMonth'],
|
||||
setup(props, { emit }) {
|
||||
return { emit, format }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
day: Date
|
||||
localeOptions: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['displayNextMonth', 'displayPreviousMonth'])
|
||||
|
||||
const { day, localeOptions } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -13,7 +13,7 @@
|
||||
aria-hidden="true"
|
||||
:title="
|
||||
workout.records.map(
|
||||
(record) => ` ${t(`workouts.RECORD_${record.record_type}`)}`
|
||||
(record) => ` ${$t(`workouts.RECORD_${record.record_type}`)}`
|
||||
)
|
||||
"
|
||||
/>
|
||||
@ -21,29 +21,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
interface Props {
|
||||
workout: IWorkout
|
||||
sportLabel: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarWorkout',
|
||||
props: {
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: true,
|
||||
},
|
||||
sportLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
return { t }
|
||||
},
|
||||
})
|
||||
const { workout, sportLabel } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from 'vue'
|
||||
|
||||
import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue'
|
||||
import CalendarWorkoutsChart from '@/components/Dashboard/UserCalendar/CalendarWorkoutsChart.vue'
|
||||
@ -44,31 +44,16 @@
|
||||
import { getSportLabel, sportIdColors } from '@/utils/sports'
|
||||
import { getDonutDatasets } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarWorkouts',
|
||||
components: {
|
||||
CalendarWorkout,
|
||||
CalendarWorkoutsChart,
|
||||
},
|
||||
props: {
|
||||
workouts: {
|
||||
type: Object as PropType<IWorkout[]>,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
chartDatasets: computed(() => getDonutDatasets(props.workouts)),
|
||||
colors: computed(() => sportIdColors(props.sports)),
|
||||
displayedWorkoutCount: 6,
|
||||
getSportLabel,
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
workouts: IWorkout[]
|
||||
sports: ISport[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workouts, sports } = toRefs(props)
|
||||
const chartDatasets = computed(() => getDonutDatasets(props.workouts))
|
||||
const colors = computed(() => sportIdColors(props.sports))
|
||||
const displayedWorkoutCount = 6
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -22,8 +22,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, ref } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, toRefs } from 'vue'
|
||||
|
||||
import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue'
|
||||
import DonutChart from '@/components/Dashboard/UserCalendar/DonutChart.vue'
|
||||
@ -31,39 +31,21 @@
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { getSportLabel } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarWorkoutsChart',
|
||||
components: {
|
||||
CalendarWorkout,
|
||||
DonutChart,
|
||||
},
|
||||
props: {
|
||||
colors: {
|
||||
type: Object as PropType<Record<number, string>>,
|
||||
required: true,
|
||||
},
|
||||
datasets: {
|
||||
type: Object as PropType<Record<number, Record<string, number>>>,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
workouts: {
|
||||
type: Object as PropType<IWorkout[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const isHidden = ref(true)
|
||||
function togglePane(event: Event & { target: HTMLElement }) {
|
||||
event.stopPropagation()
|
||||
isHidden.value = !isHidden.value
|
||||
}
|
||||
return { isHidden, getSportLabel, togglePane }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
colors: Record<number, string>
|
||||
datasets: Record<number, Record<string, number>>
|
||||
sports: ISport[]
|
||||
workouts: IWorkout[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { colors, datasets, sports, workouts } = toRefs(props)
|
||||
const isHidden = ref(true)
|
||||
|
||||
function togglePane(event: Event & { target: HTMLElement }) {
|
||||
event.stopPropagation()
|
||||
isHidden.value = !isHidden.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -22,52 +22,34 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DonutChart',
|
||||
props: {
|
||||
colors: {
|
||||
type: Object as PropType<Record<number, string>>,
|
||||
required: true,
|
||||
},
|
||||
datasets: {
|
||||
type: Object as PropType<Record<number, Record<string, number>>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
let angleOffset = -90
|
||||
const cx = 16
|
||||
const cy = 16
|
||||
const radius = 14
|
||||
const circumference = 2 * Math.PI * radius
|
||||
interface Props {
|
||||
colors: Record<number, string>
|
||||
datasets: Record<number, Record<string, number>>
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
function calculateStrokeDashOffset(
|
||||
percentage: number,
|
||||
circumference: number
|
||||
): number {
|
||||
return circumference - percentage * circumference
|
||||
}
|
||||
function returnCircleTransformValue(
|
||||
index: number,
|
||||
percentage: number
|
||||
): string {
|
||||
const rotation = `rotate(${angleOffset}, ${cx}, ${cy})`
|
||||
angleOffset = percentage * 360 + angleOffset
|
||||
return rotation
|
||||
}
|
||||
const { colors, datasets } = toRefs(props)
|
||||
let angleOffset = -90
|
||||
const cx = 16
|
||||
const cy = 16
|
||||
const radius = 14
|
||||
const circumference = 2 * Math.PI * radius
|
||||
|
||||
return {
|
||||
angleOffset,
|
||||
circumference,
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
calculateStrokeDashOffset,
|
||||
returnCircleTransformValue,
|
||||
}
|
||||
},
|
||||
})
|
||||
function calculateStrokeDashOffset(
|
||||
percentage: number,
|
||||
circumference: number
|
||||
): number {
|
||||
return circumference - percentage * circumference
|
||||
}
|
||||
function returnCircleTransformValue(
|
||||
index: number,
|
||||
percentage: number
|
||||
): string {
|
||||
const rotation = `rotate(${angleOffset}, ${cx}, ${cy})`
|
||||
angleOffset = percentage * 360 + angleOffset
|
||||
return rotation
|
||||
}
|
||||
</script>
|
||||
|
@ -21,16 +21,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { addMonths, format, subMonths } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onBeforeMount,
|
||||
} from 'vue'
|
||||
import { ComputedRef, computed, ref, toRefs, onBeforeMount } from 'vue'
|
||||
|
||||
import CalendarCells from '@/components/Dashboard/UserCalendar/CalendarCells.vue'
|
||||
import CalendarDays from '@/components/Dashboard/UserCalendar/CalendarDays.vue'
|
||||
@ -43,70 +36,43 @@
|
||||
import { getCalendarStartAndEnd } from '@/utils/dates'
|
||||
import { defaultOrder } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserCalendar',
|
||||
components: {
|
||||
CalendarCells,
|
||||
CalendarDays,
|
||||
CalendarHeader,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onBeforeMount(() => getCalendarWorkouts())
|
||||
const store = useStore()
|
||||
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
let day = ref(new Date())
|
||||
let calendarDates = ref(
|
||||
getCalendarStartAndEnd(day.value, props.user.weekm)
|
||||
)
|
||||
const calendarWorkouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.CALENDAR_WORKOUTS]
|
||||
)
|
||||
const { sports, user } = toRefs(props)
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
let day = ref(new Date())
|
||||
let calendarDates = ref(getCalendarStartAndEnd(day.value, props.user.weekm))
|
||||
const calendarWorkouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.CALENDAR_WORKOUTS]
|
||||
)
|
||||
|
||||
function getCalendarWorkouts() {
|
||||
calendarDates.value = getCalendarStartAndEnd(
|
||||
day.value,
|
||||
props.user.weekm
|
||||
)
|
||||
const apiParams: TWorkoutsPayload = {
|
||||
from: format(calendarDates.value.start, dateFormat),
|
||||
to: format(calendarDates.value.end, dateFormat),
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
...defaultOrder,
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_CALENDAR_WORKOUTS, apiParams)
|
||||
}
|
||||
onBeforeMount(() => getCalendarWorkouts())
|
||||
|
||||
function displayNextMonth() {
|
||||
day.value = addMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
function displayPreviousMonth() {
|
||||
day.value = subMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
|
||||
return {
|
||||
day,
|
||||
calendarDates,
|
||||
calendarWorkouts,
|
||||
displayNextMonth,
|
||||
displayPreviousMonth,
|
||||
}
|
||||
},
|
||||
})
|
||||
function getCalendarWorkouts() {
|
||||
calendarDates.value = getCalendarStartAndEnd(day.value, props.user.weekm)
|
||||
const apiParams: TWorkoutsPayload = {
|
||||
from: format(calendarDates.value.start, dateFormat),
|
||||
to: format(calendarDates.value.end, dateFormat),
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
...defaultOrder,
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_CALENDAR_WORKOUTS, apiParams)
|
||||
}
|
||||
function displayNextMonth() {
|
||||
day.value = addMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
function displayPreviousMonth() {
|
||||
day.value = subMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -15,41 +15,28 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { endOfMonth, startOfMonth } from 'date-fns'
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import StatChart from '@/components/Common/StatsChart/index.vue'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserMonthStats',
|
||||
components: {
|
||||
StatChart,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const date = new Date()
|
||||
return {
|
||||
chartParams: {
|
||||
duration: 'week',
|
||||
start: startOfMonth(date),
|
||||
end: endOfMonth(date),
|
||||
},
|
||||
selectedSportIds: props.sports.map((sport) => sport.id),
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { sports, user } = toRefs(props)
|
||||
const date = new Date()
|
||||
const chartParams = {
|
||||
duration: 'week',
|
||||
start: startOfMonth(date),
|
||||
end: endOfMonth(date),
|
||||
}
|
||||
const selectedSportIds = props.sports.map((sport) => sport.id)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -7,9 +7,9 @@
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="record" v-for="record in records.records" :key="record.id">
|
||||
<span class="record-type">{{
|
||||
t(`workouts.RECORD_${record.record_type}`)
|
||||
}}</span>
|
||||
<span class="record-type">
|
||||
{{ $t(`workouts.RECORD_${record.record_type}`) }}
|
||||
</span>
|
||||
<span class="record-value">{{ record.value }}</span>
|
||||
<span class="record-date">
|
||||
<router-link
|
||||
@ -17,8 +17,9 @@
|
||||
name: 'Workout',
|
||||
params: { workoutId: record.workout_id },
|
||||
}"
|
||||
>{{ record.workout_date }}</router-link
|
||||
>
|
||||
{{ record.workout_date }}
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -26,29 +27,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IRecord } from '@/types/workouts'
|
||||
import { IRecordsBySports } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RecordsCard',
|
||||
props: {
|
||||
records: {
|
||||
type: Object as PropType<IRecord[]>,
|
||||
required: true,
|
||||
},
|
||||
sportTranslatedLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
return { t }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
records: IRecordsBySports
|
||||
sportTranslatedLabel: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { records, sportTranslatedLabel } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -18,8 +18,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import RecordsCard from '@/components/Dashboard/UserRecords/RecordsCard.vue'
|
||||
@ -28,33 +28,21 @@
|
||||
import { getRecordsBySports } from '@/utils/records'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserRecords',
|
||||
components: {
|
||||
RecordsCard,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const recordsBySport = computed(() =>
|
||||
getRecordsBySports(
|
||||
props.user.records,
|
||||
translateSports(props.sports, t),
|
||||
props.user.timezone
|
||||
)
|
||||
)
|
||||
return { recordsBySport }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const recordsBySport = computed(() =>
|
||||
getRecordsBySports(
|
||||
props.user.records,
|
||||
translateSports(props.sports, t),
|
||||
props.user.timezone
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -12,8 +12,8 @@
|
||||
/>
|
||||
<StatCard
|
||||
icon="clock-o"
|
||||
:value="total_duration.days"
|
||||
:text="total_duration.duration"
|
||||
:value="totalDuration.days"
|
||||
:text="totalDuration.duration"
|
||||
/>
|
||||
<StatCard
|
||||
icon="tags"
|
||||
@ -23,49 +23,40 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, PropType, defineComponent, computed } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import StatCard from '@/components/Common/StatCard.vue'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserStatsCards',
|
||||
components: {
|
||||
StatCard,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const total_duration: ComputedRef<string> = computed(
|
||||
() => props.user.total_duration
|
||||
)
|
||||
const { t } = useI18n()
|
||||
|
||||
function get_duration(total_duration: ComputedRef<string>) {
|
||||
const duration = total_duration.value.match(/day/g)
|
||||
? total_duration.value.split(', ')[1]
|
||||
: total_duration.value
|
||||
return {
|
||||
days: total_duration.value.match(/day/g)
|
||||
? `${total_duration.value.split(' ')[0]} ${
|
||||
total_duration.value.match(/days/g)
|
||||
? t('common.DAY', 2)
|
||||
: t('common.DAY', 1)
|
||||
}`
|
||||
: `0 ${t('common.DAY', 2)},`,
|
||||
duration: `${duration.split(':')[0]}h ${duration.split(':')[1]}min`,
|
||||
}
|
||||
}
|
||||
const { user } = toRefs(props)
|
||||
const userTotalDuration: ComputedRef<string> = computed(
|
||||
() => props.user.total_duration
|
||||
)
|
||||
const totalDuration = computed(() => get_duration(userTotalDuration))
|
||||
|
||||
return { total_duration: computed(() => get_duration(total_duration)) }
|
||||
},
|
||||
})
|
||||
function get_duration(total_duration: ComputedRef<string>) {
|
||||
const duration = total_duration.value.match(/day/g)
|
||||
? total_duration.value.split(', ')[1]
|
||||
: total_duration.value
|
||||
return {
|
||||
days: total_duration.value.match(/day/g)
|
||||
? `${total_duration.value.split(' ')[0]} ${
|
||||
total_duration.value.match(/days/g)
|
||||
? t('common.DAY', 2)
|
||||
: t('common.DAY', 1)
|
||||
}`
|
||||
: `0 ${t('common.DAY', 2)},`,
|
||||
duration: `${duration.split(':')[0]}h ${duration.split(':')[1]}min`,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -37,18 +37,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Footer',
|
||||
props: {
|
||||
version: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
version: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { version } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -77,8 +77,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent, ref, capitalize } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, ref, capitalize } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
@ -86,68 +86,39 @@
|
||||
import { IDropdownOption } from '@/types/forms'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getApiUrl } from '@/utils'
|
||||
import { availableLanguages } from '@/utils/locales'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NavBar',
|
||||
components: {
|
||||
UserPicture,
|
||||
},
|
||||
emits: ['menuInteraction'],
|
||||
setup(props, { emit }) {
|
||||
const { locale } = useI18n()
|
||||
const store = useStore()
|
||||
const emit = defineEmits(['menuInteraction'])
|
||||
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const isAuthenticated: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED]
|
||||
)
|
||||
const authUserPictureUrl: ComputedRef<string> = computed(() =>
|
||||
isAuthenticated.value && authUser.value.picture
|
||||
? `${getApiUrl()}/users/${
|
||||
authUser.value.username
|
||||
}/picture?${Date.now()}`
|
||||
: ''
|
||||
)
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
let isMenuOpen = ref(false)
|
||||
const { locale } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
function openMenu() {
|
||||
isMenuOpen.value = true
|
||||
emit('menuInteraction', true)
|
||||
}
|
||||
function closeMenu() {
|
||||
isMenuOpen.value = false
|
||||
emit('menuInteraction', false)
|
||||
}
|
||||
function updateLanguage(option: IDropdownOption) {
|
||||
locale.value = option.value.toString()
|
||||
store.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, option.value)
|
||||
}
|
||||
function logout() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
|
||||
}
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const isAuthenticated: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED]
|
||||
)
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
let isMenuOpen = ref(false)
|
||||
|
||||
return {
|
||||
availableLanguages,
|
||||
authUser,
|
||||
authUserPictureUrl,
|
||||
isAuthenticated,
|
||||
isMenuOpen,
|
||||
language,
|
||||
capitalize,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
updateLanguage,
|
||||
logout,
|
||||
}
|
||||
},
|
||||
})
|
||||
function openMenu() {
|
||||
isMenuOpen.value = true
|
||||
emit('menuInteraction', true)
|
||||
}
|
||||
function closeMenu() {
|
||||
isMenuOpen.value = false
|
||||
emit('menuInteraction', false)
|
||||
}
|
||||
function updateLanguage(option: IDropdownOption) {
|
||||
locale.value = option.value.toString()
|
||||
store.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, option.value)
|
||||
}
|
||||
function logout() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -28,19 +28,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NoConfig',
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
return { t }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/base';
|
||||
|
||||
|
@ -37,29 +37,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StatsMenu',
|
||||
emits: ['arrowClick', 'timeFrameUpdate'],
|
||||
setup(props, { emit }) {
|
||||
let selectedTimeFrame = ref('month')
|
||||
const timeFrames = ['week', 'month', 'year']
|
||||
const emit = defineEmits(['arrowClick', 'timeFrameUpdate'])
|
||||
|
||||
function onUpdateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
emit('timeFrameUpdate', timeFrame)
|
||||
}
|
||||
let selectedTimeFrame = ref('month')
|
||||
const timeFrames = ['week', 'month', 'year']
|
||||
|
||||
return {
|
||||
selectedTimeFrame,
|
||||
timeFrames,
|
||||
onUpdateTimeFrame,
|
||||
emit,
|
||||
}
|
||||
},
|
||||
})
|
||||
function onUpdateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
emit('timeFrameUpdate', timeFrame)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -19,43 +19,34 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, PropType, computed, defineComponent, inject } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, inject, withDefaults, toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { ISport, ITranslatedSport } from '@/types/sports'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SportsMenu',
|
||||
props: {
|
||||
selectedSportIds: {
|
||||
type: Array as PropType<number[]>,
|
||||
default: () => [],
|
||||
},
|
||||
userSports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['selectedSportIdsUpdate'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.userSports, t)
|
||||
)
|
||||
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
emit('selectedSportIdsUpdate', sportId)
|
||||
}
|
||||
|
||||
return {
|
||||
sportColors: inject('sportColors'),
|
||||
translatedSports,
|
||||
updateSelectedSportIds,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
userSports: ISport[]
|
||||
selectedSportIds?: number[]
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
selectedSportIds: () => [],
|
||||
})
|
||||
|
||||
const emit = defineEmits(['selectedSportIdsUpdate'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const sportColors = inject('sportColors')
|
||||
const { selectedSportIds } = toRefs(props)
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.userSports, t)
|
||||
)
|
||||
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
emit('selectedSportIdsUpdate', sportId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -19,16 +19,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, Ref, computed, ref, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import StatChart from '@/components/Common/StatsChart/index.vue'
|
||||
@ -40,81 +32,57 @@
|
||||
import { translateSports } from '@/utils/sports'
|
||||
import { getStatsDateParams, updateChartParams } from '@/utils/statistics'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Statistics',
|
||||
components: {
|
||||
SportsMenu,
|
||||
StatChart,
|
||||
StatsMenu,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
let selectedTimeFrame = ref('month')
|
||||
const timeFrames = ['week', 'month', 'year']
|
||||
const chartParams: Ref<IStatisticsDateParams> = ref(
|
||||
getChartParams(selectedTimeFrame.value)
|
||||
)
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const selectedSportIds: Ref<number[]> = ref(getSports(props.sports))
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
function updateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
chartParams.value = getChartParams(selectedTimeFrame.value)
|
||||
}
|
||||
function getChartParams(timeFrame: string): IStatisticsDateParams {
|
||||
return getStatsDateParams(new Date(), timeFrame, props.user.weekm)
|
||||
}
|
||||
function handleOnClickArrows(backward: boolean) {
|
||||
chartParams.value = updateChartParams(
|
||||
chartParams.value,
|
||||
backward,
|
||||
props.user.weekm
|
||||
)
|
||||
}
|
||||
function getSports(sports: ISport[]) {
|
||||
return sports.map((sport) => sport.id)
|
||||
}
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
if (selectedSportIds.value.includes(sportId)) {
|
||||
selectedSportIds.value = selectedSportIds.value.filter(
|
||||
(id) => id !== sportId
|
||||
)
|
||||
} else {
|
||||
selectedSportIds.value.push(sportId)
|
||||
}
|
||||
}
|
||||
const { t } = useI18n()
|
||||
|
||||
watch(
|
||||
() => props.sports,
|
||||
(newSports) => {
|
||||
selectedSportIds.value = getSports(newSports)
|
||||
}
|
||||
const { sports, user } = toRefs(props)
|
||||
let selectedTimeFrame = ref('month')
|
||||
const chartParams: Ref<IStatisticsDateParams> = ref(
|
||||
getChartParams(selectedTimeFrame.value)
|
||||
)
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const selectedSportIds: Ref<number[]> = ref(getSports(props.sports))
|
||||
|
||||
function updateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
chartParams.value = getChartParams(selectedTimeFrame.value)
|
||||
}
|
||||
function getChartParams(timeFrame: string): IStatisticsDateParams {
|
||||
return getStatsDateParams(new Date(), timeFrame, props.user.weekm)
|
||||
}
|
||||
function handleOnClickArrows(backward: boolean) {
|
||||
chartParams.value = updateChartParams(
|
||||
chartParams.value,
|
||||
backward,
|
||||
props.user.weekm
|
||||
)
|
||||
}
|
||||
function getSports(sports: ISport[]) {
|
||||
return sports.map((sport) => sport.id)
|
||||
}
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
if (selectedSportIds.value.includes(sportId)) {
|
||||
selectedSportIds.value = selectedSportIds.value.filter(
|
||||
(id) => id !== sportId
|
||||
)
|
||||
} else {
|
||||
selectedSportIds.value.push(sportId)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
chartParams,
|
||||
selectedTimeFrame,
|
||||
timeFrames,
|
||||
translatedSports,
|
||||
selectedSportIds,
|
||||
handleOnClickArrows,
|
||||
updateSelectedSportIds,
|
||||
updateTimeFrame,
|
||||
}
|
||||
},
|
||||
})
|
||||
watch(
|
||||
() => props.sports,
|
||||
(newSports) => {
|
||||
selectedSportIds.value = getSports(newSports)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -15,25 +15,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import EmailSent from '@/components/Common/Images/EmailSent.vue'
|
||||
import Password from '@/components/Common/Images/Password.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PasswordActionDone',
|
||||
components: {
|
||||
EmailSent,
|
||||
Password,
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
action: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { action } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -9,27 +9,20 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
import UserAuthForm from '@/components/User/UserAuthForm.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PasswordResetForm',
|
||||
components: {
|
||||
UserAuthForm,
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
action: string
|
||||
token?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
token: '',
|
||||
})
|
||||
|
||||
const { action, token } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -27,24 +27,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfileDisplay',
|
||||
components: {
|
||||
UserPicture,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { user } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -43,67 +43,46 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import { ComputedRef, Ref, computed, ref, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserInfos',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
fromAdmin: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const birthDate = computed(() =>
|
||||
props.user.birth_date
|
||||
? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
|
||||
: ''
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteUserAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
|
||||
return {
|
||||
authUser,
|
||||
birthDate,
|
||||
displayModal,
|
||||
registrationDate,
|
||||
deleteUserAccount,
|
||||
updateDisplayModal,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
fromAdmin?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fromAdmin: false,
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user, fromAdmin } = toRefs(props)
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const birthDate = computed(() =>
|
||||
props.user.birth_date
|
||||
? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
|
||||
: ''
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteUserAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -17,35 +17,26 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { languageLabels } from '@/utils/locales'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPreferences',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const language = computed(() =>
|
||||
props.user.language
|
||||
? languageLabels[props.user.language]
|
||||
: languageLabels['en']
|
||||
)
|
||||
const fistDayOfWeek = computed(() =>
|
||||
props.user.weekm ? 'MONDAY' : 'SUNDAY'
|
||||
)
|
||||
const timezone = computed(() =>
|
||||
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
||||
)
|
||||
return { fistDayOfWeek, language, timezone }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const language = computed(() =>
|
||||
props.user.language
|
||||
? languageLabels[props.user.language]
|
||||
: languageLabels['en']
|
||||
)
|
||||
const fistDayOfWeek = computed(() => (props.user.weekm ? 'MONDAY' : 'SUNDAY'))
|
||||
const timezone = computed(() =>
|
||||
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -8,35 +8,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import UserHeader from '@/components/User/ProfileDisplay/UserHeader.vue'
|
||||
import UserProfileTabs from '@/components/User/UserProfileTabs.vue'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfileDisplay',
|
||||
components: {
|
||||
UserHeader,
|
||||
UserProfileTabs,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
tab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tabs: ['PROFILE', 'PREFERENCES'],
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
tab: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { user, tab } = toRefs(props)
|
||||
const tabs = ['PROFILE', 'PREFERENCES']
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -27,74 +27,56 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, defineComponent, ref, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref, toRefs, watch, withDefaults } from 'vue'
|
||||
|
||||
import { timeZones } from '@/utils/timezone'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TimezoneDropdown',
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
input: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateTimezone'],
|
||||
setup(props, { emit }) {
|
||||
const timezone: Ref<string> = ref(props.input)
|
||||
const isOpen: Ref<boolean> = ref(false)
|
||||
const tzList: Ref<HTMLInputElement | null> = ref(null)
|
||||
const focusItemIndex: Ref<number> = ref(0)
|
||||
|
||||
function matchTimezone(t: string): RegExpMatchArray | null {
|
||||
return t.toLowerCase().match(timezone.value.toLowerCase())
|
||||
}
|
||||
function onMouseOver(index: number) {
|
||||
focusItemIndex.value = index
|
||||
}
|
||||
function onUpdateTimezone(value: string) {
|
||||
timezone.value = value
|
||||
isOpen.value = false
|
||||
emit('updateTimezone', value)
|
||||
}
|
||||
function onEnter(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
if (tzList.value?.firstElementChild?.innerHTML) {
|
||||
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
|
||||
}
|
||||
}
|
||||
function openDropdown(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
isOpen.value = true
|
||||
timezone.value = event.target.value.trim()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
timezone.value = value
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
focusItemIndex,
|
||||
isOpen,
|
||||
timezone,
|
||||
timeZones,
|
||||
tzList,
|
||||
matchTimezone,
|
||||
onEnter,
|
||||
onMouseOver,
|
||||
onUpdateTimezone,
|
||||
openDropdown,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
input: string
|
||||
disabled?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['updateTimezone'])
|
||||
|
||||
const { input, disabled } = toRefs(props)
|
||||
const timezone: Ref<string> = ref(props.input)
|
||||
const isOpen: Ref<boolean> = ref(false)
|
||||
const tzList: Ref<HTMLInputElement | null> = ref(null)
|
||||
const focusItemIndex: Ref<number> = ref(0)
|
||||
|
||||
function matchTimezone(t: string): RegExpMatchArray | null {
|
||||
return t.toLowerCase().match(timezone.value.toLowerCase())
|
||||
}
|
||||
function onMouseOver(index: number) {
|
||||
focusItemIndex.value = index
|
||||
}
|
||||
function onUpdateTimezone(value: string) {
|
||||
timezone.value = value
|
||||
isOpen.value = false
|
||||
emit('updateTimezone', value)
|
||||
}
|
||||
function onEnter(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
if (tzList.value?.firstElementChild?.innerHTML) {
|
||||
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
|
||||
}
|
||||
}
|
||||
function openDropdown(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
isOpen.value = true
|
||||
timezone.value = event.target.value.trim()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
timezone.value = value
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -93,16 +93,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
|
||||
@ -110,79 +109,63 @@
|
||||
import { IUserProfile, IUserPayload } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserInfosEdition',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const userForm: IUserPayload = reactive({
|
||||
password: '',
|
||||
password_conf: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
birth_date: '',
|
||||
location: '',
|
||||
bio: '',
|
||||
})
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
const store = useStore()
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.first_name = user.first_name ? user.first_name : ''
|
||||
userForm.last_name = user.last_name ? user.last_name : ''
|
||||
userForm.birth_date = user.birth_date
|
||||
? format(new Date(user.birth_date), 'yyyy-MM-dd')
|
||||
: ''
|
||||
userForm.location = user.location ? user.location : ''
|
||||
userForm.bio = user.bio ? user.bio : ''
|
||||
}
|
||||
function updateBio(value: string) {
|
||||
userForm.bio = value
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
|
||||
return {
|
||||
displayModal,
|
||||
errorMessages,
|
||||
loading,
|
||||
registrationDate,
|
||||
userForm,
|
||||
deleteAccount,
|
||||
updateBio,
|
||||
updateDisplayModal,
|
||||
updateProfile,
|
||||
}
|
||||
},
|
||||
const { user } = toRefs(props)
|
||||
const userForm: IUserPayload = reactive({
|
||||
password: '',
|
||||
password_conf: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
birth_date: '',
|
||||
location: '',
|
||||
bio: '',
|
||||
})
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.first_name = user.first_name ? user.first_name : ''
|
||||
userForm.last_name = user.last_name ? user.last_name : ''
|
||||
userForm.birth_date = user.birth_date
|
||||
? format(new Date(user.birth_date), 'yyyy-MM-dd')
|
||||
: ''
|
||||
userForm.location = user.location ? user.location : ''
|
||||
userForm.bio = user.bio ? user.bio : ''
|
||||
}
|
||||
function updateBio(value: string) {
|
||||
userForm.bio = value
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -32,15 +32,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
defineComponent,
|
||||
computed,
|
||||
ref,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, Ref, computed, ref, toRefs } from 'vue'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -49,56 +42,40 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPictureEdition',
|
||||
components: {
|
||||
UserPicture,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
let pictureFile: Ref<File | null> = ref(null)
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
function deleteUserPicture() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_PICTURE)
|
||||
}
|
||||
function updatePictureFile(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.files) {
|
||||
pictureFile.value = event.target.files[0]
|
||||
}
|
||||
}
|
||||
function updateUserPicture() {
|
||||
if (pictureFile.value) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE, {
|
||||
picture: pictureFile.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
const store = useStore()
|
||||
|
||||
return {
|
||||
errorMessages,
|
||||
fileSizeLimit,
|
||||
pictureFile,
|
||||
deleteUserPicture,
|
||||
updateUserPicture,
|
||||
updatePictureFile,
|
||||
}
|
||||
},
|
||||
})
|
||||
const { user } = toRefs(props)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
let pictureFile: Ref<File | null> = ref(null)
|
||||
|
||||
function deleteUserPicture() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_PICTURE)
|
||||
}
|
||||
function updatePictureFile(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.files) {
|
||||
pictureFile.value = event.target.files[0]
|
||||
}
|
||||
}
|
||||
function updateUserPicture() {
|
||||
if (pictureFile.value) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE, {
|
||||
picture: pictureFile.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -51,15 +51,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, reactive, onMounted } from 'vue'
|
||||
|
||||
import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -67,71 +60,50 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { availableLanguages } from '@/utils/locales'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPreferencesEdition',
|
||||
components: {
|
||||
TimezoneDropdown,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const userForm: IUserPreferencesPayload = reactive({
|
||||
language: '',
|
||||
timezone: 'Europe/Paris',
|
||||
weekm: false,
|
||||
})
|
||||
const weekStart = [
|
||||
{
|
||||
label: 'MONDAY',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'SUNDAY',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
const store = useStore()
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.language = user.language ? user.language : 'en'
|
||||
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
||||
userForm.weekm = user.weekm ? user.weekm : false
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES,
|
||||
userForm
|
||||
)
|
||||
}
|
||||
function updateTZ(value: string) {
|
||||
userForm.timezone = value
|
||||
}
|
||||
|
||||
return {
|
||||
availableLanguages,
|
||||
errorMessages,
|
||||
loading,
|
||||
userForm,
|
||||
weekStart,
|
||||
updateProfile,
|
||||
updateTZ,
|
||||
}
|
||||
},
|
||||
const userForm: IUserPreferencesPayload = reactive({
|
||||
language: '',
|
||||
timezone: 'Europe/Paris',
|
||||
weekm: false,
|
||||
})
|
||||
const weekStart = [
|
||||
{
|
||||
label: 'MONDAY',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'SUNDAY',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.language = user.language ? user.language : 'en'
|
||||
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
||||
userForm.weekm = user.weekm ? user.weekm : false
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES, userForm)
|
||||
}
|
||||
function updateTZ(value: string) {
|
||||
userForm.timezone = value
|
||||
}
|
||||
</script>
|
||||
|
@ -17,37 +17,25 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from 'vue'
|
||||
|
||||
import UserProfileTabs from '@/components/User/UserProfileTabs.vue'
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfileEdition',
|
||||
components: {
|
||||
UserProfileTabs,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
tab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
return {
|
||||
loading: computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
),
|
||||
tabs: ['PROFILE', 'PICTURE', 'PREFERENCES'],
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
tab: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user, tab } = toRefs(props)
|
||||
const tabs = ['PROFILE', 'PICTURE', 'PREFERENCES']
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
</script>
|
||||
|
@ -86,8 +86,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent, reactive, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
computed,
|
||||
reactive,
|
||||
toRefs,
|
||||
watch,
|
||||
withDefaults,
|
||||
} from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -95,104 +102,90 @@
|
||||
import { ILoginRegisterFormData } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserAuthForm',
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const formData: ILoginRegisterFormData = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_conf: '',
|
||||
})
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const buttonText: ComputedRef<string> = computed(() =>
|
||||
getButtonText(props.action)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const registration_disabled: ComputedRef<boolean> = computed(
|
||||
() =>
|
||||
props.action === 'register' &&
|
||||
!appConfig.value.is_registration_enabled
|
||||
)
|
||||
|
||||
function getButtonText(action: string): string {
|
||||
switch (action) {
|
||||
case 'reset-request':
|
||||
case 'reset':
|
||||
return 'buttons.SUBMIT'
|
||||
default:
|
||||
return `buttons.${props.action.toUpperCase()}`
|
||||
}
|
||||
}
|
||||
function onSubmit(actionType: string) {
|
||||
switch (actionType) {
|
||||
case 'reset':
|
||||
if (!props.token) {
|
||||
return store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
'user.INVALID_TOKEN'
|
||||
)
|
||||
}
|
||||
return store.dispatch(AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
|
||||
password: formData.password,
|
||||
password_conf: formData.password_conf,
|
||||
token: props.token,
|
||||
})
|
||||
case 'reset-request':
|
||||
return store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST,
|
||||
{
|
||||
email: formData.email,
|
||||
}
|
||||
)
|
||||
default:
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
|
||||
actionType,
|
||||
formData,
|
||||
redirectUrl: route.query.from,
|
||||
})
|
||||
}
|
||||
}
|
||||
function resetFormData() {
|
||||
formData.username = ''
|
||||
formData.email = ''
|
||||
formData.password = ''
|
||||
formData.password_conf = ''
|
||||
}
|
||||
watch(
|
||||
() => route.path,
|
||||
async () => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
resetFormData()
|
||||
}
|
||||
)
|
||||
return {
|
||||
appConfig,
|
||||
buttonText,
|
||||
errorMessages,
|
||||
formData,
|
||||
registration_disabled,
|
||||
onSubmit,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
action: string
|
||||
token?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
token: '',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const { action } = toRefs(props)
|
||||
const formData: ILoginRegisterFormData = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_conf: '',
|
||||
})
|
||||
const buttonText: ComputedRef<string> = computed(() =>
|
||||
getButtonText(props.action)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const registration_disabled: ComputedRef<boolean> = computed(
|
||||
() =>
|
||||
props.action === 'register' && !appConfig.value.is_registration_enabled
|
||||
)
|
||||
|
||||
function getButtonText(action: string): string {
|
||||
switch (action) {
|
||||
case 'reset-request':
|
||||
case 'reset':
|
||||
return 'buttons.SUBMIT'
|
||||
default:
|
||||
return `buttons.${props.action.toUpperCase()}`
|
||||
}
|
||||
}
|
||||
function onSubmit(actionType: string) {
|
||||
switch (actionType) {
|
||||
case 'reset':
|
||||
if (!props.token) {
|
||||
return store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
'user.INVALID_TOKEN'
|
||||
)
|
||||
}
|
||||
return store.dispatch(AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
|
||||
password: formData.password,
|
||||
password_conf: formData.password_conf,
|
||||
token: props.token,
|
||||
})
|
||||
case 'reset-request':
|
||||
return store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST,
|
||||
{
|
||||
email: formData.email,
|
||||
}
|
||||
)
|
||||
default:
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
|
||||
actionType,
|
||||
formData,
|
||||
redirectUrl: route.query.from,
|
||||
})
|
||||
}
|
||||
}
|
||||
function resetFormData() {
|
||||
formData.username = ''
|
||||
formData.email = ''
|
||||
formData.password = ''
|
||||
formData.password_conf = ''
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
async () => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
resetFormData()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -12,30 +12,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { getApiUrl } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPicture',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
authUserPictureUrl: computed(() =>
|
||||
props.user.picture
|
||||
? `${getApiUrl()}users/${props.user.username}/picture`
|
||||
: ''
|
||||
),
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const authUserPictureUrl = computed(() =>
|
||||
props.user.picture
|
||||
? `${getApiUrl()}users/${props.user.username}/picture`
|
||||
: ''
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -18,44 +18,32 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserProfileTabs',
|
||||
props: {
|
||||
tabs: {
|
||||
type: Object as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
selectedTab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
edition: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
function getPath(tab: string) {
|
||||
switch (tab) {
|
||||
case 'PICTURE':
|
||||
return '/profile/edit/picture'
|
||||
case 'PREFERENCES':
|
||||
return `/profile${props.edition ? '/edit' : ''}/preferences`
|
||||
default:
|
||||
case 'PROFILE':
|
||||
return `/profile${props.edition ? '/edit' : ''}`
|
||||
}
|
||||
}
|
||||
return { getPath }
|
||||
},
|
||||
interface Props {
|
||||
tabs: string[]
|
||||
selectedTab: string
|
||||
edition: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const { tabs, selectedTab, disabled } = toRefs(props)
|
||||
|
||||
function getPath(tab: string) {
|
||||
switch (tab) {
|
||||
case 'PICTURE':
|
||||
return '/profile/edit/picture'
|
||||
case 'PREFERENCES':
|
||||
return `/profile${props.edition ? '/edit' : ''}/preferences`
|
||||
default:
|
||||
case 'PROFILE':
|
||||
return `/profile${props.edition ? '/edit' : ''}`
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<router-link
|
||||
class="workout-title"
|
||||
v-if="workout"
|
||||
v-if="workout.id"
|
||||
:to="{
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
@ -27,7 +27,7 @@
|
||||
</router-link>
|
||||
<div
|
||||
class="workout-date"
|
||||
v-if="workout && user"
|
||||
v-if="workout.workout_date && user"
|
||||
:title="
|
||||
format(
|
||||
getDateWithTZ(workout.workout_date, user.timezone),
|
||||
@ -47,7 +47,7 @@
|
||||
class="workout-map"
|
||||
:class="{ 'no-cursor': !workout }"
|
||||
@click="
|
||||
workout
|
||||
workout.id
|
||||
? $router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
@ -66,11 +66,16 @@
|
||||
class="workout-data"
|
||||
:class="{ 'without-gpx': workout && !workout.with_gpx }"
|
||||
@click="
|
||||
$router.push({ name: 'Workout', params: { workoutId: workout.id } })
|
||||
workout.id
|
||||
? $router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
})
|
||||
: null
|
||||
"
|
||||
>
|
||||
<div class="img">
|
||||
<SportImage v-if="sport" :sport-label="sport.label" />
|
||||
<SportImage v-if="sport.label" :sport-label="sport.label" />
|
||||
</div>
|
||||
<div class="data">
|
||||
<i class="fa fa-clock-o" aria-hidden="true" />
|
||||
@ -103,9 +108,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { Locale, format, formatDistance } from 'date-fns'
|
||||
import { PropType, defineComponent, ComputedRef, computed } from 'vue'
|
||||
import { ComputedRef, computed, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import StaticMap from '@/components/Common/StaticMap.vue'
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
@ -116,39 +121,22 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutCard',
|
||||
components: {
|
||||
StaticMap,
|
||||
UserPicture,
|
||||
},
|
||||
props: {
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: false,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
sport: {
|
||||
type: Object as PropType<ISport>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const locale: ComputedRef<Locale> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
|
||||
)
|
||||
return {
|
||||
format,
|
||||
formatDistance,
|
||||
getDateWithTZ,
|
||||
locale,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
workout?: IWorkout
|
||||
sport?: ISport
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
workout: () => ({} as IWorkout),
|
||||
sport: () => ({} as ISport),
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user, workout, sport } = toRefs(props)
|
||||
const locale: ComputedRef<Locale> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -36,9 +36,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ChartData, ChartOptions } from 'chart.js'
|
||||
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
|
||||
import { ComputedRef, computed, ref } from 'vue'
|
||||
import { LineChart, useLineChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@ -50,160 +50,142 @@
|
||||
} from '@/types/workouts'
|
||||
import { getDatasets } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutChart',
|
||||
components: {
|
||||
LineChart,
|
||||
},
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
workoutData: {
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
required: true,
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
workoutData: IWorkoutData
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['getCoordinates'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.chartData, t)
|
||||
)
|
||||
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
labels: displayDistance.value
|
||||
? datasets.value.distance_labels
|
||||
: datasets.value.duration_labels,
|
||||
datasets: JSON.parse(
|
||||
JSON.stringify([
|
||||
datasets.value.datasets.speed,
|
||||
datasets.value.datasets.elevation,
|
||||
])
|
||||
),
|
||||
}))
|
||||
const coordinates: ComputedRef<TCoordinates[]> = computed(
|
||||
() => datasets.value.coordinates
|
||||
)
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
},
|
||||
},
|
||||
emits: ['getCoordinates'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.chartData, t)
|
||||
)
|
||||
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
labels: displayDistance.value
|
||||
? datasets.value.distance_labels
|
||||
: datasets.value.duration_labels,
|
||||
datasets: JSON.parse(
|
||||
JSON.stringify([
|
||||
datasets.value.datasets.speed,
|
||||
datasets.value.datasets.elevation,
|
||||
])
|
||||
),
|
||||
}))
|
||||
const coordinates: ComputedRef<TCoordinates[]> = computed(
|
||||
() => datasets.value.coordinates
|
||||
)
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
scales: {
|
||||
[displayDistance.value ? 'xDistance' : 'xDuration']: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: formatDuration(value)
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
[displayDistance.value ? 'xDistance' : 'xDuration']: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: formatDuration(value)
|
||||
},
|
||||
},
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
title: {
|
||||
display: true,
|
||||
text: displayDistance.value
|
||||
? t('workouts.DISTANCE') + ' (km)'
|
||||
: t('workouts.DURATION'),
|
||||
},
|
||||
},
|
||||
ySpeed: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.SPEED') + ' (km/h)',
|
||||
},
|
||||
},
|
||||
yElevation: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.ELEVATION') + ' (m)',
|
||||
},
|
||||
},
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
title: {
|
||||
display: true,
|
||||
text: displayDistance.value
|
||||
? t('workouts.DISTANCE') + ' (km)'
|
||||
: t('workouts.DURATION'),
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
pointStyle: 'circle',
|
||||
pointRadius: 0,
|
||||
},
|
||||
},
|
||||
ySpeed: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const label = ` ${context.dataset.label}: ${context.formattedValue}`
|
||||
return context.dataset.yAxisID === 'yElevation'
|
||||
? label + ' m'
|
||||
: label + ' km/h'
|
||||
},
|
||||
title: function (tooltipItems) {
|
||||
if (tooltipItems.length > 0) {
|
||||
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
|
||||
}
|
||||
return tooltipItems.length === 0
|
||||
? ''
|
||||
: displayDistance.value
|
||||
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
|
||||
: `${t('workouts.DURATION')}: ${formatDuration(
|
||||
tooltipItems[0].label.replace(',', '')
|
||||
)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.SPEED') + ' (km/h)',
|
||||
},
|
||||
}))
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
function formatDuration(duration: string | number): string {
|
||||
return new Date(+duration * 1000).toISOString().substr(11, 8)
|
||||
}
|
||||
function emitCoordinates(coordinates: TCoordinates) {
|
||||
emit('getCoordinates', coordinates)
|
||||
}
|
||||
function emitEmptyCoordinates() {
|
||||
emitCoordinates({ latitude: null, longitude: null })
|
||||
}
|
||||
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
return {
|
||||
displayDistance,
|
||||
lineChartProps,
|
||||
emitEmptyCoordinates,
|
||||
updateDisplayDistance,
|
||||
}
|
||||
},
|
||||
yElevation: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.ELEVATION') + ' (m)',
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
pointStyle: 'circle',
|
||||
pointRadius: 0,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const label = ` ${context.dataset.label}: ${context.formattedValue}`
|
||||
return context.dataset.yAxisID === 'yElevation'
|
||||
? label + ' m'
|
||||
: label + ' km/h'
|
||||
},
|
||||
title: function (tooltipItems) {
|
||||
if (tooltipItems.length > 0) {
|
||||
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
|
||||
}
|
||||
return tooltipItems.length === 0
|
||||
? ''
|
||||
: displayDistance.value
|
||||
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
|
||||
: `${t('workouts.DURATION')}: ${formatDuration(
|
||||
tooltipItems[0].label.replace(',', '')
|
||||
)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
function formatDuration(duration: string | number): string {
|
||||
return new Date(+duration * 1000).toISOString().substr(11, 8)
|
||||
}
|
||||
function emitCoordinates(coordinates: TCoordinates) {
|
||||
emit('getCoordinates', coordinates)
|
||||
}
|
||||
function emitEmptyCoordinates() {
|
||||
emitCoordinates({ latitude: null, longitude: null })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user