leon 2 vuotta sitten
vanhempi
commit
e883e3e940

+ 1 - 0
ios/Gemfile

@@ -2,3 +2,4 @@
 
 source "https://rubygems.org"
 gem 'fastlane'
+gem 'activesupport', '~> 7.0', '<= 7.0.8'

+ 0 - 218
ios/Gemfile.lock

@@ -1,218 +0,0 @@
-GEM
-  remote: https://rubygems.org/
-  specs:
-    CFPropertyList (3.0.5)
-      rexml
-    addressable (2.8.0)
-      public_suffix (>= 2.0.2, < 5.0)
-    artifactory (3.0.15)
-    atomos (0.1.3)
-    aws-eventstream (1.2.0)
-    aws-partitions (1.587.0)
-    aws-sdk-core (3.130.2)
-      aws-eventstream (~> 1, >= 1.0.2)
-      aws-partitions (~> 1, >= 1.525.0)
-      aws-sigv4 (~> 1.1)
-      jmespath (~> 1.0)
-    aws-sdk-kms (1.56.0)
-      aws-sdk-core (~> 3, >= 3.127.0)
-      aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.114.0)
-      aws-sdk-core (~> 3, >= 3.127.0)
-      aws-sdk-kms (~> 1)
-      aws-sigv4 (~> 1.4)
-    aws-sigv4 (1.5.0)
-      aws-eventstream (~> 1, >= 1.0.2)
-    babosa (1.0.4)
-    claide (1.1.0)
-    colored (1.2)
-    colored2 (3.1.2)
-    commander (4.6.0)
-      highline (~> 2.0.0)
-    declarative (0.0.20)
-    digest-crc (0.6.4)
-      rake (>= 12.0.0, < 14.0.0)
-    domain_name (0.5.20190701)
-      unf (>= 0.0.5, < 1.0.0)
-    dotenv (2.7.6)
-    emoji_regex (3.2.3)
-    excon (0.92.3)
-    faraday (1.10.0)
-      faraday-em_http (~> 1.0)
-      faraday-em_synchrony (~> 1.0)
-      faraday-excon (~> 1.1)
-      faraday-httpclient (~> 1.0)
-      faraday-multipart (~> 1.0)
-      faraday-net_http (~> 1.0)
-      faraday-net_http_persistent (~> 1.0)
-      faraday-patron (~> 1.0)
-      faraday-rack (~> 1.0)
-      faraday-retry (~> 1.0)
-      ruby2_keywords (>= 0.0.4)
-    faraday-cookie_jar (0.0.7)
-      faraday (>= 0.8.0)
-      http-cookie (~> 1.0.0)
-    faraday-em_http (1.0.0)
-    faraday-em_synchrony (1.0.0)
-    faraday-excon (1.1.0)
-    faraday-httpclient (1.0.1)
-    faraday-multipart (1.0.3)
-      multipart-post (>= 1.2, < 3)
-    faraday-net_http (1.0.1)
-    faraday-net_http_persistent (1.2.0)
-    faraday-patron (1.0.0)
-    faraday-rack (1.0.0)
-    faraday-retry (1.0.3)
-    faraday_middleware (1.2.0)
-      faraday (~> 1.0)
-    fastimage (2.2.6)
-    fastlane (2.205.2)
-      CFPropertyList (>= 2.3, < 4.0.0)
-      addressable (>= 2.8, < 3.0.0)
-      artifactory (~> 3.0)
-      aws-sdk-s3 (~> 1.0)
-      babosa (>= 1.0.3, < 2.0.0)
-      bundler (>= 1.12.0, < 3.0.0)
-      colored
-      commander (~> 4.6)
-      dotenv (>= 2.1.1, < 3.0.0)
-      emoji_regex (>= 0.1, < 4.0)
-      excon (>= 0.71.0, < 1.0.0)
-      faraday (~> 1.0)
-      faraday-cookie_jar (~> 0.0.6)
-      faraday_middleware (~> 1.0)
-      fastimage (>= 2.1.0, < 3.0.0)
-      gh_inspector (>= 1.1.2, < 2.0.0)
-      google-apis-androidpublisher_v3 (~> 0.3)
-      google-apis-playcustomapp_v1 (~> 0.1)
-      google-cloud-storage (~> 1.31)
-      highline (~> 2.0)
-      json (< 3.0.0)
-      jwt (>= 2.1.0, < 3)
-      mini_magick (>= 4.9.4, < 5.0.0)
-      multipart-post (~> 2.0.0)
-      naturally (~> 2.2)
-      optparse (~> 0.1.1)
-      plist (>= 3.1.0, < 4.0.0)
-      rubyzip (>= 2.0.0, < 3.0.0)
-      security (= 0.1.3)
-      simctl (~> 1.6.3)
-      terminal-notifier (>= 2.0.0, < 3.0.0)
-      terminal-table (>= 1.4.5, < 2.0.0)
-      tty-screen (>= 0.6.3, < 1.0.0)
-      tty-spinner (>= 0.8.0, < 1.0.0)
-      word_wrap (~> 1.0.0)
-      xcodeproj (>= 1.13.0, < 2.0.0)
-      xcpretty (~> 0.3.0)
-      xcpretty-travis-formatter (>= 0.0.3)
-    gh_inspector (1.1.3)
-    google-apis-androidpublisher_v3 (0.20.0)
-      google-apis-core (>= 0.4, < 2.a)
-    google-apis-core (0.4.2)
-      addressable (~> 2.5, >= 2.5.1)
-      googleauth (>= 0.16.2, < 2.a)
-      httpclient (>= 2.8.1, < 3.a)
-      mini_mime (~> 1.0)
-      representable (~> 3.0)
-      retriable (>= 2.0, < 4.a)
-      rexml
-      webrick
-    google-apis-iamcredentials_v1 (0.10.0)
-      google-apis-core (>= 0.4, < 2.a)
-    google-apis-playcustomapp_v1 (0.7.0)
-      google-apis-core (>= 0.4, < 2.a)
-    google-apis-storage_v1 (0.13.0)
-      google-apis-core (>= 0.4, < 2.a)
-    google-cloud-core (1.6.0)
-      google-cloud-env (~> 1.0)
-      google-cloud-errors (~> 1.0)
-    google-cloud-env (1.6.0)
-      faraday (>= 0.17.3, < 3.0)
-    google-cloud-errors (1.2.0)
-    google-cloud-storage (1.36.2)
-      addressable (~> 2.8)
-      digest-crc (~> 0.4)
-      google-apis-iamcredentials_v1 (~> 0.1)
-      google-apis-storage_v1 (~> 0.1)
-      google-cloud-core (~> 1.6)
-      googleauth (>= 0.16.2, < 2.a)
-      mini_mime (~> 1.0)
-    googleauth (1.1.3)
-      faraday (>= 0.17.3, < 3.a)
-      jwt (>= 1.4, < 3.0)
-      memoist (~> 0.16)
-      multi_json (~> 1.11)
-      os (>= 0.9, < 2.0)
-      signet (>= 0.16, < 2.a)
-    highline (2.0.3)
-    http-cookie (1.0.4)
-      domain_name (~> 0.5)
-    httpclient (2.8.3)
-    jmespath (1.6.1)
-    json (2.6.1)
-    jwt (2.3.0)
-    memoist (0.16.2)
-    mini_magick (4.11.0)
-    mini_mime (1.1.2)
-    multi_json (1.15.0)
-    multipart-post (2.0.0)
-    nanaimo (0.3.0)
-    naturally (2.2.1)
-    optparse (0.1.1)
-    os (1.1.4)
-    plist (3.6.0)
-    public_suffix (4.0.7)
-    rake (13.0.6)
-    representable (3.1.1)
-      declarative (< 0.1.0)
-      trailblazer-option (>= 0.1.1, < 0.2.0)
-      uber (< 0.2.0)
-    retriable (3.1.2)
-    rexml (3.2.5)
-    rouge (2.0.7)
-    ruby2_keywords (0.0.5)
-    rubyzip (2.3.2)
-    security (0.1.3)
-    signet (0.16.1)
-      addressable (~> 2.8)
-      faraday (>= 0.17.5, < 3.0)
-      jwt (>= 1.5, < 3.0)
-      multi_json (~> 1.10)
-    simctl (1.6.8)
-      CFPropertyList
-      naturally
-    terminal-notifier (2.0.0)
-    terminal-table (1.8.0)
-      unicode-display_width (~> 1.1, >= 1.1.1)
-    trailblazer-option (0.1.2)
-    tty-cursor (0.7.1)
-    tty-screen (0.8.1)
-    tty-spinner (0.9.3)
-      tty-cursor (~> 0.7)
-    uber (0.1.0)
-    unf (0.1.4)
-      unf_ext
-    unf_ext (0.0.8.1)
-    unicode-display_width (1.8.0)
-    webrick (1.7.0)
-    word_wrap (1.0.0)
-    xcodeproj (1.21.0)
-      CFPropertyList (>= 2.3.3, < 4.0)
-      atomos (~> 0.1.3)
-      claide (>= 1.0.2, < 2.0)
-      colored2 (~> 3.1)
-      nanaimo (~> 0.3.0)
-      rexml (~> 3.2.4)
-    xcpretty (0.3.0)
-      rouge (~> 2.0.7)
-    xcpretty-travis-formatter (1.0.1)
-      xcpretty (~> 0.2, >= 0.0.7)
-
-PLATFORMS
-  universal-darwin-21
-
-DEPENDENCIES
-  fastlane
-
-BUNDLED WITH
-   2.2.27

