Last active
January 8, 2023 22:55
-
-
Save shawnco/7c11188bdb08250d58bead208684a72d to your computer and use it in GitHub Desktop.
Time Management Web App section 7
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
import React from 'react'; | |
import ReactDOM from 'react-dom/client'; | |
import App from './App'; | |
import {BrowserRouter, Routes, Route} from 'react-router-dom'; | |
import TaskTable from './tasks'; | |
import Workspace from './workspace'; | |
import Home from './home'; | |
import Nav from './nav'; | |
import ProjectTable from './projects'; | |
import Project from './project'; | |
const root = ReactDOM.createRoot(document.getElementById('root')); | |
root.render( | |
<React.StrictMode> | |
<BrowserRouter> | |
<Nav /> | |
<Routes> | |
<Route path='/' element={<App />}> | |
<Route path='' element={<Home />} /> | |
<Route path='project/:id' element={<Project />} /> | |
<Route path='projects' element={<ProjectTable />} /> | |
<Route path='tasks' element={<TaskTable />} /> | |
<Route path='workspace' element={<Workspace />} /> | |
</Route> | |
</Routes> | |
</BrowserRouter> | |
</React.StrictMode> | |
); |
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
import {useState, useEffect} from 'react'; | |
import {useParams} from 'react-router-dom'; | |
import TaskTable from './tasks'; | |
const url = 'http://localhost:3001/api/project'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async getProject(id) { | |
const res = await fetch(`${url}/${id}`, {method: 'GET', headers}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async getTasks(id) { | |
const res = await fetch(`${url}/${id}/tasks`, {method: 'GET', headers}); | |
const {result} = await res.json(); | |
return result; | |
} | |
}; | |
const Project = props => { | |
const [project, setProject] = useState({}); | |
const {id} = useParams(); | |
useEffect(() => { | |
async function getProject() { | |
const data = await API.getProject(id); | |
setProject(data); | |
} | |
getProject(); | |
}, []); | |
return <> | |
{project.id && <> | |
<h1>{project.name}</h1> | |
<div>{project.description}</div> | |
</>} | |
<TaskTable project={id} /> | |
</> | |
} | |
export default Project; |
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
import {useState, useEffect} from 'react'; | |
import {Link} from 'react-router-dom'; | |
const url = 'http://localhost:3001/api/project'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async getAll() { | |
const res = await fetch(`${url}/all`, {method: 'GET', headers}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async create(body) { | |
const res = await fetch(url, {method: 'POST', headers, body: JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async update(id, body) { | |
const res = await fetch(`${url}/${id}`, {method: 'PUT',headers, body:JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async del(id) { | |
const res = await fetch(`${url}/${id}`, {method: 'DELETE', headers}); | |
const {result} = await res.json(); | |
return result; | |
} | |
} | |
const NewProject = props => { | |
const [project, setProject] = useState({name: '', description: ''}); | |
const handleChange = e => setProject({...project, [e.target.name]: e.target.value}); | |
const save = async e => { | |
const result = await API.create(project); | |
props.handleSave(result); | |
setProject({name: '', description: ''}); | |
} | |
return <tr> | |
<td></td> | |
<td><input type='text' name='name' value={project.name} onChange={handleChange} /></td> | |
<td><input type='text' name='description' value={project.description} onChange={handleChange} /></td> | |
<td><button onClick={save} disabled={!project.name || !project.description}>Save</button></td> | |
<td></td> | |
</tr> | |
} | |
const ProjectRow = props => { | |
const [project, setProject] = useState(props.project); | |
const handleChange = e => setProject({...project, [e.target.name]: e.target.value}); | |
const update = async e => { | |
const {id, ...rest} = project; | |
const result = await API.update(id, rest); | |
props.handleUpdate(id, result); | |
} | |
const del = async e => { | |
if(window.confirm(`Delete project: ${project.name}?`)) { | |
const result = await API.del(project.id); | |
props.handleDelete(project.id); | |
} | |
} | |
return <tr> | |
<td><Link to={`/project/${project.id}`}>{project.id}</Link></td> | |
<td><input type='text' name='name' value={project.name} onChange={handleChange} /></td> | |
<td><input type='text' name='description' value={project.description} onChange={handleChange} /></td> | |
<td><button onClick={update} disabled={!project.name || !project.description}>Update</button></td> | |
<td><button onClick={del}>Delete</button></td> | |
</tr> | |
} | |
const ProjectTable = props => { | |
const [projects, setProjects] = useState([]); | |
const handleSave = project => { | |
setProjects([...projects, project]); | |
} | |
const handleUpdate = (id, project) => { | |
const oldProjects = [...projects]; | |
const find = oldProjects.find(t => t.id == id); | |
if (find) { | |
find.name = project.name; | |
find.description = project.description; | |
} | |
setProjects(oldProjects); | |
} | |
const handleDelete = id => { | |
const oldProjects = [...projects]; | |
const index = oldProjects.findIndex(t => t.id == id); | |
if (index > -1) { | |
oldProjects.splice(index, 1); | |
} | |
setProjects(oldProjects); | |
} | |
useEffect(() => { | |
async function getProjects() { | |
const data = await API.getAll(); | |
setProjects(data); | |
} | |
getProjects(); | |
}, []); | |
return <table border='1'> | |
<thead> | |
<tr> | |
<th>View</th> | |
<th>Name</th> | |
<th>Description</th> | |
<th>Edit</th> | |
<th>Delete</th> | |
</tr> | |
</thead> | |
<tbody> | |
{projects.map(p => <ProjectRow key={p.id} project={p} handleUpdate={handleUpdate} handleDelete={handleDelete} />)} | |
<NewProject handleSave={handleSave} /> | |
</tbody> | |
</table> | |
} | |
export default ProjectTable; |
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
import {useState, useEffect} from 'react'; | |
import {isValidCron} from 'cron-validator'; | |
import CronDate from './cron_date'; | |
const url = 'http://localhost:3001/api/task'; | |
const projectUrl = 'http://localhost:3001/api/project'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async getProjectTasks(id) { | |
const res = await fetch(`${projectUrl}/${id}/tasks`, {method: 'GET', headers}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async create(body) { | |
const res = await fetch(url, {method: 'POST', headers, body: JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async update(id, body) { | |
const res = await fetch(`${url}/${id}`, {method: 'PUT',headers, body:JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async del(id) { | |
const res = await fetch(`${url}/${id}`, {method: 'DELETE', headers}); | |
const {result} = await res.json(); | |
return result; | |
} | |
} | |
const NewTask = props => { | |
const [task, setTask] = useState({name: '', cron_string: '', completed: false}); | |
const handleChange = e => setTask({...task, [e.target.name]:e.target.value}); | |
const saveNewCronString = cron => setTask({...task, cron_string: cron}); | |
const save = async e => { | |
const result = await API.create({...task, project: props.project}); | |
props.handleSave(result); | |
setTask({name: '', cron_string: '', completed: false}); | |
} | |
return <tr> | |
<td></td> | |
<td><input type='text' name='name' value={task.name} onChange={handleChange} /></td> | |
<CronDate cron_string={task.cron_string} saveNewCronString={saveNewCronString} /> | |
<td><button onClick={save} disabled={!task.name || !isValidCron(task.cron_string)}>Save</button></td> | |
<td></td> | |
</tr> | |
} | |
const TaskRow = props => { | |
const [task, setTask] = useState(props.task); | |
const [newCronString, setNewCronString] = useState(props.task.cron_string); | |
const handleClick = e => setTask({...task, completed: !task.completed}); | |
const handleChange = e => setTask({...task, [e.target.name]: e.target.value}); | |
const saveNewCronString = cron => setNewCronString(cron); | |
const update = async e => { | |
const {id, ...rest} = task; | |
rest.cron_string = newCronString; | |
const result = await API.update(id, rest); | |
props.handleUpdate(id, result); | |
} | |
const del = async e => { | |
if(window.confirm(`Delete task: ${task.name}?`)) { | |
const result = await API.del(task.id); | |
props.handleDelete(task.id); | |
} | |
} | |
return <tr> | |
<td><input type='checkbox' name='completed' checked={task.completed} onChange={handleClick} /></td> | |
<td><input type='text' name='name' value={task.name} onChange={handleChange} /></td> | |
<CronDate cron_string={task.cron_string} saveNewCronString={saveNewCronString} /> | |
<td><button onClick={update} disabled={!task.name || !isValidCron(task.cron_string)}>Update</button></td> | |
<td><button onClick={del}>Delete</button></td> | |
</tr> | |
} | |
const TaskTable = props => { | |
const [tasks, setTasks] = useState([]); | |
const handleSave = task => { | |
setTasks([...tasks, task]); | |
} | |
const handleUpdate = (id, task) => { | |
const oldTasks = [...tasks]; | |
const find = oldTasks.find(t => t.id == id); | |
if (find) { | |
find.completed = task.completed; | |
find.name = task.name; | |
find.cron_string = task.cron_string; | |
} | |
setTasks(oldTasks); | |
} | |
const handleDelete = id => { | |
const oldTasks = [...tasks]; | |
const index = oldTasks.findIndex(t => t.id == id); | |
if (index > -1) { | |
oldTasks.splice(index, 1); | |
} | |
setTasks(oldTasks); | |
} | |
useEffect(() => { | |
async function getTasks() { | |
const data = await API.getProjectTasks(props.project); | |
setTasks(data); | |
} | |
getTasks(); | |
}, []); | |
return <table border='1'> | |
<thead> | |
<tr> | |
<th>Completed</th> | |
<th>Name</th> | |
<th>Frequency</th> | |
<th>Days</th> | |
<th>Time</th> | |
<th>Edit</th> | |
<th>Delete</th> | |
</tr> | |
</thead> | |
<tbody> | |
{tasks.map(t => <TaskRow key={t.id} task={t} handleUpdate={handleUpdate} handleDelete={handleDelete} />)} | |
<NewTask handleSave={handleSave} project={props.project} /> | |
</tbody> | |
</table> | |
} | |
export default TaskTable; |
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
const express = require('express'); | |
const cors = require('cors'); | |
const bodyParser = require('body-parser'); | |
const {Sequelize} = require('sequelize'); | |
const path = require('path'); | |
const Socket = require('./socket'); | |
const TaskSchedule = require('./task_schedule'); | |
const db = new Sequelize('task_schedule', 'root', 'password', { | |
host: 'localhost', | |
dialect: 'mysql', | |
operatorsAliases: false, | |
pool: { | |
max: 5, | |
min: 0, | |
acquire: 30000, | |
idle: 10000 | |
} | |
}); | |
const Task = db.define('task', { | |
id: { | |
type: Sequelize.INTEGER, | |
primaryKey: true, | |
autoIncrement: true | |
}, | |
name: Sequelize.STRING, | |
cron_string: Sequelize.STRING, | |
completed: Sequelize.BOOLEAN, | |
parent: Sequelize.INTEGER, | |
project: Sequelize.INTEGER | |
}, { | |
freezeTableName: true, | |
timestamps: false | |
}); | |
const TaskNote = db.define('task_note', { | |
id: { | |
type: Sequelize.INTEGER, | |
primaryKey: true, | |
autoIncrement: true | |
}, | |
task: Sequelize.INTEGER, | |
time: Sequelize.DATE, | |
content: Sequelize.TEXT | |
}, { | |
freezeTableName: true, | |
timestamps: false | |
}); | |
const Project = db.define('project', { | |
id: { | |
type: Sequelize.INTEGER, | |
primaryKey: true, | |
autoIncrement: true | |
}, | |
name: Sequelize.STRING, | |
description: Sequelize.TEXT | |
}, { | |
freezeTableName: true, | |
timestamps: false | |
}); | |
const TaskFunctions = { | |
async getAll() { | |
return Task.findAll(); | |
}, | |
async getByIds(ids) { | |
return Task.findAll({where:{id: ids}}); | |
}, | |
async get(id) { | |
const task = await Task.findByPk(id); | |
return task; | |
}, | |
async getIncomplete() { | |
return Task.findAll({where:{completed: false}}); | |
}, | |
async create(data) { | |
const task = Task.create({ | |
name: data.name, | |
cron_string: data.cron_string, | |
parent: data.parent || null, | |
project: data.project || null | |
}); | |
return task; | |
}, | |
async update(id, data) { | |
const task = await Task.findByPk(id); | |
task.name = data.name || task.name; | |
task.cron_string = data.cron_string || data.cron_string == '' | |
? data.cron_string : task.cron_string; | |
task.completed = [true, false].includes(data.completed) ? data.completed : task.completed; | |
task.project = data.project || task.project; | |
await task.save(); | |
return task; | |
}, | |
async del(id) { | |
const task = await Task.destroy({where: {id}}); | |
return id; | |
} | |
} | |
const TaskNoteFunctions = { | |
async getByTask(task) { | |
return TaskNote.findAll({where:{task}}); | |
}, | |
async create(data) { | |
return TaskNote.create({ | |
task: data.task, | |
time: data.time, | |
content: data.content | |
}); | |
}, | |
async update(id, data) { | |
const note = await TaskNote.findByPk(id); | |
note.content = data.content; | |
return note.save(); | |
}, | |
async del(id) { | |
const note = await TaskNote.destroy({where:{id}}); | |
return id; | |
} | |
} | |
const ProjectFunctions = { | |
async getAll() { | |
return Project.findAll(); | |
}, | |
async get(id) { | |
return Project.findByPk(id); | |
}, | |
async create(data) { | |
return Project.create({ | |
name: data.name, | |
description: data.description | |
}); | |
}, | |
async update(id, data) { | |
const project = await Project.findByPk(id); | |
project.name = data.name || project.name; | |
project.description = data.description || project.description; | |
await project.save(); | |
return project; | |
}, | |
async del(id) { | |
const project = await Project.destroy({where: {id}}); | |
return id; | |
}, | |
async getTasks(id) { | |
return Task.findAll({where: {project: id}}); | |
} | |
} | |
const socket = new Socket( | |
TaskFunctions, | |
TaskNoteFunctions | |
); | |
const taskSchedule = new TaskSchedule(socket, TaskFunctions); | |
taskSchedule.rebuildSchedule(); | |
const PORT = 3001; | |
const app = express(); | |
app.use(cors()); | |
app.use(bodyParser.json()); | |
app.use(express.static(path.join(__dirname + '/../client'))); | |
app.post('/api/task', async (req, res) => { | |
const day = new Date(); | |
day.setHours(0); | |
day.setMinutes(0); | |
day.setSeconds(0); | |
day.setMilliseconds(0); | |
res.send({result}); | |
}); | |
app.get('/api/task/today', async (req,res)=> { | |
const start = new Date(); | |
start.setHours(0); | |
start.setMinutes(0); | |
start.setSeconds(0); | |
start.setMilliseconds(0); | |
const end = new Date(); | |
end.setHours(23); | |
end.setMinutes(59); | |
end.setSeconds(59); | |
end.setMilliseconds(999); | |
const ids = taskSchedule.getTasksForRange(start.getTime(), end.getTime()); | |
const result = await TaskFunctions.getByIds(ids); | |
res.send({result}); | |
}); | |
app.get('/api/task/all', async (req, res) => { | |
const result = await TaskFunctions.getAll(); | |
res.send({result}); | |
}); | |
app.get('/api/task/:id', async (req, res) => { | |
const result = await TaskFunctions.get(req.params.id); | |
res.send({result}); | |
}); | |
app.post('/api/task', async (req,res) => { | |
// need to validate the cron string!! | |
const result = await TaskFunctions.create(req.body); | |
await taskSchedule.rebuildSchedule(); | |
res.send({result}); | |
}); | |
app.put('/api/task/:id', async (req, res) => { | |
// need to validate the cron string!! | |
const result = await TaskFunctions.update(req.params.id, req.body); | |
await taskSchedule.rebuildSchedule(); | |
res.send({result}); | |
}); | |
app.delete('/api/task/:id', async (req,res) => { | |
const result = await TaskFunctions.del(+req.params.id); | |
await taskSchedule.rebuildSchedule(); | |
res.send({result}); | |
}); | |
app.get('/api/task/:id/notes', async (req, res) => { | |
const result = await TaskNoteFunctions.getByTask(+req.params.id); | |
res.send({result}); | |
}); | |
app.post('/api/task/:id/notes', async (req, res) => { | |
const result = await TaskNoteFunctions.create({ | |
id: req.params.id, | |
...req.body | |
}); | |
res.send({result}); | |
}); | |
app.put('/api/task/:id/notes', async (req, res) => { | |
const {id, ...data} = req.body; | |
const result = await TaskNoteFunctions.update(id, data); | |
res.send({result}) | |
}); | |
app.delete('/api/task/:id/notes', async (req, res) => { | |
const {id} = req.body; | |
const result = await TaskNoteFunctions.del(id); | |
res.send({result}) | |
}); | |
app.get('/api/project/all', async (req, res) => { | |
const result = await ProjectFunctions.getAll(); | |
res.send({result}); | |
}); | |
app.get('/api/project/:id', async (req, res) => { | |
const result = await ProjectFunctions.get(req.params.id); | |
res.send({result}); | |
}); | |
app.get('/api/project/:id/tasks', async (req, res) => { | |
const result = await ProjectFunctions.getTasks(req.params.id); | |
res.send({result}); | |
}); | |
app.post('/api/project', async (req, res) => { | |
const result = await ProjectFunctions.create(req.body); | |
res.send({result}); | |
}); | |
app.put('/api/project/:id', async (req, res) => { | |
const result = await ProjectFunctions.update(req.params.id, req.body); | |
res.send({result}); | |
}); | |
app.delete('/api/project/:id', async (req, res) => { | |
const result = await ProjectFunctions.del(+req.params.id); | |
res.send({result}); | |
}); | |
app.listen(PORT, () => { | |
console.log(`App is live on port ${PORT}`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment