
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 }) {
const [timeLeft, setTimeLeft] = useState(pomo)
const [isRun, setIsRun] = useState(false)
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}`
}
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(() => {
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({ 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>
)
}
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) => {
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>
)
}
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 = () => {
setShowNote(!showNote)
}
const handleUpdate = () => {
setIsEdit(true)
setEditValue(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>
)
}