Skip to main content

Command Palette

Search for a command to run...

总结写番茄任务网站代码

Published
4 min read

// TimerPanel.jsx
import { VscDebugStart } from "react-icons/vsc";
import { RiResetLeftLine } from "react-icons/ri";
import { useEffect, useRef, useState } from "react";
import { supabase } from "../lib/supabase";

const pomo = 25 * 60
export default function TimerPanel({ activeTask, refresh }) {

    // let time = 25 * 60
    const [timeLeft, setTimeLeft] = useState(pomo)
    const [isRun, setIsRun] = useState(false)
    // useRef 更改值页面不会刷新,timer.current获取值
    let timer = useRef(null)

    const convert = function (time) {
        const min = Math.floor(time / 60)
        const sec = time % 60
        console.log(`${min}: ${sec}`);

        return `${addZero(min)}:${addZero(sec)}`
    }
    const addZero = function (num) {

        return String(num).length > 1 ? num : `0${num}`
    }

    // 副作用,组件state变化,要做些事情,可以写多个
    useEffect(() => {
        if (!activeTask) return
        setTimeLeft(() => pomo)
        setIsRun(() => true)
    }, [activeTask])

    useEffect(() => {
        if (isRun === false) return
        if (timeLeft <= 0) {
            hanldeComplete()
            clearInterval(timer.current)
            return
        }
        clearInterval(timer.current)
        timer.current = setInterval(() => {
            // timeLeft = timeLeft - 1
            setTimeLeft(prev => prev - 1)
        }, 1000)
        return () => {
            clearInterval(timer.current)
        }
    }, [timeLeft, isRun])

    const handleStart = function () {
        setIsRun((prev) => 'running')
    }

    const hanldeComplete = async function () {
        const { err } = await supabase
            .from('tasks')
            // update方法传对象
            .update({ tomato_count: (activeTask.tomato_count || 0) + 1 })
            .eq('id', activeTask.id)

        const { data } = await supabase.auth.getUser()
        const userid = data.user.id

        await supabase.from("focus_sessions")
            .insert({
                user_id: userid,
                task_id: activeTask.id,
                started_at: new Date((+new Date() - 25 * 60 * 1000)).toISOString(),
                ended_at: new Date().toISOString()
            })


        refresh(prev => prev + 1)
    }

    const handleReset = function () {
        setTimeLeft(() => pomo)
        setIsRun(() => 'stop')
    }

    return (
        <div>
            {/* <div className="flex justify-center items-center h-[200px] text-6xl">25:00</div> */}
            <div className="flex justify-center items-center h-[200px] text-6xl">{convert(timeLeft)}</div>
            <div className="flex justify-center items-center">
                <button onClick={handleStart} className="p-3"><VscDebugStart size={50} /></button>
                <button onClick={handleReset} className="p-3"><RiResetLeftLine size={50} /></button>
            </div>
        </div>
    )
}
// TaskList.jsx
// import TaskCard from "./TaskCard"
import TaskCard from "./TaskCard"
import CreateTaskForm from "./CreateTaskForm"
import { useEffect, useState } from "react"
import { supabase } from "../lib/supabase"

export default function ({ activeGoal }) {
    const [taskList, setTaskList] = useState([])
    useEffect(() => {
        if (activeGoal) {
            fetchTasks()
        }


    }, [activeGoal])

    const fetchTasks = async () => {
        const { data, err } = await supabase
            .from('tasks')
            .select("*")
            .eq('goal_id', activeGoal.id)
            .order('created_at', { ascending: false })
        setTaskList(data)
    }

    const addTask = async (taskName, num) => {
        const { data: { user } } = await supabase.auth.getUser()
        const { error } = await supabase
            .from('tasks')
            .insert({
                user_id: user.id,
                goal_id: activeGoal.id,
                title: taskName,
                potato_count: num,
            })
        if (error) console.log(error);

        fetchTasks()
    }
    const update = async (task, updateValue) => {
        // 更新要编辑的task,map返回了新对象,注意要setTaskList触发页面更新
        setTaskList(taskList.map((t) => t.id === task.id ? { ...task, ...updateValue } : t))

        const { error } = await supabase
            .from('tasks')
            .update({ 'note': updateValue.note })
            .eq('id', task.id)
        if (error) {
            setTaskList(taskList.map(t => t.id === task.id ? task : t))
        }
    }

    return (
        <div className="p-10">
            <CreateTaskForm addTask={addTask} ></CreateTaskForm>
            {taskList.map(item => <TaskCard update={update} task={item} key={item.id}></TaskCard>)}
            {/* <TaskCard></TaskCard> */}
            {/* <TaskCard></TaskCard> */}
        </div>
    )
}
// TaskCard.jsx
import { LuNotebookText } from "react-icons/lu";
import { FaPlay } from "react-icons/fa";
import { FaCheckCircle } from "react-icons/fa";
import { Circle, CircleCheck } from 'lucide-react'
import { useState } from "react";
import { supabase } from '../lib/supabase'



