Merge pull request #245 from jat255/add_date_formats

Add ability to customize format used to display dates throughout the app
This commit is contained in:
Sam 2022-11-01 14:46:02 +01:00 committed by GitHub
commit c4bb41caf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 490 additions and 82 deletions

View File

@ -42,6 +42,9 @@ clean-install: clean
rm -rf dist/ rm -rf dist/
## Docker commands for evaluation purposes ## Docker commands for evaluation purposes
docker-bandit:
docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_BANDIT) -r fittrackee -c pyproject.toml
docker-build: docker-build:
docker-compose -f docker-compose-dev.yml build fittrackee docker-compose -f docker-compose-dev.yml build fittrackee
@ -50,11 +53,15 @@ docker-build-all: docker-build docker-build-client
docker-build-client: docker-build-client:
docker-compose -f docker-compose-dev.yml build fittrackee_client docker-compose -f docker-compose-dev.yml build fittrackee_client
docker-check-all: docker-bandit docker-lint-all docker-type-check docker-test-client docker-test-python
docker-init: docker-run docker-init-db docker-restart docker-run-workers docker-init: docker-run docker-init-db docker-restart docker-run-workers
docker-init-db: docker-init-db:
docker-compose -f docker-compose-dev.yml exec fittrackee docker/init-database.sh docker-compose -f docker-compose-dev.yml exec fittrackee docker/init-database.sh
docker-lint-all: docker-lint-client docker-lint-python
docker-lint-client: docker-lint-client:
docker-compose -f docker-compose-dev.yml up -d fittrackee_client docker-compose -f docker-compose-dev.yml up -d fittrackee_client
docker-compose -f docker-compose-dev.yml exec fittrackee_client yarn lint docker-compose -f docker-compose-dev.yml exec fittrackee_client yarn lint
@ -65,12 +72,18 @@ docker-lint-python: docker-run
docker-logs: docker-logs:
docker-compose -f docker-compose-dev.yml logs --follow docker-compose -f docker-compose-dev.yml logs --follow
docker-migrate-db:
docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_FLASK) db migrate --directory $(DOCKER_MIGRATIONS)
docker-rebuild: docker-rebuild:
docker-compose -f docker-compose-dev.yml build --no-cache docker-compose -f docker-compose-dev.yml build --no-cache
docker-restart: docker-restart:
docker-compose -f docker-compose-dev.yml restart fittrackee docker-compose -f docker-compose-dev.yml restart fittrackee
docker-revision:
docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_FLASK) db revision --directory $(DOCKER_MIGRATIONS) --message $(MIGRATION_MESSAGE)
docker-run-all: docker-run docker-run-workers docker-run-all: docker-run docker-run-workers
docker-run: docker-run:
@ -104,9 +117,16 @@ docker-test-e2e: docker-run
docker-test-python: docker-run docker-test-python: docker-run
docker-compose -f docker-compose-dev.yml exec fittrackee docker/test-python.sh $(PYTEST_ARGS) docker-compose -f docker-compose-dev.yml exec fittrackee docker/test-python.sh $(PYTEST_ARGS)
docker-type-check:
echo 'Running mypy in docker...'
docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_MYPY) fittrackee
docker-up: docker-up:
docker-compose -f docker-compose-dev.yml up fittrackee docker-compose -f docker-compose-dev.yml up fittrackee
docker-upgrade-db:
docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_FTCLI) db upgrade
downgrade-db: downgrade-db:
$(FLASK) db downgrade --directory $(MIGRATIONS) $(FLASK) db downgrade --directory $(MIGRATIONS)

View File

@ -27,6 +27,14 @@ BANDIT = $(VENV)/bin/bandit
PYBABEL = $(VENV)/bin/pybabel PYBABEL = $(VENV)/bin/pybabel
FTCLI = $(VENV)/bin/ftcli FTCLI = $(VENV)/bin/ftcli
# Docker env
export DOCKER_APP_DIR = /usr/src/app
export DOCKER_MIGRATIONS = $(DOCKER_APP_DIR)/fittrackee/migrations
export DOCKER_FLASK = /usr/local/bin/flask
export DOCKER_FTCLI = /usr/local/bin/ftcli
export DOCKER_BANDIT = /usr/local/bin/bandit
export DOCKER_MYPY = /usr/local/bin/mypy
# Node env # Node env
NODE_MODULES = $(PWD)/fittrackee_client/node_modules NODE_MODULES = $(PWD)/fittrackee_client/node_modules
NPM ?= yarn NPM ?= yarn

View File

@ -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><script defer="defer" src="/static/js/chunk-vendors.e9c5b3a5.js"></script><script defer="defer" src="/static/js/app.f492102f.js"></script><link href="/static/css/app.eee1934d.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></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><script defer="defer" src="/static/js/chunk-vendors.a306e708.js"></script><script defer="defer" src="/static/js/app.2c35ea55.js"></script><link href="/static/css/app.a67ca8d9.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></body></html>

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

View File

@ -1,2 +1,2 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[328],{6e3:function(t,e,i){i.r(e),i.d(e,{default:function(){return _}});var a=i(6252),n=i(2262),s=i(8273),c=i(5801),r=i(9917);const S=t=>((0,a.dD)("data-v-64629971"),t=t(),(0,a.Cn)(),t),l={id:"admin",class:"view"},p={key:0,class:"container"},u=S((()=>(0,a._)("div",{id:"bottom"},null,-1)));var T=(0,a.aZ)({__name:"AdminView",setup(t){const e=(0,r.o)(),i=(0,a.Fl)((()=>e.getters[c.SY.GETTERS.APP_CONFIG])),S=(0,a.Fl)((()=>e.getters[c.SY.GETTERS.APP_STATS])),T=(0,a.Fl)((()=>e.getters[c.YN.GETTERS.IS_ADMIN])),d=(0,a.Fl)((()=>e.getters[c.YN.GETTERS.USER_LOADING]));return(0,a.wF)((()=>e.dispatch(c.SY.ACTIONS.GET_APPLICATION_STATS))),(t,e)=>{const c=(0,a.up)("router-view");return(0,a.wg)(),(0,a.iD)("div",l,[(0,n.SU)(d)?(0,a.kq)("",!0):((0,a.wg)(),(0,a.iD)("div",p,[(0,n.SU)(T)?((0,a.wg)(),(0,a.j4)(c,{key:0,appConfig:(0,n.SU)(i),appStatistics:(0,n.SU)(S)},null,8,["appConfig","appStatistics"])):((0,a.wg)(),(0,a.j4)(s.Z,{key:1})),u]))])}}}),d=i(3744);const o=(0,d.Z)(T,[["__scopeId","data-v-64629971"]]);var _=o}}]); "use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[328],{6e3:function(t,e,i){i.r(e),i.d(e,{default:function(){return _}});var a=i(6252),n=i(2262),s=i(8273),c=i(5801),r=i(9917);const S=t=>((0,a.dD)("data-v-64629971"),t=t(),(0,a.Cn)(),t),l={id:"admin",class:"view"},p={key:0,class:"container"},u=S((()=>(0,a._)("div",{id:"bottom"},null,-1)));var T=(0,a.aZ)({__name:"AdminView",setup(t){const e=(0,r.o)(),i=(0,a.Fl)((()=>e.getters[c.SY.GETTERS.APP_CONFIG])),S=(0,a.Fl)((()=>e.getters[c.SY.GETTERS.APP_STATS])),T=(0,a.Fl)((()=>e.getters[c.YN.GETTERS.IS_ADMIN])),d=(0,a.Fl)((()=>e.getters[c.YN.GETTERS.USER_LOADING]));return(0,a.wF)((()=>e.dispatch(c.SY.ACTIONS.GET_APPLICATION_STATS))),(t,e)=>{const c=(0,a.up)("router-view");return(0,a.wg)(),(0,a.iD)("div",l,[(0,n.SU)(d)?(0,a.kq)("",!0):((0,a.wg)(),(0,a.iD)("div",p,[(0,n.SU)(T)?((0,a.wg)(),(0,a.j4)(c,{key:0,appConfig:(0,n.SU)(i),appStatistics:(0,n.SU)(S)},null,8,["appConfig","appStatistics"])):((0,a.wg)(),(0,a.j4)(s.Z,{key:1})),u]))])}}}),d=i(3744);const o=(0,d.Z)(T,[["__scopeId","data-v-64629971"]]);var _=o}}]);
//# sourceMappingURL=admin.ab9e5f5f.js.map //# sourceMappingURL=admin.3b0b84c0.js.map

View File

@ -1 +1 @@
{"version":3,"file":"static/js/admin.ab9e5f5f.js","mappings":"mOAGA,MAAMA,EAAeC,KAAMC,EAAAA,EAAAA,IAAa,mBAAmBD,EAAEA,KAAIE,EAAAA,EAAAA,MAAcF,GACzEG,EAAa,CACjBC,GAAI,QACJC,MAAO,QAEHC,EAAa,CACjBC,IAAK,EACLF,MAAO,aAEHG,EAA2BT,GAAa,KAAmBU,EAAAA,EAAAA,GAAoB,MAAO,CAAEL,GAAI,UAAY,MAAO,KAUrH,OAA4BM,EAAAA,EAAAA,IAAiB,CAC3CC,OAAQ,YACRC,MAAMC,GAEN,MAAMC,GAAQC,EAAAA,EAAAA,KAERC,GAAqCC,EAAAA,EAAAA,KACzC,IAAMH,EAAMI,QAAQC,EAAAA,GAAAA,QAAAA,cAEhBC,GAA6CH,EAAAA,EAAAA,KACjD,IAAMH,EAAMI,QAAQC,EAAAA,GAAAA,QAAAA,aAEhBE,GAAuCJ,EAAAA,EAAAA,KAC3C,IAAMH,EAAMI,QAAQI,EAAAA,GAAAA,QAAAA,YAEhBC,GAAoCN,EAAAA,EAAAA,KACxC,IAAMH,EAAMI,QAAQI,EAAAA,GAAAA,QAAAA,gBAKxB,OAFEE,EAAAA,EAAAA,KAAc,IAAMV,EAAMW,SAASN,EAAAA,GAAAA,QAAAA,yBAE9B,CAACO,EAAUC,KAChB,MAAMC,GAAyBC,EAAAA,EAAAA,IAAkB,eAEjD,OAAQC,EAAAA,EAAAA,OAAcC,EAAAA,EAAAA,IAAoB,MAAO5B,EAAY,EACzD6B,EAAAA,EAAAA,IAAOT,IAWLU,EAAAA,EAAAA,IAAoB,IAAI,KAVvBH,EAAAA,EAAAA,OAAcC,EAAAA,EAAAA,IAAoB,MAAOzB,EAAY,EACnD0B,EAAAA,EAAAA,IAAOX,KACHS,EAAAA,EAAAA,OAAcI,EAAAA,EAAAA,IAAaN,EAAwB,CAClDrB,IAAK,EACLS,WAAWgB,EAAAA,EAAAA,IAAOhB,GAClBI,eAAeY,EAAAA,EAAAA,IAAOZ,IACrB,KAAM,EAAG,CAAC,YAAa,qBACzBU,EAAAA,EAAAA,OAAcI,EAAAA,EAAAA,IAAaC,EAAAA,EAAU,CAAE5B,IAAK,KACjDC,MAVR,CAeD,I,UCvDD,MAAM4B,GAA2B,OAAgB,EAAQ,CAAC,CAAC,YAAY,qBAEvE,O","sources":["webpack://fittrackee_client/./src/views/AdminView.vue?67de","webpack://fittrackee_client/./src/views/AdminView.vue"],"sourcesContent":["import { defineComponent as _defineComponent } from 'vue'\nimport { unref as _unref, resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-64629971\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = {\n id: \"admin\",\n class: \"view\"\n}\nconst _hoisted_2 = {\n key: 0,\n class: \"container\"\n}\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { id: \"bottom\" }, null, -1))\n\nimport { computed, ComputedRef, onBeforeMount } from 'vue'\n\n import NotFound from '@/components/Common/NotFound.vue'\n import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'\n import { TAppConfig, IAppStatistics } from '@/types/application'\n import { useStore } from '@/use/useStore'\n\n \nexport default /*#__PURE__*/_defineComponent({\n __name: 'AdminView',\n setup(__props) {\n\n const store = useStore()\n\n const appConfig: ComputedRef<TAppConfig> = computed(\n () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]\n )\n const appStatistics: ComputedRef<IAppStatistics> = computed(\n () => store.getters[ROOT_STORE.GETTERS.APP_STATS]\n )\n const isAuthUserAmin: ComputedRef<boolean> = computed(\n () => store.getters[AUTH_USER_STORE.GETTERS.IS_ADMIN]\n )\n const userLoading: ComputedRef<boolean> = computed(\n () => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]\n )\n\n onBeforeMount(() => store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_STATS))\n\nreturn (_ctx: any,_cache: any) => {\n const _component_router_view = _resolveComponent(\"router-view\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (!_unref(userLoading))\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_2, [\n (_unref(isAuthUserAmin))\n ? (_openBlock(), _createBlock(_component_router_view, {\n key: 0,\n appConfig: _unref(appConfig),\n appStatistics: _unref(appStatistics)\n }, null, 8, [\"appConfig\", \"appStatistics\"]))\n : (_openBlock(), _createBlock(NotFound, { key: 1 })),\n _hoisted_3\n ]))\n : _createCommentVNode(\"\", true)\n ]))\n}\n}\n\n})","import script from \"./AdminView.vue?vue&type=script&setup=true&lang=ts\"\nexport * from \"./AdminView.vue?vue&type=script&setup=true&lang=ts\"\n\nimport \"./AdminView.vue?vue&type=style&index=0&id=64629971&lang=scss&scoped=true\"\n\nimport exportComponent from \"/mnt/data-lnx/Devs/00_Perso/FitTrackee/fittrackee_client/node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['__scopeId',\"data-v-64629971\"]])\n\nexport default __exports__"],"names":["_withScopeId","n","_pushScopeId","_popScopeId","_hoisted_1","id","class","_hoisted_2","key","_hoisted_3","_createElementVNode","_defineComponent","__name","setup","__props","store","useStore","appConfig","computed","getters","ROOT_STORE","appStatistics","isAuthUserAmin","AUTH_USER_STORE","userLoading","onBeforeMount","dispatch","_ctx","_cache","_component_router_view","_resolveComponent","_openBlock","_createElementBlock","_unref","_createCommentVNode","_createBlock","NotFound","__exports__"],"sourceRoot":""} {"version":3,"file":"static/js/admin.3b0b84c0.js","mappings":"mOAGA,MAAMA,EAAeC,KAAMC,EAAAA,EAAAA,IAAa,mBAAmBD,EAAEA,KAAIE,EAAAA,EAAAA,MAAcF,GACzEG,EAAa,CACjBC,GAAI,QACJC,MAAO,QAEHC,EAAa,CACjBC,IAAK,EACLF,MAAO,aAEHG,EAA2BT,GAAa,KAAmBU,EAAAA,EAAAA,GAAoB,MAAO,CAAEL,GAAI,UAAY,MAAO,KAUrH,OAA4BM,EAAAA,EAAAA,IAAiB,CAC3CC,OAAQ,YACRC,MAAMC,GAEN,MAAMC,GAAQC,EAAAA,EAAAA,KAERC,GAAqCC,EAAAA,EAAAA,KACzC,IAAMH,EAAMI,QAAQC,EAAAA,GAAAA,QAAAA,cAEhBC,GAA6CH,EAAAA,EAAAA,KACjD,IAAMH,EAAMI,QAAQC,EAAAA,GAAAA,QAAAA,aAEhBE,GAAuCJ,EAAAA,EAAAA,KAC3C,IAAMH,EAAMI,QAAQI,EAAAA,GAAAA,QAAAA,YAEhBC,GAAoCN,EAAAA,EAAAA,KACxC,IAAMH,EAAMI,QAAQI,EAAAA,GAAAA,QAAAA,gBAKxB,OAFEE,EAAAA,EAAAA,KAAc,IAAMV,EAAMW,SAASN,EAAAA,GAAAA,QAAAA,yBAE9B,CAACO,EAAUC,KAChB,MAAMC,GAAyBC,EAAAA,EAAAA,IAAkB,eAEjD,OAAQC,EAAAA,EAAAA,OAAcC,EAAAA,EAAAA,IAAoB,MAAO5B,EAAY,EACzD6B,EAAAA,EAAAA,IAAOT,IAWLU,EAAAA,EAAAA,IAAoB,IAAI,KAVvBH,EAAAA,EAAAA,OAAcC,EAAAA,EAAAA,IAAoB,MAAOzB,EAAY,EACnD0B,EAAAA,EAAAA,IAAOX,KACHS,EAAAA,EAAAA,OAAcI,EAAAA,EAAAA,IAAaN,EAAwB,CAClDrB,IAAK,EACLS,WAAWgB,EAAAA,EAAAA,IAAOhB,GAClBI,eAAeY,EAAAA,EAAAA,IAAOZ,IACrB,KAAM,EAAG,CAAC,YAAa,qBACzBU,EAAAA,EAAAA,OAAcI,EAAAA,EAAAA,IAAaC,EAAAA,EAAU,CAAE5B,IAAK,KACjDC,MAVR,CAeD,I,UCvDD,MAAM4B,GAA2B,OAAgB,EAAQ,CAAC,CAAC,YAAY,qBAEvE,O","sources":["webpack://fittrackee_client/./src/views/AdminView.vue?67de","webpack://fittrackee_client/./src/views/AdminView.vue"],"sourcesContent":["import { defineComponent as _defineComponent } from 'vue'\nimport { unref as _unref, resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from \"vue\"\n\nconst _withScopeId = n => (_pushScopeId(\"data-v-64629971\"),n=n(),_popScopeId(),n)\nconst _hoisted_1 = {\n id: \"admin\",\n class: \"view\"\n}\nconst _hoisted_2 = {\n key: 0,\n class: \"container\"\n}\nconst _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(\"div\", { id: \"bottom\" }, null, -1))\n\nimport { computed, ComputedRef, onBeforeMount } from 'vue'\n\n import NotFound from '@/components/Common/NotFound.vue'\n import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'\n import { TAppConfig, IAppStatistics } from '@/types/application'\n import { useStore } from '@/use/useStore'\n\n \nexport default /*#__PURE__*/_defineComponent({\n __name: 'AdminView',\n setup(__props) {\n\n const store = useStore()\n\n const appConfig: ComputedRef<TAppConfig> = computed(\n () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]\n )\n const appStatistics: ComputedRef<IAppStatistics> = computed(\n () => store.getters[ROOT_STORE.GETTERS.APP_STATS]\n )\n const isAuthUserAmin: ComputedRef<boolean> = computed(\n () => store.getters[AUTH_USER_STORE.GETTERS.IS_ADMIN]\n )\n const userLoading: ComputedRef<boolean> = computed(\n () => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]\n )\n\n onBeforeMount(() => store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_STATS))\n\nreturn (_ctx: any,_cache: any) => {\n const _component_router_view = _resolveComponent(\"router-view\")!\n\n return (_openBlock(), _createElementBlock(\"div\", _hoisted_1, [\n (!_unref(userLoading))\n ? (_openBlock(), _createElementBlock(\"div\", _hoisted_2, [\n (_unref(isAuthUserAmin))\n ? (_openBlock(), _createBlock(_component_router_view, {\n key: 0,\n appConfig: _unref(appConfig),\n appStatistics: _unref(appStatistics)\n }, null, 8, [\"appConfig\", \"appStatistics\"]))\n : (_openBlock(), _createBlock(NotFound, { key: 1 })),\n _hoisted_3\n ]))\n : _createCommentVNode(\"\", true)\n ]))\n}\n}\n\n})","import script from \"./AdminView.vue?vue&type=script&setup=true&lang=ts\"\nexport * from \"./AdminView.vue?vue&type=script&setup=true&lang=ts\"\n\nimport \"./AdminView.vue?vue&type=style&index=0&id=64629971&lang=scss&scoped=true\"\n\nimport exportComponent from \"/mnt/data-lnx/Devs/00_Perso/FitTrackee/fittrackee_client/node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['__scopeId',\"data-v-64629971\"]])\n\nexport default __exports__"],"names":["_withScopeId","n","_pushScopeId","_popScopeId","_hoisted_1","id","class","_hoisted_2","key","_hoisted_3","_createElementVNode","_defineComponent","__name","setup","__props","store","useStore","appConfig","computed","getters","ROOT_STORE","appStatistics","isAuthUserAmin","AUTH_USER_STORE","userLoading","onBeforeMount","dispatch","_ctx","_cache","_component_router_view","_resolveComponent","_openBlock","_createElementBlock","_unref","_createCommentVNode","_createBlock","NotFound","__exports__"],"sourceRoot":""}

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

View File

@ -1,2 +1,2 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[845],{4264:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),i=r(5801),c=r(9917);const l={key:0,id:"account-confirmation",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"AccountConfirmationView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,c.o)(),d=(0,n.Fl)((()=>_.getters[i.SY.GETTERS.ERROR_MESSAGES])),S=(0,n.Fl)((()=>t.query.token));function m(){S.value?_.dispatch(i.YN.ACTIONS.CONFIRM_ACCOUNT,{token:S.value}):r.push("/")}return(0,n.wF)((()=>m())),(0,n.Ah)((()=>_.commit(i.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(e,t)=>{const r=(0,n.up)("router-link");return(0,a.SU)(d)?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n.Wm)(r,{class:"links",to:"/account-confirmation/resend"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("buttons.ACCOUNT-CONFIRMATION-RESEND"))+"? ",1)])),_:1})])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-785df978"]]);var m=S},8160:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),i=r(5801),c=r(9917);const l={key:0,id:"email-update",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"EmailUpdateView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,c.o)(),d=(0,n.Fl)((()=>_.getters[i.YN.GETTERS.AUTH_USER_PROFILE])),S=(0,n.Fl)((()=>_.getters[i.YN.GETTERS.IS_AUTHENTICATED])),m=(0,n.Fl)((()=>_.getters[i.SY.GETTERS.ERROR_MESSAGES])),p=(0,n.Fl)((()=>t.query.token));function R(){p.value?_.dispatch(i.YN.ACTIONS.CONFIRM_EMAIL,{token:p.value,refreshUser:S.value}):r.push("/")}return(0,n.wF)((()=>R())),(0,n.Ah)((()=>_.commit(i.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(0,n.YP)((()=>m.value),(e=>{d.value.username&&e&&r.push("/")})),(e,t)=>{const r=(0,n.up)("router-link"),u=(0,n.up)("i18n-t");return(0,a.SU)(m)&&!(0,a.SU)(d).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n._)("span",null,[(0,n.Wm)(u,{keypath:"user.PROFILE.ERRORED_EMAIL_UPDATE"},{default:(0,n.w5)((()=>[(0,n.Wm)(r,{to:"/login"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("user.LOG_IN")),1)])),_:1})])),_:1})])])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-8c2ec9ce"]]);var m=S},6266:function(e,t,r){r.r(t),r.d(t,{default:function(){return d}});var n=r(6252),a=r(2262),s=r(5801),u=r(9917);const o=e=>((0,n.dD)("data-v-05463732"),e=e(),(0,n.Cn)(),e),i={key:0,id:"profile",class:"container view"},c=o((()=>(0,n._)("div",{id:"bottom"},null,-1)));var l=(0,n.aZ)({__name:"ProfileView",setup(e){const t=(0,u.o)(),r=(0,n.Fl)((()=>t.getters[s.YN.GETTERS.AUTH_USER_PROFILE]));return(e,t)=>{const s=(0,n.up)("router-view");return(0,a.SU)(r).username?((0,n.wg)(),(0,n.iD)("div",i,[(0,n.Wm)(s,{user:(0,a.SU)(r)},null,8,["user"]),c])):(0,n.kq)("",!0)}}}),E=r(3744);const _=(0,E.Z)(l,[["__scopeId","data-v-05463732"]]);var d=_},9453:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var n=r(6252),a=r(2262),s=r(2201),u=r(2179),o=r(7408),i=r(5801),c=r(9917);const l={key:0,id:"user",class:"view"},E={class:"box"};var _=(0,n.aZ)({__name:"UserView",props:{fromAdmin:{type:Boolean}},setup(e){const t=e,{fromAdmin:r}=(0,a.BK)(t),_=(0,s.yj)(),d=(0,c.o)(),S=(0,n.Fl)((()=>d.getters[i.RT.GETTERS.USER]));return(0,n.wF)((()=>{_.params.username&&"string"===typeof _.params.username&&d.dispatch(i.RT.ACTIONS.GET_USER,_.params.username)})),(0,n.Jd)((()=>{d.dispatch(i.RT.ACTIONS.EMPTY_USER)})),(e,t)=>(0,a.SU)(S).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(u.Z,{user:(0,a.SU)(S)},null,8,["user"]),(0,n._)("div",E,[(0,n.Wm)(o.Z,{user:(0,a.SU)(S),"from-admin":(0,a.SU)(r)},null,8,["user","from-admin"])])])):(0,n.kq)("",!0)}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-af7007f4"]]);var m=S}}]); "use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[845],{4264:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),i=r(5801),c=r(9917);const l={key:0,id:"account-confirmation",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"AccountConfirmationView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,c.o)(),d=(0,n.Fl)((()=>_.getters[i.SY.GETTERS.ERROR_MESSAGES])),S=(0,n.Fl)((()=>t.query.token));function m(){S.value?_.dispatch(i.YN.ACTIONS.CONFIRM_ACCOUNT,{token:S.value}):r.push("/")}return(0,n.wF)((()=>m())),(0,n.Ah)((()=>_.commit(i.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(e,t)=>{const r=(0,n.up)("router-link");return(0,a.SU)(d)?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n.Wm)(r,{class:"links",to:"/account-confirmation/resend"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("buttons.ACCOUNT-CONFIRMATION-RESEND"))+"? ",1)])),_:1})])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-785df978"]]);var m=S},8160:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});r(7658);var n=r(6252),a=r(2262),s=r(3577),u=r(2201),o=r(7167),i=r(5801),c=r(9917);const l={key:0,id:"email-update",class:"center-card with-margin"},E={class:"error-message"};var _=(0,n.aZ)({__name:"EmailUpdateView",setup(e){const t=(0,u.yj)(),r=(0,u.tv)(),_=(0,c.o)(),d=(0,n.Fl)((()=>_.getters[i.YN.GETTERS.AUTH_USER_PROFILE])),S=(0,n.Fl)((()=>_.getters[i.YN.GETTERS.IS_AUTHENTICATED])),m=(0,n.Fl)((()=>_.getters[i.SY.GETTERS.ERROR_MESSAGES])),p=(0,n.Fl)((()=>t.query.token));function R(){p.value?_.dispatch(i.YN.ACTIONS.CONFIRM_EMAIL,{token:p.value,refreshUser:S.value}):r.push("/")}return(0,n.wF)((()=>R())),(0,n.Ah)((()=>_.commit(i.SY.MUTATIONS.EMPTY_ERROR_MESSAGES))),(0,n.YP)((()=>m.value),(e=>{d.value.username&&e&&r.push("/")})),(e,t)=>{const r=(0,n.up)("router-link"),u=(0,n.up)("i18n-t");return(0,a.SU)(m)&&!(0,a.SU)(d).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(o.Z),(0,n._)("p",E,[(0,n._)("span",null,(0,s.zw)(e.$t("error.SOMETHING_WRONG"))+".",1),(0,n._)("span",null,[(0,n.Wm)(u,{keypath:"user.PROFILE.ERRORED_EMAIL_UPDATE"},{default:(0,n.w5)((()=>[(0,n.Wm)(r,{to:"/login"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.$t("user.LOG_IN")),1)])),_:1})])),_:1})])])])):(0,n.kq)("",!0)}}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-8c2ec9ce"]]);var m=S},6266:function(e,t,r){r.r(t),r.d(t,{default:function(){return d}});var n=r(6252),a=r(2262),s=r(5801),u=r(9917);const o=e=>((0,n.dD)("data-v-05463732"),e=e(),(0,n.Cn)(),e),i={key:0,id:"profile",class:"container view"},c=o((()=>(0,n._)("div",{id:"bottom"},null,-1)));var l=(0,n.aZ)({__name:"ProfileView",setup(e){const t=(0,u.o)(),r=(0,n.Fl)((()=>t.getters[s.YN.GETTERS.AUTH_USER_PROFILE]));return(e,t)=>{const s=(0,n.up)("router-view");return(0,a.SU)(r).username?((0,n.wg)(),(0,n.iD)("div",i,[(0,n.Wm)(s,{user:(0,a.SU)(r)},null,8,["user"]),c])):(0,n.kq)("",!0)}}}),E=r(3744);const _=(0,E.Z)(l,[["__scopeId","data-v-05463732"]]);var d=_},9453:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var n=r(6252),a=r(2262),s=r(2201),u=r(2179),o=r(1585),i=r(5801),c=r(9917);const l={key:0,id:"user",class:"view"},E={class:"box"};var _=(0,n.aZ)({__name:"UserView",props:{fromAdmin:{type:Boolean}},setup(e){const t=e,{fromAdmin:r}=(0,a.BK)(t),_=(0,s.yj)(),d=(0,c.o)(),S=(0,n.Fl)((()=>d.getters[i.RT.GETTERS.USER]));return(0,n.wF)((()=>{_.params.username&&"string"===typeof _.params.username&&d.dispatch(i.RT.ACTIONS.GET_USER,_.params.username)})),(0,n.Jd)((()=>{d.dispatch(i.RT.ACTIONS.EMPTY_USER)})),(e,t)=>(0,a.SU)(S).username?((0,n.wg)(),(0,n.iD)("div",l,[(0,n.Wm)(u.Z,{user:(0,a.SU)(S)},null,8,["user"]),(0,n._)("div",E,[(0,n.Wm)(o.Z,{user:(0,a.SU)(S),"from-admin":(0,a.SU)(r)},null,8,["user","from-admin"])])])):(0,n.kq)("",!0)}}),d=r(3744);const S=(0,d.Z)(_,[["__scopeId","data-v-af7007f4"]]);var m=S}}]);
//# sourceMappingURL=profile.dd30724d.js.map //# sourceMappingURL=profile.23749cd8.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7885:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var a=t(6252),r=t(2262),l=t(3577),o=(t(7658),t(9150)),n=t(4998);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-30799d13"]]);var Z=F,x=t(5630),D=t(5801),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]); "use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{7885:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});var a=t(6252),r=t(2262),l=t(3577),o=(t(7658),t(9150)),n=t(4998);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-30799d13"]]);var Z=F,x=t(5630),D=t(5801),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
//# sourceMappingURL=statistics.9cd652fd.js.map //# sourceMappingURL=statistics.eaf4afd3.js.map

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

View File

@ -0,0 +1,31 @@
"""Add date_format for date display to user preferences in DB
Revision ID: bf13b8f5589d
Revises: 84d840ce853b
Create Date: 2022-10-25 18:53:59.378423
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'bf13b8f5589d'
down_revision = '5b936821326d'
branch_labels = None
depends_on = None
def upgrade():
op.add_column(
'users', sa.Column('date_format', sa.String(length=50), nullable=True)
)
op.execute("UPDATE users SET date_format = 'MM/dd/yyyy'")
op.alter_column('users', 'date_format', nullable=False)
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'date_format')
# ### end Alembic commands ###

View File

@ -235,6 +235,27 @@ class TestUserRegistration(ApiTestCaseMixin):
assert data['status'] == 'success' assert data['status'] == 'success'
assert 'auth_token' not in data assert 'auth_token' not in data
def test_it_creates_user_with_default_date_format(
self, app: Flask
) -> None:
client = app.test_client()
username = self.random_string()
client.post(
'/api/auth/register',
data=json.dumps(
dict(
username=username,
email=self.random_email(),
password=self.random_string(),
)
),
content_type='application/json',
)
new_user = User.query.filter_by(username=username).first()
assert new_user.date_format == 'MM/dd/yyyy'
@pytest.mark.parametrize( @pytest.mark.parametrize(
'input_language,expected_language', 'input_language,expected_language',
[('en', 'en'), ('fr', 'fr'), ('invalid', 'en'), (None, 'en')], [('en', 'en'), ('fr', 'fr'), ('invalid', 'en'), (None, 'en')],
@ -1394,6 +1415,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
language=input_language, language=input_language,
imperial_units=True, imperial_units=True,
display_ascent=False, display_ascent=False,
date_format='yyyy-MM-dd',
) )
), ),
headers=dict(Authorization=f'Bearer {auth_token}'), headers=dict(Authorization=f'Bearer {auth_token}'),
@ -1407,6 +1429,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
assert data['data']['imperial_units'] is True assert data['data']['imperial_units'] is True
assert data['data']['language'] == expected_language assert data['data']['language'] == expected_language
assert data['data']['timezone'] == 'America/New_York' assert data['data']['timezone'] == 'America/New_York'
assert data['data']['date_format'] == 'yyyy-MM-dd'
assert data['data']['weekm'] is True assert data['data']['weekm'] is True
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -173,6 +173,7 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]:
if not user: if not user:
new_user = User(username=username, email=email, password=password) new_user = User(username=username, email=email, password=password)
new_user.timezone = 'Europe/Paris' new_user.timezone = 'Europe/Paris'
new_user.date_format = 'MM/dd/yyyy'
new_user.confirmation_token = secrets.token_urlsafe(30) new_user.confirmation_token = secrets.token_urlsafe(30)
new_user.language = language new_user.language = language
db.session.add(new_user) db.session.add(new_user)
@ -780,6 +781,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
"bio": null, "bio": null,
"birth_date": null, "birth_date": null,
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT", "created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
"date_format": "MM/dd/yyyy",
"display_ascent": true, "display_ascent": true,
"email": "sam@example.com", "email": "sam@example.com",
"first_name": null, "first_name": null,
@ -854,6 +856,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
} }
:<json boolean display_ascent: display highest ascent records and total :<json boolean display_ascent: display highest ascent records and total
:<json string date_format: the format used to display dates in the app
:<json boolean imperial_units: display distance in imperial units :<json boolean imperial_units: display distance in imperial units
:<json string language: language preferences :<json string language: language preferences
:<json string timezone: user time zone :<json string timezone: user time zone
@ -874,6 +877,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
# get post data # get post data
post_data = request.get_json() post_data = request.get_json()
user_mandatory_data = { user_mandatory_data = {
'date_format',
'display_ascent', 'display_ascent',
'imperial_units', 'imperial_units',
'language', 'language',
@ -883,6 +887,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
if not post_data or not post_data.keys() >= user_mandatory_data: if not post_data or not post_data.keys() >= user_mandatory_data:
return InvalidPayloadErrorResponse() return InvalidPayloadErrorResponse()
date_format = post_data.get('date_format')
display_ascent = post_data.get('display_ascent') display_ascent = post_data.get('display_ascent')
imperial_units = post_data.get('imperial_units') imperial_units = post_data.get('imperial_units')
language = get_language(post_data.get('language')) language = get_language(post_data.get('language'))
@ -890,6 +895,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
weekm = post_data.get('weekm') weekm = post_data.get('weekm')
try: try:
auth_user.date_format = date_format
auth_user.display_ascent = display_ascent auth_user.display_ascent = display_ascent
auth_user.imperial_units = imperial_units auth_user.imperial_units = imperial_units
auth_user.language = language auth_user.language = language

View File

@ -33,6 +33,7 @@ class User(BaseModel):
bio = db.Column(db.String(200), nullable=True) bio = db.Column(db.String(200), nullable=True)
picture = db.Column(db.String(255), nullable=True) picture = db.Column(db.String(255), nullable=True)
timezone = db.Column(db.String(50), nullable=True) timezone = db.Column(db.String(50), nullable=True)
date_format = db.Column(db.String(50), nullable=True)
# does the week start Monday? # does the week start Monday?
weekm = db.Column(db.Boolean, default=False, nullable=False) weekm = db.Column(db.Boolean, default=False, nullable=False)
workouts = db.relationship( workouts = db.relationship(
@ -190,6 +191,7 @@ class User(BaseModel):
serialized_user = { serialized_user = {
**serialized_user, **serialized_user,
**{ **{
'date_format': self.date_format,
'display_ascent': self.display_ascent, 'display_ascent': self.display_ascent,
'imperial_units': self.imperial_units, 'imperial_units': self.imperial_units,
'language': self.language, 'language': self.language,

View File

@ -62,9 +62,10 @@
{{ $t('user.PROFILE.REGISTRATION_DATE') }} {{ $t('user.PROFILE.REGISTRATION_DATE') }}
</span> </span>
{{ {{
format( formatDate(
getDateWithTZ(user.created_at, authUser.timezone), user.created_at,
'dd/MM/yyyy HH:mm' authUser.timezone,
authUser.date_format
) )
}} }}
</td> </td>
@ -130,7 +131,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { format } from 'date-fns'
import { import {
ComputedRef, ComputedRef,
Ref, Ref,
@ -152,7 +152,7 @@
import { IAuthUserProfile, IUserProfile } from '@/types/user' import { IAuthUserProfile, IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getQuery, sortList } from '@/utils/api' import { getQuery, sortList } from '@/utils/api'
import { getDateWithTZ } from '@/utils/dates' import { formatDate } from '@/utils/dates'
const store = useStore() const store = useStore()
const route = useRoute() const route = useRoute()

View File

@ -86,7 +86,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
span { span {
padding: 2px 5px; padding: 2px;
} }
.record-type { .record-type {
flex-grow: 1; flex-grow: 1;
@ -94,7 +94,12 @@
.record-value { .record-value {
font-weight: bold; font-weight: bold;
white-space: nowrap; white-space: nowrap;
padding-right: $default-padding * 2; padding-right: $default-padding;
}
.record-date {
white-space: nowrap;
min-width: 30%;
text-align: right;
} }
} }
} }

View File

@ -43,7 +43,8 @@
translateSports(props.sports, t), translateSports(props.sports, t),
props.user.timezone, props.user.timezone,
props.user.imperial_units, props.user.imperial_units,
props.user.display_ascent props.user.display_ascent,
props.user.date_format
) )
) )
</script> </script>

View File

@ -130,6 +130,8 @@
import { TAppConfig } from '@/types/application' import { TAppConfig } from '@/types/application'
import { IAuthUserProfile, IUserProfile } from '@/types/user' import { IAuthUserProfile, IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { formatDate, getDateFormat } from '@/utils/dates'
import { localeFromLanguage } from '@/utils/locales'
interface Props { interface Props {
user: IUserProfile user: IUserProfile
@ -142,17 +144,28 @@
const store = useStore() const store = useStore()
const { user, fromAdmin } = toRefs(props) const { user, fromAdmin } = toRefs(props)
const language: ComputedRef<string> = computed(
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
)
const authUser: ComputedRef<IAuthUserProfile> = computed( const authUser: ComputedRef<IAuthUserProfile> = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE] () => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
) )
const registrationDate = computed(() => const registrationDate = computed(() =>
props.user.created_at props.user.created_at
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm') ? formatDate(
props.user.created_at,
authUser.value.timezone,
authUser.value.date_format
)
: '' : ''
) )
const birthDate = computed(() => const birthDate = computed(() =>
props.user.birth_date props.user.birth_date
? format(new Date(props.user.birth_date), 'dd/MM/yyyy') ? format(
new Date(props.user.birth_date),
`${getDateFormat(authUser.value.date_format, language.value)}`,
{ locale: localeFromLanguage[language.value] }
)
: '' : ''
) )
const isSuccess = computed( const isSuccess = computed(

View File

@ -2,9 +2,11 @@
<div id="user-preferences" class="description-list"> <div id="user-preferences" class="description-list">
<dl> <dl>
<dt>{{ $t('user.PROFILE.LANGUAGE') }}:</dt> <dt>{{ $t('user.PROFILE.LANGUAGE') }}:</dt>
<dd>{{ language }}</dd> <dd>{{ userLanguage }}</dd>
<dt>{{ $t('user.PROFILE.TIMEZONE') }}:</dt> <dt>{{ $t('user.PROFILE.TIMEZONE') }}:</dt>
<dd>{{ timezone }}</dd> <dd>{{ timezone }}</dd>
<dt>{{ $t('user.PROFILE.DATE_FORMAT') }}:</dt>
<dd>{{ getDateFormat(date_format, appLanguage) }}</dd>
<dt>{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}:</dt> <dt>{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}:</dt>
<dd>{{ $t(`user.PROFILE.${fistDayOfWeek}`) }}</dd> <dd>{{ $t(`user.PROFILE.${fistDayOfWeek}`) }}</dd>
<dt>{{ $t('user.PROFILE.UNITS.LABEL') }}:</dt> <dt>{{ $t('user.PROFILE.UNITS.LABEL') }}:</dt>
@ -28,9 +30,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ComputedRef } from 'vue'
import { ROOT_STORE } from '@/store/constants'
import { IAuthUserProfile } from '@/types/user' import { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { getDateFormat } from '@/utils/dates'
import { languageLabels } from '@/utils/locales' import { languageLabels } from '@/utils/locales'
interface Props { interface Props {
@ -38,7 +43,12 @@
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const language = computed(() => const store = useStore()
const appLanguage: ComputedRef<string> = computed(
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
)
const userLanguage = computed(() =>
props.user.language props.user.language
? languageLabels[props.user.language] ? languageLabels[props.user.language]
: languageLabels['en'] : languageLabels['en']
@ -47,6 +57,9 @@
const timezone = computed(() => const timezone = computed(() =>
props.user.timezone ? props.user.timezone : 'Europe/Paris' props.user.timezone ? props.user.timezone : 'Europe/Paris'
) )
const date_format = computed(() =>
props.user.date_format ? props.user.date_format : 'MM/dd/yyyy'
)
const display_ascent = computed(() => const display_ascent = computed(() =>
props.user.display_ascent ? 'DISPLAYED' : 'HIDDEN' props.user.display_ascent ? 'DISPLAYED' : 'HIDDEN'
) )

View File

@ -65,11 +65,12 @@
import { ComputedRef, computed, reactive, onMounted, onUnmounted } from 'vue' import { ComputedRef, computed, reactive, onMounted, onUnmounted } from 'vue'
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants' import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
import { IUserProfile, IUserPayload } from '@/types/user' import { IUserProfile, IUserPayload, IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { formatDate } from '@/utils/dates'
interface Props { interface Props {
user: IUserProfile user: IAuthUserProfile
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@ -84,7 +85,11 @@
}) })
const registrationDate = computed(() => const registrationDate = computed(() =>
props.user.created_at props.user.created_at
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm') ? formatDate(
props.user.created_at,
props.user.timezone,
props.user.date_format
)
: '' : ''
) )
const loading = computed( const loading = computed(

View File

@ -23,6 +23,22 @@
@updateTimezone="updateTZ" @updateTimezone="updateTZ"
/> />
</label> </label>
<label class="form-items">
{{ $t('user.PROFILE.DATE_FORMAT') }}
<select
id="date_format"
v-model="userForm.date_format"
:disabled="loading"
>
<option
v-for="dateFormat in dateFormatOptions"
:value="dateFormat.value"
:key="dateFormat.value"
>
{{ dateFormat.label }}
</option>
</select>
</label>
<div class="form-items form-checkboxes"> <div class="form-items form-checkboxes">
<span class="checkboxes-label"> <span class="checkboxes-label">
{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }} {{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
@ -106,6 +122,7 @@
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants' import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
import { IUserPreferencesPayload, IAuthUserProfile } from '@/types/user' import { IUserPreferencesPayload, IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { availableDateFormatOptions } from '@/utils/dates'
import { availableLanguages } from '@/utils/locales' import { availableLanguages } from '@/utils/locales'
interface Props { interface Props {
@ -120,6 +137,7 @@
imperial_units: false, imperial_units: false,
language: '', language: '',
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'dd/MM/yyyy',
weekm: false, weekm: false,
}) })
const weekStart = [ const weekStart = [
@ -158,6 +176,13 @@
const errorMessages: ComputedRef<string | string[] | null> = computed( const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES] () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
) )
const dateFormatOptions = computed(() =>
availableDateFormatOptions(
new Date().toUTCString(),
props.user.timezone,
userForm.language
)
)
onMounted(() => { onMounted(() => {
if (props.user) { if (props.user) {
@ -170,6 +195,7 @@
userForm.imperial_units = user.imperial_units ? user.imperial_units : false userForm.imperial_units = user.imperial_units ? user.imperial_units : false
userForm.language = user.language ? user.language : 'en' userForm.language = user.language ? user.language : 'en'
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris' userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
userForm.date_format = user.date_format ? user.date_format : 'dd/MM/yyyy'
userForm.weekm = user.weekm ? user.weekm : false userForm.weekm = user.weekm ? user.weekm : false
} }
function updateProfile() { function updateProfile() {

View File

@ -50,9 +50,10 @@
<dt>{{ capitalize($t('oauth2.APP.ISSUE_AT')) }}:</dt> <dt>{{ capitalize($t('oauth2.APP.ISSUE_AT')) }}:</dt>
<dd> <dd>
{{ {{
format( formatDate(
getDateWithTZ(client.issued_at, authUser.timezone), client.issued_at,
'dd/MM/yyyy HH:mm' authUser.timezone,
authUser.date_format
) )
}} }}
</dd> </dd>
@ -105,7 +106,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { format } from 'date-fns'
import { import {
ComputedRef, ComputedRef,
Ref, Ref,
@ -124,7 +124,7 @@
import { IOAuth2Client } from '@/types/oauth' import { IOAuth2Client } from '@/types/oauth'
import { IAuthUserProfile } from '@/types/user' import { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getDateWithTZ } from '@/utils/dates' import { formatDate } from '@/utils/dates'
interface Props { interface Props {
authUser: IAuthUserProfile authUser: IAuthUserProfile

View File

@ -9,9 +9,10 @@
<span class="app-issued-at"> <span class="app-issued-at">
{{ $t('oauth2.APP.ISSUE_AT') }} {{ $t('oauth2.APP.ISSUE_AT') }}
{{ {{
format( formatDate(
getDateWithTZ(client.issued_at, authUser.timezone), client.issued_at,
'dd/MM/yyyy HH:mm' authUser.timezone,
authUser.date_format
) )
}} }}
</span> </span>
@ -34,7 +35,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { format } from 'date-fns'
import { ComputedRef, computed, onBeforeMount, toRefs, watch } from 'vue' import { ComputedRef, computed, onBeforeMount, toRefs, watch } from 'vue'
import { LocationQuery, useRoute } from 'vue-router' import { LocationQuery, useRoute } from 'vue-router'
@ -45,7 +45,7 @@
import { IAuthUserProfile } from '@/types/user' import { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { defaultPage, getNumberQueryValue } from '@/utils/api' import { defaultPage, getNumberQueryValue } from '@/utils/api'
import { getDateWithTZ } from '@/utils/dates' import { formatDate } from '@/utils/dates'
interface Props { interface Props {
authUser: IAuthUserProfile authUser: IAuthUserProfile

View File

@ -29,10 +29,7 @@
class="workout-date" class="workout-date"
v-if="workout.workout_date && user" v-if="workout.workout_date && user"
:title=" :title="
format( formatDate(workout.workout_date, user.timezone, user.date_format)
getDateWithTZ(workout.workout_date, user.timezone),
'dd/MM/yyyy HH:mm'
)
" "
> >
{{ {{
@ -141,7 +138,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Locale, format, formatDistance } from 'date-fns' import { Locale, formatDistance } from 'date-fns'
import { ComputedRef, computed, toRefs, withDefaults } from 'vue' import { ComputedRef, computed, toRefs, withDefaults } from 'vue'
import StaticMap from '@/components/Common/StaticMap.vue' import StaticMap from '@/components/Common/StaticMap.vue'
@ -151,7 +148,7 @@
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
import { IWorkout } from '@/types/workouts' import { IWorkout } from '@/types/workouts'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getDateWithTZ } from '@/utils/dates' import { formatDate } from '@/utils/dates'
interface Props { interface Props {
user: IUserProfile user: IUserProfile

View File

@ -131,7 +131,8 @@
getDateWithTZ( getDateWithTZ(
props.workoutData.workout.workout_date, props.workoutData.workout.workout_date,
props.authUser.timezone props.authUser.timezone
) ),
props.authUser.date_format
) )
return { return {
ascent: segment ? segment.ascent : workout.ascent, ascent: segment ? segment.ascent : workout.ascent,

View File

@ -84,9 +84,10 @@
{{ $t('workouts.DATE') }} {{ $t('workouts.DATE') }}
</span> </span>
{{ {{
format( formatDate(
getDateWithTZ(workout.workout_date, user.timezone), workout.workout_date,
'dd/MM/yyyy HH:mm' user.timezone,
user.date_format
) )
}} }}
</td> </td>
@ -162,7 +163,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { format } from 'date-fns'
import { import {
ComputedRef, ComputedRef,
Ref, Ref,
@ -186,7 +186,7 @@
import { IWorkout, TWorkoutsPayload } from '@/types/workouts' import { IWorkout, TWorkoutsPayload } from '@/types/workouts'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getQuery, sortList, workoutsPayloadKeys } from '@/utils/api' import { getQuery, sortList, workoutsPayloadKeys } from '@/utils/api'
import { getDateWithTZ } from '@/utils/dates' import { formatDate } from '@/utils/dates'
import { getSportColor, getSportLabel } from '@/utils/sports' import { getSportColor, getSportLabel } from '@/utils/sports'
import { convertDistance } from '@/utils/units' import { convertDistance } from '@/utils/units'
import { defaultOrder } from '@/utils/workouts' import { defaultOrder } from '@/utils/workouts'

View File

@ -52,6 +52,7 @@
"BACK_TO_PROFILE": "Zurück zum Profil", "BACK_TO_PROFILE": "Zurück zum Profil",
"BIO": "Biographie", "BIO": "Biographie",
"BIRTH_DATE": "Geburtsdatum", "BIRTH_DATE": "Geburtsdatum",
"DATE_FORMAT": "Datumsanzeigeformat",
"EDIT": "Profil bearbeiten", "EDIT": "Profil bearbeiten",
"EDIT_PREFERENCES": "Einstellungen ändern", "EDIT_PREFERENCES": "Einstellungen ändern",
"EDIT_SPORTS_PREFERENCES": "Einstellungen für Sportarten ändern", "EDIT_SPORTS_PREFERENCES": "Einstellungen für Sportarten ändern",

View File

@ -52,6 +52,7 @@
"BACK_TO_PROFILE": "Back to profile", "BACK_TO_PROFILE": "Back to profile",
"BIO": "Bio", "BIO": "Bio",
"BIRTH_DATE": "Birth date", "BIRTH_DATE": "Birth date",
"DATE_FORMAT": "Date display format",
"EDIT": "Edit profile", "EDIT": "Edit profile",
"EDIT_PREFERENCES": "Edit preferences", "EDIT_PREFERENCES": "Edit preferences",
"EDIT_SPORTS_PREFERENCES": "Edit sports preferences", "EDIT_SPORTS_PREFERENCES": "Edit sports preferences",

View File

@ -52,6 +52,7 @@
"BACK_TO_PROFILE": "Revenir au profil", "BACK_TO_PROFILE": "Revenir au profil",
"BIO": "Bio", "BIO": "Bio",
"BIRTH_DATE": "Date de naissance", "BIRTH_DATE": "Date de naissance",
"DATE_FORMAT": "Format d'affichage de la date",
"EDIT": "Modifier le profil", "EDIT": "Modifier le profil",
"EDIT_PREFERENCES": "Modifier les préférences", "EDIT_PREFERENCES": "Modifier les préférences",
"EDIT_SPORTS_PREFERENCES": "Modifier les préférences des sports", "EDIT_SPORTS_PREFERENCES": "Modifier les préférences des sports",

View File

@ -29,6 +29,7 @@ export interface IAuthUserProfile extends IUserProfile {
imperial_units: boolean imperial_units: boolean
language: string | null language: string | null
timezone: string timezone: string
date_format: string
weekm: boolean weekm: boolean
} }
@ -64,6 +65,7 @@ export interface IUserPreferencesPayload {
imperial_units: boolean imperial_units: boolean
language: string language: string
timezone: string timezone: string
date_format: string
weekm: boolean weekm: boolean
} }

View File

@ -11,6 +11,11 @@ import {
} from 'date-fns' } from 'date-fns'
import { utcToZonedTime } from 'date-fns-tz' import { utcToZonedTime } from 'date-fns-tz'
import createI18n from '@/i18n'
import { localeFromLanguage } from '@/utils/locales'
const { locale } = createI18n.global
export const getStartDate = ( export const getStartDate = (
duration: string, duration: string,
day: Date, day: Date,
@ -70,11 +75,70 @@ export const formatWorkoutDate = (
if (!dateFormat) { if (!dateFormat) {
dateFormat = 'yyyy/MM/dd' dateFormat = 'yyyy/MM/dd'
} }
dateFormat = getDateFormat(dateFormat, locale.value)
if (!timeFormat) { if (!timeFormat) {
timeFormat = 'HH:mm' timeFormat = 'HH:mm'
} }
return { return {
workout_date: format(dateTime, dateFormat), workout_date: format(dateTime, dateFormat, {
locale: localeFromLanguage[locale.value],
}),
workout_time: format(dateTime, timeFormat), workout_time: format(dateTime, timeFormat),
} }
} }
const availableDateFormats = [
'MM/dd/yyyy',
'dd/MM/yyyy',
'yyyy-MM-dd',
'date_string',
]
const dateStringFormats: Record<string, string> = {
de: 'do MMM yyyy',
en: 'MMM. do, yyyy',
fr: 'd MMM yyyy',
}
export const getDateFormat = (dateFormat: string, language: string): string => {
return dateFormat === 'date_string' ? dateStringFormats[language] : dateFormat
}
export const formatDate = (
dateString: string,
timezone: string,
dateFormat: string,
withTime = true,
language: string | null = null
): string => {
if (!language) {
language = locale.value
}
return format(
getDateWithTZ(dateString, timezone),
`${getDateFormat(dateFormat, language)}${withTime ? ' HH:mm' : ''}`,
{ locale: localeFromLanguage[language] }
)
}
export const availableDateFormatOptions = (
inputDate: string,
timezone: string,
language: string | null = null
) => {
const l: string = language ? language : locale.value
const options: Record<string, string>[] = []
availableDateFormats.map((df) => {
const dateFormat = getDateFormat(df, l)
options.push({
label: `${dateFormat} - ${formatDate(
inputDate,
timezone,
dateFormat,
false,
l
)}`,
value: df,
})
})
return options
}

View File

@ -1,13 +1,17 @@
import createI18n from '@/i18n'
import { ITranslatedSport } from '@/types/sports' import { ITranslatedSport } from '@/types/sports'
import { TUnit } from '@/types/units' import { TUnit } from '@/types/units'
import { ICardRecord, IRecord, IRecordsBySports } from '@/types/workouts' import { ICardRecord, IRecord, IRecordsBySports } from '@/types/workouts'
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates' import { formatDate, getDateFormat } from '@/utils/dates'
import { convertDistance, units } from '@/utils/units' import { convertDistance, units } from '@/utils/units'
const { locale } = createI18n.global
export const formatRecord = ( export const formatRecord = (
record: IRecord, record: IRecord,
tz: string, tz: string,
useImperialUnits: boolean useImperialUnits: boolean,
date_format: string
): Record<string, string | number> => { ): Record<string, string | number> => {
const distanceUnitFrom: TUnit = 'km' const distanceUnitFrom: TUnit = 'km'
const distanceUnitTo: TUnit = useImperialUnits const distanceUnitTo: TUnit = useImperialUnits
@ -53,8 +57,7 @@ export const formatRecord = (
) )
} }
return { return {
workout_date: formatWorkoutDate(getDateWithTZ(record.workout_date, tz)) workout_date: formatDate(record.workout_date, tz, date_format, false),
.workout_date,
workout_id: record.workout_id, workout_id: record.workout_id,
id: record.id, id: record.id,
record_type: record.record_type, record_type: record.record_type,
@ -73,9 +76,11 @@ export const getRecordsBySports = (
translatedSports: ITranslatedSport[], translatedSports: ITranslatedSport[],
tz: string, tz: string,
useImperialUnits: boolean, useImperialUnits: boolean,
display_ascent: boolean display_ascent: boolean,
): IRecordsBySports => date_format: string
records ): IRecordsBySports => {
date_format = getDateFormat(date_format, locale.value)
return records
.filter((r) => (display_ascent ? true : r.record_type !== 'HA')) .filter((r) => (display_ascent ? true : r.record_type !== 'HA'))
.reduce((sportList: IRecordsBySports, record) => { .reduce((sportList: IRecordsBySports, record) => {
const sport = translatedSports.find((s) => s.id === record.sport_id) const sport = translatedSports.find((s) => s.id === record.sport_id)
@ -88,8 +93,9 @@ export const getRecordsBySports = (
} }
} }
sportList[sport.translatedLabel].records.push( sportList[sport.translatedLabel].records.push(
formatRecord(record, tz, useImperialUnits) formatRecord(record, tz, useImperialUnits, date_format)
) )
} }
return sportList return sportList
}, {}) }, {})
}

View File

@ -5,6 +5,9 @@ import {
incrementDate, incrementDate,
getStartDate, getStartDate,
formatWorkoutDate, formatWorkoutDate,
formatDate,
availableDateFormatOptions,
getDateFormat,
} from '@/utils/dates' } from '@/utils/dates'
describe('startDate (week starting Sunday)', () => { describe('startDate (week starting Sunday)', () => {
@ -240,3 +243,158 @@ describe('formatWorkoutDate', () => {
}) })
}) })
}) })
describe('formatDate', () => {
const dateString = 'Tue, 01 Nov 2022 00:00:00 GMT'
const testsParams = [
{
description:
'format date for "Europe/Paris" timezone and "dd/MM/yyyy" format (with time)',
inputParams: {
timezone: 'Europe/Paris',
dateFormat: 'dd/MM/yyyy',
withTime: true,
},
expectedDate: '01/11/2022 01:00',
},
{
description:
'format date for "America/New_York" timezone and "MM/dd/yyyy" format (w/o time)',
inputParams: {
timezone: 'America/New_York',
dateFormat: 'MM/dd/yyyy',
withTime: false,
},
expectedDate: '10/31/2022',
},
]
testsParams.map((testParams) => {
it(testParams.description, () => {
assert.deepEqual(
formatDate(
dateString,
testParams.inputParams.timezone,
testParams.inputParams.dateFormat,
testParams.inputParams.withTime
),
testParams.expectedDate
)
})
})
})
describe('formatDate (w/ default value)', () => {
it('format date for "Europe/Paris" timezone and "dd/MM/yyyy" format', () => {
assert.deepEqual(
formatDate('Tue, 01 Nov 2022 00:00:00 GMT', 'Europe/Paris', 'yyyy-MM-dd'),
'2022-11-01 01:00'
)
})
})
describe('getDateFormat', () => {
const testsParams = [
{
inputParams: {
dateFormat: 'dd/MM/yyyy',
language: 'en',
},
expectedFormat: 'dd/MM/yyyy',
},
{
inputParams: {
dateFormat: 'MM/dd/yyyy',
language: 'en',
},
expectedFormat: 'MM/dd/yyyy',
},
{
inputParams: {
dateFormat: 'yyyy-MM-dd',
language: 'en',
},
expectedFormat: 'yyyy-MM-dd',
},
{
inputParams: {
dateFormat: 'date_string',
language: 'en',
},
expectedFormat: 'MMM. do, yyyy',
},
{
inputParams: {
dateFormat: 'date_string',
language: 'fr',
},
expectedFormat: 'd MMM yyyy',
},
{
inputParams: {
dateFormat: 'date_string',
language: 'de',
},
expectedFormat: 'do MMM yyyy',
},
]
testsParams.map((testParams) => {
it(`get date format for "${testParams.inputParams.language}" and "${testParams.inputParams.dateFormat}" `, () => {
assert.deepEqual(
getDateFormat(
testParams.inputParams.dateFormat,
testParams.inputParams.language
),
testParams.expectedFormat
)
})
})
})
describe('availableDateFormatOptions', () => {
const inputDate = `Sun, 9 Oct 2022 18:18:41 GMT`
const inputTimezone = `Europe/Paris`
const testsParams = [
{
inputLanguage: 'en',
expectedOptions: [
{ label: 'MM/dd/yyyy - 10/09/2022', value: 'MM/dd/yyyy' },
{ label: 'dd/MM/yyyy - 09/10/2022', value: 'dd/MM/yyyy' },
{ label: 'yyyy-MM-dd - 2022-10-09', value: 'yyyy-MM-dd' },
{ label: 'MMM. do, yyyy - Oct. 9th, 2022', value: 'date_string' },
],
},
{
inputLanguage: 'fr',
expectedOptions: [
{ label: 'MM/dd/yyyy - 10/09/2022', value: 'MM/dd/yyyy' },
{ label: 'dd/MM/yyyy - 09/10/2022', value: 'dd/MM/yyyy' },
{ label: 'yyyy-MM-dd - 2022-10-09', value: 'yyyy-MM-dd' },
{ label: 'd MMM yyyy - 9 oct. 2022', value: 'date_string' },
],
},
{
inputLanguage: 'de',
expectedOptions: [
{ label: 'MM/dd/yyyy - 10/09/2022', value: 'MM/dd/yyyy' },
{ label: 'dd/MM/yyyy - 09/10/2022', value: 'dd/MM/yyyy' },
{ label: 'yyyy-MM-dd - 2022-10-09', value: 'yyyy-MM-dd' },
{ label: 'do MMM yyyy - 9. Okt. 2022', value: 'date_string' },
],
},
]
testsParams.map((testParams) => {
it(`returns available options for ${testParams.inputLanguage} locale`, () => {
assert.deepEqual(
availableDateFormatOptions(
inputDate,
inputTimezone,
testParams.inputLanguage
),
testParams.expectedOptions
)
})
})
})

View File

@ -19,6 +19,7 @@ describe('formatRecord', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
id: 9, id: 9,
@ -41,6 +42,7 @@ describe('formatRecord', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/MM/dd'
}, },
expected: { expected: {
id: 10, id: 10,
@ -63,6 +65,7 @@ describe('formatRecord', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/MM/dd'
}, },
expected: { expected: {
id: 11, id: 11,
@ -85,12 +88,13 @@ describe('formatRecord', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'dd/MM/yyyy'
}, },
expected: { expected: {
id: 12, id: 12,
record_type: 'MS', record_type: 'MS',
value: '18 km/h', value: '18 km/h',
workout_date: '2019/07/08', workout_date: '08/07/2019',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
}, },
@ -107,12 +111,13 @@ describe('formatRecord', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'MMM. do, yyyy'
}, },
expected: { expected: {
id: 13, id: 13,
record_type: 'HA', record_type: 'HA',
value: '100 m', value: '100 m',
workout_date: '2019/07/07', workout_date: 'Jul. 7th, 2019',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
}, },
@ -123,7 +128,8 @@ describe('formatRecord', () => {
formatRecord( formatRecord(
testParams.inputParams.record, testParams.inputParams.record,
testParams.inputParams.timezone, testParams.inputParams.timezone,
false false,
testParams.inputParams.date_format
), ),
testParams.expected testParams.expected
) )
@ -146,6 +152,7 @@ describe('formatRecord after conversion', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
id: 9, id: 9,
@ -168,12 +175,13 @@ describe('formatRecord after conversion', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
id: 10, id: 10,
record_type: 'FD', record_type: 'FD',
value: '11.185 mi', value: '11.185 mi',
workout_date: '2019/07/08', workout_date: '2019/08/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
}, },
@ -190,6 +198,7 @@ describe('formatRecord after conversion', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
id: 11, id: 11,
@ -212,12 +221,13 @@ describe('formatRecord after conversion', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
id: 12, id: 12,
record_type: 'MS', record_type: 'MS',
value: '11.18 mi/h', value: '11.18 mi/h',
workout_date: '2019/07/08', workout_date: '2019/08/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
}, },
@ -234,6 +244,7 @@ describe('formatRecord after conversion', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
timezone: 'Europe/Paris', timezone: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
id: 13, id: 13,
@ -250,7 +261,8 @@ describe('formatRecord after conversion', () => {
formatRecord( formatRecord(
testParams.inputParams.record, testParams.inputParams.record,
testParams.inputParams.timezone, testParams.inputParams.timezone,
true true,
testParams.inputParams.date_format
), ),
testParams.expected testParams.expected
) )
@ -272,7 +284,8 @@ describe('formatRecord (invalid record type)', () => {
workout_id: 'hvYBqYBRa7wwXpaStWR4V2', workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
}, },
'Europe/Paris', 'Europe/Paris',
false false,
'yyyy/dd/MM'
) )
).to.throw( ).to.throw(
'Invalid record type, expected: "AS", "FD", "HA", "LD", "MD", got: "M"' 'Invalid record type, expected: "AS", "FD", "HA", "LD", "MD", got: "M"'
@ -287,6 +300,7 @@ describe('getRecordsBySports', () => {
input: { input: {
records: [], records: [],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: {}, expected: {},
}, },
@ -305,6 +319,7 @@ describe('getRecordsBySports', () => {
}, },
], ],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
'Cycling (Sport)': { 'Cycling (Sport)': {
@ -355,6 +370,7 @@ describe('getRecordsBySports', () => {
}, },
], ],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
'Cycling (Sport)': { 'Cycling (Sport)': {
@ -385,7 +401,7 @@ describe('getRecordsBySports', () => {
id: 10, id: 10,
record_type: 'FD', record_type: 'FD',
value: '18 km', value: '18 km',
workout_date: '2019/07/08', workout_date: '2019/08/07',
workout_id: 'n6JcLPQt3QtZWFfiSnYm4C', workout_id: 'n6JcLPQt3QtZWFfiSnYm4C',
}, },
], ],
@ -401,7 +417,8 @@ describe('getRecordsBySports', () => {
translatedSports, translatedSports,
testParams.input.tz, testParams.input.tz,
false, false,
true true,
testParams.input.date_format
), ),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
@ -418,6 +435,7 @@ describe('getRecordsBySports after conversion', () => {
input: { input: {
records: [], records: [],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: {}, expected: {},
}, },
@ -436,6 +454,7 @@ describe('getRecordsBySports after conversion', () => {
}, },
], ],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
'Cycling (Sport)': { 'Cycling (Sport)': {
@ -486,6 +505,7 @@ describe('getRecordsBySports after conversion', () => {
}, },
], ],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
'Cycling (Sport)': { 'Cycling (Sport)': {
@ -516,7 +536,7 @@ describe('getRecordsBySports after conversion', () => {
id: 10, id: 10,
record_type: 'FD', record_type: 'FD',
value: '11.185 mi', value: '11.185 mi',
workout_date: '2019/07/08', workout_date: '2019/08/07',
workout_id: 'n6JcLPQt3QtZWFfiSnYm4C', workout_id: 'n6JcLPQt3QtZWFfiSnYm4C',
}, },
], ],
@ -532,7 +552,8 @@ describe('getRecordsBySports after conversion', () => {
translatedSports, translatedSports,
testParams.input.tz, testParams.input.tz,
true, true,
true true,
testParams.input.date_format
), ),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
@ -549,6 +570,7 @@ describe('getRecordsBySports with HA record', () => {
input: { input: {
records: [], records: [],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: {}, expected: {},
}, },
@ -576,6 +598,7 @@ describe('getRecordsBySports with HA record', () => {
}, },
], ],
tz: 'Europe/Paris', tz: 'Europe/Paris',
date_format: 'yyyy/dd/MM'
}, },
expected: { expected: {
'Cycling (Sport)': { 'Cycling (Sport)': {
@ -602,7 +625,8 @@ describe('getRecordsBySports with HA record', () => {
translatedSports, translatedSports,
testParams.input.tz, testParams.input.tz,
false, false,
false false,
testParams.input.date_format
), ),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore