-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add a new project tiny to track project progress
- Loading branch information
1 parent
cf36682
commit 8de55fd
Showing
5 changed files
with
334 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// pages/play-lab.tsx 或 pages/play-lab/index.tsx | ||
|
||
import React from 'react' | ||
import Link from 'next/link' | ||
|
||
const PlayLab = () => { | ||
return ( | ||
<div> | ||
<h1>Welcome to PlayLab!</h1> | ||
<Link href="/projects/tiny"> | ||
Go to Project Tracker | ||
</Link> | ||
</div> | ||
) | ||
} | ||
|
||
export default PlayLab |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import React, { useState } from "react"; | ||
import { Project } from "./types"; | ||
|
||
interface Props { | ||
// Define the props for your component here | ||
project: Project; | ||
updateProject: (project: Project) => void; | ||
} | ||
|
||
const IterationDetail: React.FC<Props> = ({ | ||
project: currentProject, | ||
updateProject, | ||
}) => { | ||
const [timerId, setTimerId] = useState(null); | ||
const [elapsedTime, setElapsedTime] = useState(0); | ||
const [showPanel, setShowPanel] = useState(false); | ||
|
||
const [startTime, setStartTime] = useState(''); | ||
const [isPaused, setIsPaused] = useState(false); | ||
const [rating, setRating] = useState(1); | ||
const [notes, setNotes] = useState(""); | ||
|
||
const startTimer = () => { | ||
if (timerId !== null) return; | ||
const id = setInterval(() => { | ||
setElapsedTime((time) => time + 1); | ||
}, 1000); | ||
setTimerId(id); | ||
}; | ||
|
||
const resumeTimer = () => { | ||
const id = setInterval(() => { | ||
setElapsedTime((prevTime) => prevTime + 1); | ||
}, 1000); | ||
setTimerId(id); | ||
}; | ||
|
||
const pauseTimer = () => { | ||
clearInterval(timerId); | ||
setTimerId(null); | ||
}; | ||
|
||
const submitIteration = () => { | ||
if (!window.confirm(`你确定要完成这次${currentProject.name}吗?`)) { | ||
return; | ||
} | ||
const newIteration = { | ||
startTime: startTime, | ||
elapsedTime: elapsedTime, | ||
notes: notes, | ||
quality: rating, | ||
tags: [], | ||
}; | ||
|
||
currentProject.iterations = currentProject.iterations ? [...currentProject.iterations, newIteration] : [newIteration]; | ||
updateProject(Object.assign({}, currentProject)); | ||
}; | ||
|
||
return ( | ||
<div> | ||
{currentProject ? ( | ||
<> | ||
<h3 className="text-2xl font-bold text-blue-600"> | ||
{currentProject && currentProject.name} | ||
</h3>{" "} | ||
<div className="summary"> | ||
{currentProject.iterations && ( | ||
<p className="text-zinc-500"> | ||
<span className="pr-5"> | ||
累积时间: | ||
{Math.floor( | ||
currentProject.iterations.reduce((prev, iteration) => { | ||
return Number(iteration.elapsedTime) + prev; | ||
}, 0) / 3600 | ||
)} | ||
小时 | ||
</span> | ||
<span> | ||
累积次数: | ||
{currentProject.iterations.length} | ||
</span> | ||
</p> | ||
)} | ||
</div> | ||
<button | ||
className="bg-blue-500 text-white px-4 text-sm py-2 rounded" | ||
onClick={() => setShowPanel(!showPanel)} | ||
> | ||
开始{currentProject.name} | ||
</button> | ||
{showPanel && ( | ||
<div className="bg-gray-100 p-4 shadow-lg rounded mt-5"> | ||
<div className="text-zinc-500"> | ||
<button | ||
className="bg-blue-500 text-white px-4 py-2 rounded" | ||
onClick={() => { | ||
if (startTime != null) return; | ||
setStartTime(new Date().toISOString()); | ||
startTimer(); | ||
}} | ||
> | ||
开始计时 | ||
</button> | ||
{startTime && ( | ||
<input | ||
type="text" | ||
readOnly | ||
value={startTime.toLocaleString()} | ||
className="mt-2 p-1 border rounded" | ||
/> | ||
)} | ||
<button | ||
className="ml-5 bg-yellow-500 text-white px-4 py-2 rounded" | ||
onClick={() => { | ||
if (!startTime) return; | ||
setIsPaused(!isPaused); | ||
if (isPaused) { | ||
resumeTimer(); | ||
} else { | ||
pauseTimer(); | ||
} | ||
}} | ||
> | ||
{isPaused ? "恢复" : "暂停"} | ||
</button> | ||
<p className="mt-5 mb-5"> | ||
评分: | ||
<input | ||
type="number" | ||
min="1" | ||
max="5" | ||
value={rating} | ||
onChange={(e) => setRating(Number(e.target.value))} | ||
/> | ||
</p> | ||
<p className="mt-5 mb-5"> | ||
笔记: | ||
<textarea | ||
value={notes} | ||
onChange={(e) => setNotes(e.target.value)} | ||
className="mt-2 p-1 border rounded" | ||
/> | ||
</p> | ||
<button | ||
className="mt-5 bg-green-500 text-white px-4 py-2 rounded" | ||
onClick={submitIteration} | ||
> | ||
完成一次{currentProject.name} | ||
</button> | ||
<p> | ||
本次累积时间:{Math.floor(elapsedTime / 3600)}小时 | ||
{Math.floor(elapsedTime / 60) % 60}分钟{elapsedTime % 60}秒 | ||
</p> | ||
</div> | ||
</div> | ||
)} | ||
{currentProject.iterations && | ||
currentProject.iterations.map((iteration, index) => { | ||
return ( | ||
<div | ||
key={index} | ||
className="bg-white p-4 shadow rounded mt-5 border-gray-400" | ||
> | ||
<p className="text-lg font-bold text-blue-600"> | ||
第{index + 1}次{currentProject.name} | ||
</p> | ||
<p className="text-zinc-500"> | ||
开始时间:{" "} | ||
{iteration.startTime | ||
? `${new Date(iteration.startTime).toLocaleDateString()} ${new Date(iteration.startTime).toLocaleTimeString()}` | ||
: "未知"} | ||
</p> | ||
<p className="text-zinc-500"> | ||
持续时间: {Math.floor(iteration.elapsedTime / 3600)} 小时{" "} | ||
{Math.floor((iteration.elapsedTime % 3600) / 60)} 分钟 | ||
</p> | ||
<p className="text-zinc-500">评价: {iteration.quality}星</p> | ||
<p className="text-zinc-500">小记: {iteration.notes}</p> | ||
</div> | ||
); | ||
})} | ||
</> | ||
) : null} | ||
</div> | ||
); | ||
}; | ||
|
||
export default IterationDetail; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import React, { useState, useEffect } from "react"; | ||
import { Project } from "./types"; | ||
import IterationDetail from "./IterationDetail"; | ||
|
||
const IterationTracker: React.FC = () => { | ||
const [projects, setProjects] = useState<Project[]>([]); | ||
const [projectName, setProjectName] = useState(""); | ||
const [currentProjectId, setCurrentProjectId] = useState<string | null>(null); | ||
|
||
const updateProject = (updatedProject: Project) => { | ||
setProjects((prevProjects) => { | ||
return prevProjects.map((project) => { | ||
if (project.id === updatedProject.id) { | ||
return { | ||
...project, | ||
...updatedProject, | ||
}; | ||
} | ||
return project; | ||
}); | ||
}); | ||
localStorage.setItem("projects", JSON.stringify(projects)); | ||
}; | ||
|
||
const generateId = () => { | ||
return Math.random().toString(36).substr(2, 9) + Date.now().toString(36); | ||
}; | ||
|
||
const currentProject = projects.find( | ||
(project) => project.id === currentProjectId | ||
); | ||
|
||
const handleAddProject = () => { | ||
if (projectName === "") return; | ||
setProjects([...projects, { name: projectName, id: generateId() }]); | ||
setProjectName(""); | ||
}; | ||
|
||
useEffect(() => { | ||
const savedProjects = localStorage.getItem('projects'); | ||
if (savedProjects) { | ||
setProjects(JSON.parse(savedProjects)); | ||
} | ||
}, []); | ||
|
||
return ( | ||
<div className="bg-white min-h-screen max-w-[1000px] mx-auto"> | ||
<h1 className="text-center text-4xl font-bold text-blue-500 pt-8"> | ||
毫末 | ||
</h1> | ||
<div className="flex justify-between mt-10"> | ||
<div className="project-list flex-1 ml-10 min-w-[300px]"> | ||
<input | ||
className="border border-gray-300 px-3 py-2 rounded " | ||
value={projectName} | ||
onChange={(e) => setProjectName(e.target.value)} | ||
placeholder="项目名称" | ||
/> | ||
<button | ||
className="ml-5 bg-blue-500 text-white px-4 py-2 rounded" | ||
onClick={handleAddProject} | ||
> | ||
添加项目 | ||
</button> | ||
<ul className="list-none mt-10 max-w-xs"> | ||
{projects.map((project, index) => ( | ||
<li | ||
key={index} | ||
className="p-2 border-b border-gray-200 bg-blue-200 shadow my-2 rounded text-gray-900" | ||
onClick={() => { | ||
setCurrentProjectId(project.id); | ||
}} | ||
> | ||
{project.name} | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
|
||
<div className="project-detail flex-1 ml-20 min-w-[300px]"> | ||
<IterationDetail | ||
project={currentProject} | ||
updateProject={updateProject} | ||
></IterationDetail> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default IterationTracker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export interface Iteration { | ||
elapsedTime: number; | ||
quality: number; | ||
notes: string; | ||
tags: String[]; | ||
startTime?: string; | ||
endTime?: Date; | ||
} | ||
|
||
export interface Project { | ||
name: string; | ||
id: string; | ||
targetTime?: number; | ||
targetNumberOfIterations?: number; | ||
iterations?: Array<Iteration>; | ||
} |