export default function ({ task, update }) {

    const [isEdit, setIsEdit] = useState(false)
    const [showNote, setShowNote] = useState(false)
    const [editValue, setEditValue] = useState('')

    const handleEdit = () => {
        // if (task.note) return
        setShowNote(!showNote)
    }

    const handleUpdate = () => {
        setIsEdit(true)
        setEditValue(task.note)
    }
    // console.log('task.note', task.note);

    const saveValue = () => {
        const updateValue = {}
        updateValue.note = editValue
        // 子组件往父组件传参用回调方法
        update(task, updateValue)
        setIsEdit(false)
    }

    const cancel = (e) => {
        if (e.key === 'Escape') {
            setIsEdit(false)
        }
    }


    return (
        <div className="flex shadow-md mt-5 p-5">
            <div className="w-10">
                {/* <input type="checkbox" checked={task.is_completed} /> */}
                <button className="text-grey-500 hover:text-green-500 transition">
                    {task.is_completed ? <CircleCheck className="text-green-500" /> : <Circle></Circle>}
                </button>
            </div>
            <div className="flex-1">
                <div className={`h-10  ${task.is_completed ? 'line-through text-grey-400' : 'text-grey-800'}`}>{task.title}</div>
                <div className="flex">
                    <div className="w-20 border-r">
                        <div className="text-grey-400">PLAN</div>
                        {/* <div>{Array(task.potato_count).map(item => <span>🥔</span>)}</div> */}
                        <div>{Array(task.potato_count).fill(0).map((_, i) => <span key={i}>🥔</span>)}</div>
                    </div>

                    <div className="pl-5">
                        <div className="text-red-400">ACTUAL</div>
                        <div>{Array(task.tomato_count).fill(0).map((_, i) => <span key={i}>🍅</span>)}</div>
                    </div>
                </div>
                {(showNote || task.note) && (
                    isEdit ?
                        <textarea type="text" value={editValue}
                            onChange={(e) => setEditValue(e.target.value)}
                            onBlur={saveValue}
                            onKeyUp={cancel}
                            autoFocus
                            placeholder="add notes here..."
                            className="w-full p-3 text-sm border rounded-lg focus:ring-2 focus:ring-red-100 outline-none min-h-[80px] bg-yellow-50/50"></textarea>
                        :
                        // white-space: pre-wrap; 是一个 CSS 属性值,它告诉浏览器 保留文本中的空格和换行符,并且在内容超出容器宽度时,允许文本自动换行。
                        <div onClick={handleUpdate} className="w-full text-sm p-3
                        rounded-lg cursor-pointer hover:bg-gray-100 border-transparent transition whitespace-pre-wrap bg-gray-50/50">{task.note || 'click to add note ...'}</div>
                )}
            </div>
            <div className="w-20 flex space-x-4">
                {/* className="`{}`" 这种写法是不对的,会被当作普通字符串 */}
                <LuNotebookText size={30} className={`${(showNote || task.note) ? 'text-red-400' : ''}`} onClick={handleEdit} />
                {/* <LuNotebookText size={30} className="text-red-400" onClick={handleEdit} /> */}
                <FaPlay size={30} className="text-red-600" />
            </div>
        </div>
    )
}

More from this blog

读书《掌握api架构》

API应用程序接口 远程调用,调用服务器方法,拿到结果。一些方法本地电脑跑不起来,调用服务器方法,像是调用本地方法一样,拿到结果。 内部错误信息不要告知客户端,容易被黑客找到漏洞攻击 API网关,后端服务有很多微服务,没有网关需要记住每个微服务的地址,自己查询。有网关,有统一访问后台地址,网关帮你路由到正确地址,查询返回结果。网关还可以身份校验,流量控制。API网关反向代理请求,隐藏内部服务地址。 研究新东西,新东西有新机会 api网关处理外部流量 service mesh处理内部微服务调用。流...

Jan 12, 20261 min read

读书《服务端开发技术、方法与实用解决方案》

分布式系统中,消息中间件,事务中间件 中间件=平台+通信 基础监控工具,Zabbix 服务监控工具,SkyWalking 业务监控工具 业务指的是商业的活动流程 上班的过程中提高自己,提高自己的挣钱能力,勇于承担责任,提高能力,不要混日子 细节拆分+规划 衡量目标,反思复盘 实践中学习,动手练习 通过分层来解决问题,不合理的分层会增加复杂度 尽量避免重写父类方法,子类不能有比父类更严格的限制条件,子类不能改变父类的返回 mongodb数据库适合存储物流信息、游戏信息,社交信息 数据库表设计原则...

Dec 19, 20251 min read

进程线程协程

CPython Internals sample code 读书CPython Internals 最终编译成CPU指令 进程执行顺序取决于操作系统调度,不保证先进先出 进程不用考虑执行顺序,如果需要考虑,可能需要用线程 密集计算用进程 进程不适合处理IO,因为有锁,只有一个进程工作 python的多线程不支持并行,有GIL 有例外,丢弃GIL锁 发起http请求 与本地硬件交互 加密数据 阅读和写文件 协程 asyncio 大量并发,I/O 定时任务 在 CPython 里,线程和 async...

Jun 4, 20254 min read

somelearningnote

18 posts

some note, not worth reading.