+ 4 - 4
src/components/LimitPickers.tsx

@@ -108,7 +108,7 @@ export default function Component(props: { limit: number, onChange: Function, on
     }
 
     function confirm() {
-        const date = new Date();
+        const date = new Date(global.set_time);
         date.setDate(today.getDate() - (6 - values[0]));
         date.setHours(values[1])
         date.setMinutes(values[2])
@@ -149,9 +149,9 @@ export default function Component(props: { limit: number, onChange: Function, on
             <Text style={{ color: '#000', fontSize: 16, fontWeight: 'bold' }}>:</Text>
         </View>
 
-        <View style={{ marginBottom: 20, display: 'flex', flexDirection: 'row', width: '100%' }}>
-            <Text style={{ flex: 1, textAlign: 'center' }} onClick={cancel}>取消</Text>
-            <Text style={{ flex: 1, textAlign: 'center' }} onClick={confirm}>确认</Text>
+        <View style={{ marginBottom: 20,marginTop:20, display: 'flex', flexDirection: 'row', width: '100%' }}>
+            <Text style={{ flex: 1, textAlign: 'center',height:50 }} onClick={cancel}>取消</Text>
+            <Text style={{ flex: 1, textAlign: 'center',height:50 }} onClick={confirm}>确认</Text>
         </View>
     </View>
 }

+ 3 - 3
src/components/PickerViews.tsx

@@ -41,9 +41,9 @@ export default function Component(props: { value: any, onChange: Function, items
 
     </PickerView >
         {
-            props.showBtns ? <View style={{ marginBottom: 20, display: 'flex', flexDirection: 'row', width: '100%' }}>
-                <Text style={{ flex: 1, textAlign: 'center' }} onClick={cancel}>取消</Text>
-                <Text style={{ flex: 1, textAlign: 'center' }} onClick={confirm}>确认</Text>
+            props.showBtns ? <View style={{ marginBottom: 20,marginTop:20, display: 'flex', flexDirection: 'row', width: '100%' }}>
+                <Text style={{ flex: 1, textAlign: 'center',height:50 }} onClick={cancel}>取消</Text>
+                <Text style={{ flex: 1, textAlign: 'center',height:50 }} onClick={confirm}>确认</Text>
             </View> : null
         }
     </View>

+ 56 - 0
src/features/trackSomething/components/Metric.scss

@@ -0,0 +1,56 @@
+.metric_bg {
+    width: 320px;
+    display: flex;
+    flex-direction: column;
+    border-radius: 36px;
+    padding-left: 36px;
+    padding-right: 36px;
+    padding-top: 24px;
+    padding-bottom: 24px;
+    background-color: #1c1c1c;
+}
+
+.metric_title {
+    font-size: 36px;
+    line-height: 36px;
+    color: #ffffff;
+}
+
+.metric_value {
+    font-size: 48px;
+    line-height: 48px;
+    color: #ffffff;
+    margin-top: 20px;
+    margin-bottom: 12px;
+}
+
+.metric_unit {
+    font-size: 32px;
+    line-height: 32px;
+    color: #ffffff;
+    opacity: 0.8;
+}
+
+.metric_desc_bg {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+}
+
+.mteric_desc {
+    font-size: 24px;
+    line-height: 24px;
+    color: #ffffff;
+    opacity: 0.2;
+}
+
+.operate {
+    margin-top: 32px;
+    height: 84px;
+    border-radius: 42px;
+    background-color: #EEC01F;
+    color: #000;
+    line-height: 84px;
+    text-align: center;
+}

+ 109 - 0
src/features/trackSomething/components/Metric.tsx

@@ -0,0 +1,109 @@
+import { View, Text } from "@tarojs/components";
+import './Metric.scss'
+import { setAuth } from "../hooks/werun";
+import { useReady } from "@tarojs/taro";
+import { useSelector } from "react-redux";
+import { useEffect, useState } from "react";
+import Taro from "@tarojs/taro";
+import { uploadSteps } from "@/services/trackSomething";
+import { TimeFormatter } from "@/utils/time_format";
+
+export default function Component(props: any) {
+    const user = useSelector((state: any) => state.user);
+    const [allowRun, setAllowRun] = useState(false)
+    const [stepInfo, setStepInfo] = useState(null)
+    const [lastTime, setLastTime] = useState(new Date().getTime())
+    // const [title, setTitle] = useState('打卡')
+
+    //未登录<->已登录 状态切换时,执行一次授权检查
+    useEffect(() => {
+        checkAuth()
+    }, [user.isLogin])
+
+    //页面渲染完成后执行一次授权检查
+    useReady(() => {
+        // checkAuth()
+    })
+
+    function checkAuth() {
+        // Taro.checkSession
+        if (user.isLogin) {
+            // setAuth()
+            Taro.getSetting({
+                success: res => {
+                    //第一步,检测是否有授权 - 没有授权
+                    if (!res.authSetting['scope.werun']) {
+                        // setTitle('开启')
+                        setAllowRun(false)
+                    }
+                    else {
+                        setAllowRun(true)
+                        // setTitle('打卡')
+                        getWeRunData()
+                    }
+                }
+            })
+        }
+        else {
+            setAllowRun(false)
+            // setTitle('开启');
+        }
+    }
+
+    function checkout() {
+        console.log('开始获取步数的时间戳:'+new Date().getTime())
+        if (user.isLogin) {
+            setAuth(getWeRunData, refuseAuth)
+        }
+        else {
+            Taro.navigateTo({
+                url: '/pages/ChooseAuth'
+            })
+        }
+    }
+
+    function getWeRunData() {
+        // setTitle('打卡');
+        setAllowRun(true)
+        var time = new Date().getTime()
+        Taro.getWeRunData({
+            success: res => {
+                console.log('已获取步数的时间戳:'+new Date().getTime())
+                uploadSteps({
+                    encryptedData: res.encryptedData,
+                    iv: res.iv,
+                    cloudID: res.cloudID
+                }).then(res => {
+                    console.log(res)
+                    if ((res as any).length > 0) {
+                        setStepInfo((res as any)[(res as any).length - 1])
+                        setLastTime(time)
+                        console.log('接口返回的时间戳:'+new Date().getTime())
+                    }
+                });
+            }
+        })
+    }
+
+    function refuseAuth() {
+        // setTitle('开启');
+        setAllowRun(false)
+    }
+
+    return <View className="metric_bg">
+        <Text className="metric_title">行走</Text>
+        {
+            !allowRun && <Text className="metric_value">未开启</Text>
+        }
+        {
+            !allowRun && <Text className="mteric_desc">开启步数仅自己可见</Text>
+        }
+        {
+            allowRun && stepInfo && <Text className="metric_value">{(stepInfo as any).step}<Text className="metric_unit">步</Text></Text>
+        }
+        {
+            allowRun && stepInfo && <Text className="mteric_desc">{TimeFormatter.formatTimestamp(lastTime)}</Text>
+        }
+        <View className="operate" onClick={checkout}>{allowRun ? '打卡' : '开启'}</View>
+    </View>
+}

+ 0 - 0
src/features/trackSomething/components/placeholder


+ 76 - 0
src/features/trackSomething/hooks/werun.tsx

@@ -0,0 +1,76 @@
+import Taro from "@tarojs/taro";
+
+export const setAuth = (getRunData:Function,refuseAuth:Function) => {
+    Taro.getSetting({
+        success: res => {
+            //第一步,检测是否有授权 - 没有授权
+            if (!res.authSetting['scope.werun']) {
+                //第二步,开始授权,但这里有一个坑点(腾讯的bug),之前授权过但是是拒绝,所以会进入失败
+                Taro.authorize({
+                    scope: 'scope.werun',
+                    success: () => {
+                        debugger
+                    },
+                    fail: () => {
+                        Taro.showModal({
+                            title: '提示',
+                            content: '检测到您没有打开微信运动的权限,是否去设置打开?',
+                            success: res => {
+                                if (res.confirm) {
+                                    Taro.openSetting({
+                                        success: res => {
+                                            if (res.authSetting['scope.werun']) {
+                                                console.log("授权了")
+                                                getRunData()
+                                                //   this.getRunData();
+                                            } else {
+                                                refuseAuth()
+                                                Taro.showToast({
+                                                    title: '您已拒绝授权,无法获取步数',
+                                                    icon: 'none'
+                                                });
+                                            }
+                                        }
+                                    });
+                                } else {
+                                    refuseAuth()
+                                    Taro.showToast({
+                                        title: '您已拒绝授权,无法获取步数',
+                                        icon: 'none'
+                                    });
+                                }
+                            }
+                        })
+                        //第三步,引导用户,手动引导用户点击按钮,去设置页开启,## Modals是自定义组件
+                        // this.$invoke('Modals', '__modalConfirm__', [
+                        //     '检测到您没有打微信运动的权限,是否去设置?',
+                        //     'openSetting',
+                        //     //第四步,进入设置页的回调 - 成功
+                        //     res => {
+                        //         let { authSetting } = res.detail;
+                        //         if (authSetting['scope.werun']) {
+                        //             this.getRunData();
+                        //         } else {
+                        //             this.$invoke('Toast', '__warning__', [
+                        //                 `您没有同意授权微信运动,获取步数失败`
+                        //             ]);
+                        //         }
+                        //     },
+                        //     //第五步,点击取消按钮的回调
+                        //     () => {
+                        //         this.$invoke('Toast', '__warning__', [
+                        //             `您已拒绝微信运动授权,无法获取步数`
+                        //         ]);
+                        //     }
+                        // ]);
+                    }
+                });
+            } else {
+                //第六步,已经授权直接进入保存逻辑
+                console.log("授权了")
+                getRunData()
+                //   this.getRunData();
+            }
+        }
+    });
+}

+ 167 - 53
src/features/trackTimeDuration/components/Schedule.tsx

@@ -5,9 +5,10 @@ import Taro from "@tarojs/taro";
 import { TimeFormatter } from "@/utils/time_format";
 import { AtFloatLayout } from 'taro-ui';
 import "taro-ui/dist/style/components/float-layout.scss";
+import { delRecord } from "@/services/trackTimeDuration";
 
 
-export default function Component(props: { type?: string }) {
+export default function Component(props: { type?: string, data?: any, delSuccess?: Function }) {
     const [checkData, setCheckData] = useState(null)
 
     const [key, setKey] = useState('');
@@ -34,34 +35,34 @@ export default function Component(props: { type?: string }) {
         });
     }, []);
 
-    useEffect(() => {
-        return () => {
-            // 在组件卸载时清除定时器
-            if (timerId) {
-                clearInterval(timerId);
-            }
-        };
-    }, [timerId]);
-
-    const startTimer = () => {
-        // 避免重复启动定时器
-        if (timerId) {
-            return;
-        }
-
-        const id = setInterval(() => {
-            setCounter((prevCounter) => prevCounter + 1);
-        }, 1000);
-
-        setTimerId(id as any);
-    };
-
-    const stopTimer = () => {
-        if (timerId) {
-            clearInterval(timerId);
-            setTimerId(null);
-        }
-    };
+    // useEffect(() => {
+    //     return () => {
+    //         // 在组件卸载时清除定时器
+    //         if (timerId) {
+    //             clearInterval(timerId);
+    //         }
+    //     };
+    // }, [timerId]);
+
+    // const startTimer = () => {
+    //     // 避免重复启动定时器
+    //     if (timerId) {
+    //         return;
+    //     }
+
+    //     const id = setInterval(() => {
+    //         setCounter((prevCounter) => prevCounter + 1);
+    //     }, 1000);
+
+    //     setTimerId(id as any);
+    // };
+
+    // const stopTimer = () => {
+    //     if (timerId) {
+    //         clearInterval(timerId);
+    //         setTimerId(null);
+    //     }
+    // };
 
     function getStateDetail() {
         if (props.type == 'latest') {
@@ -94,16 +95,17 @@ export default function Component(props: { type?: string }) {
         })
     }
 
-    function showStage() {
-        startTimer();
+    function showStage(e) {
         setIsLatest(false);
         setIsOpen(true)
+        e.stopPropagation()
     }
 
-    function showLatest() {
-        startTimer();
+    function showLatest(e) {
+        // startTimer();
         setIsLatest(true)
         setIsOpen(true)
+        e.stopPropagation()
     }
 
     function getTime(t1: number, t2: number) {
@@ -143,6 +145,9 @@ export default function Component(props: { type?: string }) {
     function layoutContent() {
         //当前断食阶段
         var obj = isLatest ? (checkData as any).latest_record : (checkData as any).current_record
+        if (props.type == 'record') {
+            obj = props.data
+        }
         return <View style={{ flexDirection: 'column', display: 'flex', color: '#000' }}>
             {
                 obj.status == 'WAIT_FOR_START' ? <Text>断食阶段目标</Text> :
@@ -179,21 +184,21 @@ export default function Component(props: { type?: string }) {
     }
 
     //🚫❌⭕️✅
-    function statusString(isFast: boolean, isStart: boolean) {
-        if (props.type == 'latest') {
+    function statusString(isFast: boolean, isStart: boolean, data: any) {
+        if (props.type == 'latest' || props.type == 'record') {
             if (isFast) {
-                if ((checkData as any).latest_record.fast.status == 'COMPLETED') {
+                if (data.fast.status == 'COMPLETED') {
                     return '✅'
                 }
             }
             else {
-                if ((checkData as any).latest_record.sleep.status == 'COMPLETED') {
+                if (data.sleep.status == 'COMPLETED') {
                     return '✅'
                 }
-                else if ((checkData as any).latest_record.sleep.status == 'NOT_STARTED') {
+                else if (data.sleep.status == 'NOT_STARTED') {
                     return '🚫'
                 }
-                else if ((checkData as any).latest_record.sleep.status == 'NOT_COMPLETED') {
+                else if (data.sleep.status == 'NOT_COMPLETED') {
                     return isStart ? '✅' : '🚫'
                 }
             }
@@ -201,6 +206,14 @@ export default function Component(props: { type?: string }) {
         if (value == 'WAIT_FOR_START') {
             return '⭕️'
         }
+        else if (value == 'ONGOING') {
+            if (isFast && isStart) {
+                return '✅'
+            }
+            else if (!isFast && isStart) {
+                return '✅'
+            }
+        }
         else if (value == 'ONGOING1') {
             if (isFast && isStart) {
                 return '✅'
@@ -221,20 +234,67 @@ export default function Component(props: { type?: string }) {
         }
 
         return '⭕️'
+        // if (props.type == 'latest') {
+        //     if (isFast) {
+        //         if ((checkData as any).latest_record.fast.status == 'COMPLETED') {
+        //             return '✅'
+        //         }
+        //     }
+        //     else {
+        //         if ((checkData as any).latest_record.sleep.status == 'COMPLETED') {
+        //             return '✅'
+        //         }
+        //         else if ((checkData as any).latest_record.sleep.status == 'NOT_STARTED') {
+        //             return '🚫'
+        //         }
+        //         else if ((checkData as any).latest_record.sleep.status == 'NOT_COMPLETED') {
+        //             return isStart ? '✅' : '🚫'
+        //         }
+        //     }
+        // }
+        // if (value == 'WAIT_FOR_START') {
+        //     return '⭕️'
+        // }
+        // else if (value == 'ONGOING1'||value == 'ONGOING') {
+        //     if (isFast && isStart) {
+        //         return '✅'
+        //     }
+        //     else if (!isFast && isStart) {
+        //         return '✅'
+        //     }
+        // }
+        // else if (value == 'ONGOING2') {
+        //     if (isStart) {
+        //         return '✅'
+        //     }
+        // }
+        // else if (value == 'ONGOING3') {
+        //     if (isFast && !isStart) {
+        //         return '⭕️'
+        //     }
+        //     else {
+        //         return '✅'
+        //     }
+        // }
+
+        // return '⭕️'
     }
 
-    function scheduleItems() {
-        if (!checkData) {
+    function scheduleItems(data) {
+        if (!data) {
             return <View></View>
         }
-        var obj = props.type == 'latest' ? (checkData as any).latest_record : (checkData as any).current_record;
+        var obj = props.type == 'latest' ? (data as any).latest_record : (data as any).current_record;
+        if (props.type == 'record') {
+            obj = data//(data as any).latest_record
+        }
         return <View>
             {
                 obj && <View style={{ flexDirection: 'column', display: 'flex' }}>
-                    {obj.fast && <Text>{statusString(true, true)}开始断食:{formateTime(obj.fast, false)}</Text>}
-                    {obj.sleep && <Text>{statusString(false, true)}开始睡眠:{formateTime(obj.sleep, false)}</Text>}
-                    {obj.sleep && <Text>{statusString(false, false)}结束睡眠:{formateTime(obj.sleep, true)}</Text>}
-                    {obj.fast && <Text>{statusString(true, false)}结束断食:{formateTime(obj.fast, true)}</Text>}
+                    {obj.fast && <Text>{statusString(true, true, obj)}开始断食:{formateTime(obj.fast, false)}</Text>}
+                    {obj.sleep && <Text>{statusString(false, true, obj)}开始睡眠:{formateTime(obj.sleep, false)}</Text>}
+                    {obj.sleep && <Text>{statusString(false, false, obj)}结束睡眠:{formateTime(obj.sleep, true)}</Text>}
+                    {obj.fast && <Text>{statusString(true, false, obj)}结束断食:{formateTime(obj.fast, true)}</Text>}
                 </View>
             }
         </View>
@@ -261,40 +321,94 @@ export default function Component(props: { type?: string }) {
 
     }
 
-    return <View style={{ flexDirection: 'column', display: 'flex', alignItems: 'center' }}>
+    function more(e) {
+        Taro.showActionSheet({
+            itemList: ['删除', '分享']
+        })
+            .then(res => {
+                console.log(res.tapIndex)
+                switch (res.tapIndex) {
+                    case 0:
+                        {
+                            del()
+                        }
+                        break;
+                }
+            })
+            .catch(err => {
+                console.log(err.errMsg)
+            })
+        e.stopPropagation()
+    }
+
+    function del() {
+        Taro.showModal({
+            title: '删除记录',
+            content: '确定删除该记录吗?',
+            success: res => {
+                if (res.confirm) {
+                    delRecord(props.data.id
+                    ).then(res => {
+                        Taro.showToast({
+                            title: '删除成功'
+                        })
+                        props.delSuccess && props.delSuccess(props.data)
+                        // Taro.navigateBack()
+                    })
+                }
+            }
+        })
+    }
+
+    function all() {
+        if (props.type == 'latest') {
+            Taro.navigateTo({
+                url: '/pages/RecordsHistory'
+            })
+        }
+    }
+
+    return <View style={{ flexDirection: 'column', display: 'flex', alignItems: 'center', position: 'relative' }} onClick={all}>
         {
             props.type == 'latest' ? <Text style={{ color: 'red' }}>Latest</Text> :
                 <Text>{value == 'WAIT_FOR_START' ? 'Schedule' : 'Log in Progress'}</Text>
         }
         {
-            scheduleItems()
+            scheduleItems(props.type == 'record' ? props.data : checkData)
         }
 
         {
-            value == 'WAIT_FOR_START' && <Text onClick={editSchedule}>调整日程</Text>
+            (props.type != 'record' && value == 'WAIT_FOR_START') && <Text onClick={editSchedule}>调整日程</Text>
         }
         {
-            props.type == 'latest' && key == 'FAST_SLEEP' && <Text onClick={showLatest}>Durations by stage</Text>
+            ((props.type == 'record' && props.data.scenario == 'FAST_SLEEP') || (props.type == 'latest' && key == 'FAST_SLEEP')) && <Text onClick={showLatest}>Durations by stage</Text>
         }
         {
-            props.type != 'latest' && key == 'FAST_SLEEP' && (value == 'WAIT_FOR_START' ? <Text onClick={showStage}>Duration goals by stage</Text> : <Text onClick={showStage}>Current stage</Text>)
+            props.type != 'record' && props.type != 'latest' && key == 'FAST_SLEEP' && (value == 'WAIT_FOR_START' ? <Text onClick={showStage}>Duration goals by stage</Text> : <Text onClick={showStage}>Current stage</Text>)
         }
 
         {
             key == 'FAST_SLEEP' && <AtFloatLayout
                 isOpened={isOpen}
                 onClose={() => {
-                    stopTimer()
+                    // stopTimer()
                     setIsOpen(false)
                 }}
                 title="这是个标题">
                 {
-                    checkData && layoutContent()
+                    props.type != 'record'&&checkData && layoutContent()
+                }
+                {
+                    props.type == 'record' && props.data.scenario=='FAST_SLEEP' && layoutContent()
                 }
 
 
             </AtFloatLayout>
         }
+
+        {
+            props.type == 'record' && <Text style={{ position: 'absolute', right: 20, top: 20 }} onClick={more}>More</Text>
+        }
         <Text style={{ opacity: 0 }}>{counter}</Text>
 
     </View >

+ 3 - 1
src/features/trackTimeDuration/components/SetSchedule.tsx

@@ -18,7 +18,9 @@ import { TimeFormatter } from "@/utils/time_format";
 export default function Component() {
   const isFastFirst = false;
   const dispatch = useDispatch();
-  const scenario = useSelector((state: any) => state.scenario);
+  
+  const  [scenario] = useState(useSelector((state: any) => state.scenario))
+  // const scenario = useSelector((state: any) => state.scenario);
   const common = useSelector((state: any) => state.common);
 
   const [isOpen, setIsOpen] = useState(false)

+ 204 - 0
src/features/trackTimeDuration/components/SetSchedule_backup.tsx

@@ -0,0 +1,204 @@
+import Buttons from "@/components/Buttons";
+import { setPlan } from "@/services/trackTimeDuration";
+import { setScenario, setStep } from "@/store/scenario";
+import { View, Text } from "@tarojs/components";
+import "./SetSchedule.scss";
+import Taro, { useReady } from "@tarojs/taro";
+import TimePickers from "@/components/TimePickers";
+// import { AtList, AtListItem } from 'taro-ui'
+import { useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import Footer from "@/components/Footer";
+import PickerViews from "@/components/PickerViews";
+import { AtFloatLayout } from "taro-ui";
+import "taro-ui/dist/style/components/float-layout.scss";
+import { durationDatas, durationIndex, durationTime, pickerDurations } from "../hooks/Console";
+import { TimeFormatter } from "@/utils/time_format";
+
+export default function Component() {
+  const isFastFirst = false;
+  const dispatch = useDispatch();
+  
+  const scenario = useSelector((state: any) => state.scenario);
+  const common = useSelector((state: any) => state.common);
+
+  const [isOpen, setIsOpen] = useState(false)
+
+  // const [count,setCount] = useState(0)
+  const [beginChange, setBeginChange] = useState(true)
+  var scheduleObj: { start_time: any; end_time: any; };
+  if (scenario.name == 'FAST') {
+    scheduleObj = scenario.schedule.fast
+  }
+  else if (scenario.name == 'SLEEP') {
+    scheduleObj = scenario.schedule.sleep
+  }
+  else {
+    if (scenario.step == 'fast') {
+      scheduleObj = scenario.schedule.fast
+    }
+    else {
+      scheduleObj = scenario.schedule.sleep
+    }
+  }
+
+  const [startTime, setStartTime] = useState(scheduleObj.start_time)
+  const [endTime, setEndTime] = useState(scheduleObj.end_time)
+  const [pickerValue, setPickerValue] = useState(durationIndex(scheduleObj.start_time, scheduleObj.end_time, common))
+
+  const [hours, setHours] = useState(durationTime(scheduleObj.start_time, scheduleObj.end_time)[0])
+  const [minutes, setMinutes] = useState(durationTime(scheduleObj.start_time, scheduleObj.end_time)[1])
+
+  function start() {
+    if (scenario.name == 'FAST' || scenario.name == 'SLEEP') {
+      setPlan({
+        scenario: scenario.name,
+        schedule: scenario.name == 'FAST' ? {
+          fast: {
+            start_time: startTime,
+            end_time: endTime,
+          }
+        } : {
+          sleep: {
+            start_time: startTime,
+            end_time: endTime,
+          }
+        }
+      }).then(res => {
+        Taro.navigateBack({ delta: 3 })
+        // console.log('success')
+      })
+    }
+    else {
+      if ((scenario.step == 'fast' && isFastFirst) || (scenario.step == 'sleep' && !isFastFirst)) {
+        var obj = JSON.parse(JSON.stringify(scenario))
+        if (isFastFirst) {
+          obj.schedule.fast = {
+            start_time: startTime,
+            end_time: endTime,
+          }
+          dispatch(setStep('sleep'))
+          dispatch(setScenario(obj))
+        }
+        else {
+          obj.schedule.sleep = {
+            start_time: startTime,
+            end_time: endTime,
+          }
+          dispatch(setStep('fast'))
+          dispatch(setScenario(obj))
+        }
+
+        Taro.navigateTo({
+          url: '/pages/SetSchedule'
+        })
+      }
+      else {
+        commit()
+      }
+    }
+  }
+
+  function commit() {
+    setPlan({
+      scenario: scenario.name,
+      schedule: {
+        fast: {
+          start_time: isFastFirst ? scenario.schedule.fast.start_time : startTime,
+          end_time: isFastFirst ? scenario.schedule.fast.end_time : endTime,
+        }, sleep: {
+          start_time: !isFastFirst ? scenario.schedule.sleep.start_time : startTime,
+          end_time: !isFastFirst ? scenario.schedule.sleep.end_time : endTime,
+          // start_time: startTime,
+          // end_time: endTime,
+        }
+      }
+    }).then(res => {
+      dispatch(setStep('fast'))
+      Taro.navigateBack({ delta: 4 })
+    })
+  }
+
+  function onStartTimeChange(e: string) {
+    setBeginChange(true)
+    setStartTime(e)
+
+    setPickerValue(durationIndex(e, endTime, common))
+    setHours(durationTime(e, endTime)[0])
+    setMinutes(durationTime(e, endTime)[1])
+  }
+
+  function onEndTimeChange(e: string) {
+    setBeginChange(false)
+    setEndTime(e)
+
+    setPickerValue(durationIndex(startTime, e, common))
+    setHours(durationTime(startTime, e)[0])
+    setMinutes(durationTime(startTime, e)[1])
+  }
+
+  useReady(() => {
+    var title = scenario.step == 'fast' ? 'Fast schedule' : 'Sleep schedule'
+    Taro.setNavigationBarTitle({
+      title: title
+    })
+  })
+
+  function showPicker() {
+    setIsOpen(true)
+  }
+
+  function durationChange(e) {
+    var list = durationDatas(common)
+    setHours(list[0][e[0]])
+    setMinutes(list[1][e[1]])
+    setPickerValue(e)
+    setIsOpen(false)
+    if (beginChange) {
+      setEndTime(TimeFormatter.calculateTimeByTimeRange(list[0][e[0]] * 60 + list[1][e[1]], startTime, true));
+    }
+    else {
+      setStartTime(TimeFormatter.calculateTimeByTimeRange(list[0][e[0]] * 60 + list[1][e[1]], endTime, false));
+    }
+  }
+
+  function layoutContent() {
+    return <View style={{ color: '#000' }}>
+      <PickerViews onChange={durationChange} items={durationDatas(common)} value={pickerValue} height={200} showBtns={true} onCancel={() => { setIsOpen(false) }} />
+    </View>
+  }
+
+  return <View >
+    {/* <Text>场景名称{scenario.name}</Text> */}
+
+    <View className="box">
+      <View className="header">
+        <View className="item">
+          <TimePickers time={startTime} content={scenario.step == 'fast' ? 'Fast starts\n' + startTime : 'Sleep starts\n' + startTime} change={onStartTimeChange} />
+        </View>
+        <View className="item">
+          <TimePickers time={endTime} content={scenario.step == 'fast' ? 'Fast ends\n' + endTime : 'Sleep ends\n' + endTime} change={onEndTimeChange} />
+        </View>
+
+
+      </View>
+      <View style={{ flex: 1 }} />
+      <Text className="duration" onClick={() => { setIsOpen(true) }}>{hours > 0 ? hours + ' hours ' : ''}{minutes > 0 ? minutes + ' minutes' : ''}</Text>
+    </View>
+
+    <Footer child={
+      <Buttons title={scenario.step == 'fast' ? 'Set fast schedule' : 'Set sleep schedule'} style={{ backgroundColor: scenario.step == 'fast' ? '#AAFF00' : '#00ffff', width: 320 }} onClick={() => start()}></Buttons>
+    } />
+
+    <AtFloatLayout
+      isOpened={isOpen}
+      onClose={() => {
+        setIsOpen(false)
+      }}>
+      {
+        layoutContent()
+      }
+    </AtFloatLayout>
+
+  </View>;
+}

+ 5 - 0
src/pages/Activity.scss

@@ -0,0 +1,5 @@
+.activity{
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+}

+ 3 - 1
src/pages/Activity.tsx

@@ -1,9 +1,11 @@
 import { View,Text } from "@tarojs/components";
+import Metric from '@/features/trackSomething/components/Metric'
 
 export default function Page(){
     return (
-        <View className="container">
+        <View className="container activity">
             <Text>Activity Page</Text>
+            <Metric />
         </View>
     )
 }

+ 1 - 0
src/pages/ChooseAuth.tsx

@@ -35,6 +35,7 @@ export default function Page() {
         if (process.env.TARO_ENV === 'weapp'){
             Taro.login().then(res => {
                 code = res.code;
+                debugger
     
             })
         }

+ 23 - 3
src/pages/Metric.tsx

@@ -1,9 +1,29 @@
-import { View,Text } from "@tarojs/components";
+import { View, Text, CoverView, Button } from "@tarojs/components";
+import { useState } from "react";
 
-export default function Page(){
+export default function Page() {
+    const [isModalOpen, setIsModalOpen] = useState(false);
+
+    const openModal = () => {
+        setIsModalOpen(true);
+    };
+
+    const closeModal = () => {
+        setIsModalOpen(false);
+    };
     return (
         <View className="container">
-            <Text>Metric Page</Text>
+            <Text onClick={openModal}>Metric Page</Text>
+
+            {
+                isModalOpen && <CoverView style={{position:'absolute',left:0,top:0,bottom:-100,right:0,backgroundColor:'red'}}>
+                    <View>
+                        <Text>这是一个弹窗</Text>
+                        <Button onClick={closeModal}>关闭弹窗</Button>
+                    </View>
+                </CoverView>
+            }
+
         </View>
     )
 }

+ 83 - 5
src/pages/RecordsHistory.tsx

@@ -1,5 +1,8 @@
-import { View } from "@tarojs/components";
-import { useReady } from "@tarojs/taro";
+import { getClockRecords } from "@/services/trackTimeDuration";
+import { View, Text, ScrollView } from "@tarojs/components";
+import { usePullDownRefresh, useReachBottom, useReady } from "@tarojs/taro";
+import { useEffect, useState } from "react";
+import Schedule from '@/features/trackTimeDuration/components/Schedule'
 
 export default function Page() {
     // const router = useRouter();
@@ -7,15 +10,90 @@ export default function Page() {
     // useEffect(() => {
     //   console.log(router.params);
     // }, [router.params]);
+    const pageSize = 10
+    const [pageIndex, setPageIndex] = useState(1)
+    const [records, setRecords] = useState<any[]>([])
+    const [counter, setCounter] = useState(0)
+    const [timerId, setTimerId] = useState(null)
 
-    useReady(()=>{
+    useEffect(() => {
+        startTimer();
+        return () => {
+            // 在组件卸载时清除定时器
+            if (timerId) {
+                clearInterval(timerId);
+            }
+        };
+    }, [timerId]);
+
+    const startTimer = () => {
+        // 避免重复启动定时器
+        if (timerId) {
+            return;
+        }
+
+        const id = setInterval(() => {
+            setCounter((prevCounter) => prevCounter + 1);
+        }, 1000);
+
+        setTimerId(id as any);
+    };
+
+
+    useReady(() => {
         getHistory()
     })
 
-    function getHistory(){
-        
+    usePullDownRefresh(() => {
+        refresh()
+    })
+
+    // useReachBottom(() => {
+    //     setPageIndex(pageIndex+1)
+    //     getHistory()
+    // })
+
+    function refresh() {
+        setPageIndex(1)
+        getHistory()
+    }
+
+    function more() {
+        setPageIndex(pageIndex + 1)
+        getHistory()
+    }
+
+    function getHistory() {
+        getClockRecords({
+            page: pageIndex,
+            page_size: pageSize,
+            only_finished: false
+        }).then(res => {
+            if (pageIndex == 1) {
+                setRecords((res as any).data)
+            } else {
+                setRecords(records.concat((res as any).data))
+            }
+        })
+    }
+
+    function removeItem(item) {
+        setRecords(records.filter(i => i.id != item.id))
     }
 
     return <View className="container">
+        <ScrollView scrollY style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}
+            onScrollToLower={more}
+        // onRefresh={refresh}
+        >
+            {
+                records.map((item, index) => {
+                    // return <View style={{height:120}}>
+                    //     <Text>{index}</Text>
+                    // </View>
+                    return <Schedule key={index} data={item} type="record" delSuccess={(item) => removeItem(item)} />
+                })
+            }
+        </ScrollView>
     </View>;
 }

+ 8 - 0
src/pages/clock.tsx

@@ -56,6 +56,14 @@ export default function IndexPage() {
         getCheckData()
       }
     })
+
+    const handlePageShow = () => {
+      console.log('页面展示,相当于onResume');
+      // 进行相应的处理
+      // ...
+    };
+
+    Taro.eventCenter.on('onShow', handlePageShow);
   }, [])
 
   useEffect(() => {

+ 3 - 1
src/services/http/api.js

@@ -7,7 +7,7 @@ export let imgUrl = online
 //common
 export const API_ADJUST_TIMES = `${baseUrl}/api/fast/adjust-times`
 // export const API_WX_PUB_FOLLOWED = `${baseUrl}/api/fast/user/wx-pub-followed`
-export const API_OAUTH_LOGIN = `${baseUrl}/api/user/oauth2/login`
+export const API_OAUTH_LOGIN = `${baseUrl}/api/user/login/oauth2`
 export const API_LOGIN = `${baseUrl}/api/user/login/password`
 export const API_REGISTER = `${baseUrl}/api/user/account`
 export const API_LOGOUT = `${baseUrl}/api/user/logout`
@@ -30,6 +30,8 @@ export const API_FAST_SCHEDULES = `${baseUrl}/api/fast/schedules`
 export const API_FAST_CALENDARS = `${baseUrl}/api/fast/calendars/`
 
 //track something
+export const API_CLOCK_RECORDS = `${baseUrl}/api/clock/records`
+export const API_UPLOAD_STEPS = `${baseUrl}/api/thirdparty/wx/we-run-data`
 
 //journal
 

+ 4 - 1
src/services/http/request.ts

@@ -66,10 +66,13 @@ export async function request<T>(param: RequestParam): Promise<T> {
                     resolve(resp);
                 }
                 else if (statusCode == 401) {
-                    debugger
                     global.dispatch(logoutSuccess());
                 }
                 else {
+                    Taro.showToast({
+                        icon: 'none',
+                        title:data.error_message
+                    })
                     reject(data);
                 }
                 // if (statusCode == 204){

+ 13 - 0
src/services/trackSomething.tsx

@@ -0,0 +1,13 @@
+import {  API_UPLOAD_STEPS } from './http/api';
+import { request } from './http/request';
+
+export const uploadSteps = (params) => {
+    return new Promise((resolve) => {
+        request({
+            url: API_UPLOAD_STEPS, method: 'POST', data: {...params}
+        }).then(res => {
+            resolve(res);
+            // dispatch(loginSuccess(res));
+        })
+    })
+}

+ 4 - 4
src/services/trackTimeDuration.tsx

@@ -1,5 +1,5 @@
 
-import { API_FAST_PLANS, API_FAST_CHECKS,API_FAST_CLOCKS } from './http/api'
+import { API_FAST_PLANS, API_FAST_CHECKS,API_FAST_CLOCKS, API_CLOCK_RECORDS } from './http/api'
 import { request } from './http/request';
 
 export const getPlans = () => {
@@ -46,10 +46,10 @@ export const getClocks = () => {
     })
 }
 
-export const getHistory = ()=>{
+export const getClockRecords = (params:any)=>{
     return new Promise((resolve) => {
         request({
-            url: API_FAST_CLOCKS, method: 'GET', data: {}
+            url: API_CLOCK_RECORDS, method: 'GET', data: {...params}
         }).then(res => {
             resolve(res);
         })
@@ -70,7 +70,7 @@ export const recordCheck = (params: Record<string, any> | undefined) => {
 export const delRecord = (id: string) => {
     return new Promise((resolve) => {
         request({
-            url: API_FAST_CLOCKS + '/' + id, method: 'DELETE', data: {}
+            url: API_CLOCK_RECORDS + '/' + id, method: 'DELETE', data: {}
         }).then(res => {
             resolve(res);
         })

+ 90 - 0
src/store/metric.tsx

@@ -0,0 +1,90 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+
+interface MetricsState {
+    metrics: [
+        {
+            index: number;                      //序号
+            id: string;                         //id
+            name: string;                       //名称
+            description: string | '';           //描述
+            require_permission: boolean | false;//是否需要授权
+            record_format: [
+                {
+                    name: string;
+                    default_value: number;
+                    //取值类型(整数、小数)
+                    //取值精度(小数点后几位)
+                    step: number;           //步长
+                    min: number;            //最小值
+                    max: number;            //最大值  
+                    default_unit: string;           //单位
+                }
+            ] | []
+            all_records: [
+                {
+                    record: [
+                        {
+                            name: string;
+                            value: number;
+                            unit: string;
+                        }
+                    ],
+                    timestamp: number;
+                    // note: string | ''
+                }
+
+            ] | [];
+        },
+    ];
+}
+
+const initialState: MetricsState = {
+    metrics:[
+        {
+            index: 0,
+            id: '',
+            name: '',
+            description: '',
+            require_permission: false,
+            record_format: [
+                {
+                    name: '',
+                    default_value: 0,
+                    step: 0,
+                    min: 0,
+                    max: 0,
+                    default_unit: '',
+                }
+            ],
+            all_records: [
+                {
+                    record: [
+                        {
+                            name: '',
+                            value: 0,
+                            unit: '',
+                        }
+                    ],
+                    timestamp: 0,
+                }
+            ]
+        }
+    ]
+};
+
+
+const metricsSlice = createSlice({
+    name: 'metrics',
+    initialState,
+    reducers: {
+        getMetricsSuccess(state, action) { }
+
+    }
+})
+
+
+
+
+export const { getMetricsSuccess } = metricsSlice.actions;
+export default metricsSlice.reducer;

+ 6 - 1
src/store/permission.tsx

@@ -8,6 +8,7 @@ interface PermissionState {
     albumAllow?: boolean | false;   //rn
     cameraAllow?: boolean | false;  //rn
     photoAllow?: boolean | false;   //weapp
+    addToMini?: boolean | false;    //weapp     是否已经添加到我的小程序
 }
 
 const initialState: PermissionState = {
@@ -17,6 +18,7 @@ const initialState: PermissionState = {
     albumAllow: false,
     cameraAllow: false,
     photoAllow: false,
+    addToMini: false,
 }
 
 const permissionSlice = createSlice({
@@ -42,8 +44,11 @@ const permissionSlice = createSlice({
         setPhotoAllow(state, action) {
             state.photoAllow = action.payload;
         },
+        setAddToMini(state, action) {
+            state.addToMini = action.payload;
+        }
     }
 });
 
 export default permissionSlice.reducer;
-export const { setWXPubFollow, setPushAllow, setStepAllow, setCameraAllow, setAlbumAllow, setPhotoAllow } = permissionSlice.actions;
+export const { setWXPubFollow, setPushAllow, setStepAllow, setCameraAllow, setAlbumAllow, setPhotoAllow,setAddToMini} = permissionSlice.actions;

+ 1 - 1
src/utils/time_format.ts

@@ -44,7 +44,7 @@ export class TimeFormatter {
   }
 
   static formatTime(date: Date): string {
-    return `${TimeFormatter.formatNumber(date.getHours())}:${TimeFormatter.formatNumber(date.getMinutes())}`;
+    return `${TimeFormatter.formatNumber(date.getHours())}:${TimeFormatter.formatNumber(date.getMinutes())}:${TimeFormatter.formatNumber(date.getSeconds())}`;
   }
 
   static formatNumber(num: number): string {