Created
October 17, 2022 21:23
-
-
Save shawnco/b73aea609c73206e1ae42c4f3c99de75 to your computer and use it in GitHub Desktop.
Time Management Web App section 4
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} from 'react'; | |
const url = 'http://localhost:3001/api/task'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async create(task, content) { | |
const res = await fetch(`${url}/${task}/notes`, { | |
method: 'POST', | |
headers, | |
body: JSON.stringify({ | |
task, | |
content, | |
time: new Date() | |
}) | |
}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async update(task, body) { | |
const res = await fetch(`${url}/${task}/notes`, { | |
method: 'PUT', | |
headers, | |
body: JSON.stringify(body) | |
}); | |
const {result} = await res.json(); | |
return result; | |
}, | |
async del(task, id) { | |
const res = await fetch(`${url}/${task}/notes`, { | |
method: 'DELETE', | |
headers, | |
body: JSON.stringify({id}) | |
}); | |
const {result} = await res.json(); | |
return result; | |
} | |
}; | |
const NewNote = props => { | |
const [content, setContent] = useState(''); | |
const handleChange = e => setContent(e.target.value); | |
const save = async e => { | |
const result = await API.create(props.task, content); | |
props.handleSave(result); | |
setContent(''); | |
} | |
return <tr> | |
<td></td> | |
<td><textarea cols={40} rows={10} value={content} onChange={handleChange} /></td> | |
<td><button onClick={save}>Save</button></td> | |
<td></td> | |
</tr> | |
} | |
const NoteRow = props => { | |
const [content, setContent] = useState(props.note.content); | |
const handleChange = e => setContent(e.target.value); | |
const update = async e => { | |
const {task} = props; | |
const result = await API.update(task, {id: props.note.id, content}); | |
props.handleUpdate(result); | |
} | |
const del = async e => { | |
if (window.confirm('Delete note?')) { | |
const {task} = props; | |
const result = await API.del(task, props.note.id); | |
props.handleDelete(props.note.id); | |
} | |
} | |
return <tr> | |
<td>{(new Date(props.note.time)).toLocaleString()}</td> | |
<td><textarea cols={40} rows={10} value={content} onChange={handleChange} /></td> | |
<td><button onClick={update}>Update</button></td> | |
<td><button onClick={del}>Delete</button></td> | |
</tr> | |
} | |
const TaskNotes = props => { | |
const [notes, setNotes] = useState(props.notes); | |
const handleSave = note => { | |
setNotes([...notes, note]) | |
} | |
const handleUpdate = note => { | |
const oldNotes = [...notes]; | |
const find = oldNotes.find(n => n.id == note.id); | |
if (find) { | |
find.content = note.content; | |
} | |
setNotes(oldNotes); | |
} | |
const handleDelete = id => { | |
const oldNotes = [...notes]; | |
const index = oldNotes.findIndex(n => n.id == id); | |
if (index > -1) { | |
oldNotes.splice(index, 1); | |
} | |
setNotes(oldNotes); | |
} | |
return <table border='1'> | |
<thead> | |
<tr> | |
<th>Date/Time</th> | |
<th>Content</th> | |
<th>Update</th> | |
<th>Delete</th> | |
</tr> | |
</thead> | |
<tbody> | |
{notes.map(n => <NoteRow | |
task={props.task} | |
key={n.id} | |
note={n} | |
handleUpdate={handleUpdate} | |
handleDelete={handleDelete} | |
/>)} | |
<NewNote task={props.task} handleSave={handleSave} /> | |
</tbody> | |
</table> | |
} | |
export default TaskNotes; |
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 TaskNotes from './task_notes'; | |
const url = 'http://localhost:3001/api/task'; | |
const headers = { | |
'Content-Type': 'application/json' | |
}; | |
const API = { | |
async update(id, body) { | |
const res = await fetch(`${url}/${id}`, {method: 'PUT', headers, body: JSON.stringify(body)}); | |
const {result} = await res.json(); | |
return result; | |
} | |
} | |
const Workspace = props => { | |
const [task, setTask] = useState({}); | |
const [notes, setNotes] = useState([]); | |
useEffect(() => { | |
const socket = new WebSocket('ws://localhost:8080'); | |
socket.onmessage = e => { | |
try { | |
const data = JSON.parse(e.data); | |
setTask(data.task); | |
setNotes(data.taskNotes); | |
} catch (e) { | |
console.log(e); | |
} | |
} | |
}, []); | |
const handleClick = async e => { | |
const {id, completed} = task; | |
const result = await API.update(id, {completed: !completed}); | |
setTask({...task, completed: !completed}); | |
}; | |
if (task.id) { | |
return <> | |
<table border='1'> | |
<thead> | |
<tr> | |
<th>Completed</th> | |
<th>Name</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td><input type='checkbox' name='completed' checked={task.completed} onChange={handleClick} /></td> | |
<td>{task.name}</td> | |
</tr> | |
</tbody> | |
</table> | |
<TaskNotes task={task.id} notes={notes} /> | |
</> | |
} else { | |
return <b>Waiting for a task to start</b> | |
} | |
} | |
export default Workspace; |
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 TaskSchedule = require('./task_schedule'); | |
const Socket = require('./socket'); | |
const db = new Sequelize('task_schedule', 'username', 'password', { | |
host: 'localhost', | |
dialect: 'mysql', | |
operatorAliases: 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 | |
}, { | |
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 TaskFunctions = { | |
async getAll() { | |
return Task.findAll(); | |
}, | |
async get(id) { | |
return Task.findByPk(id); | |
}, | |
async create(data) { | |
const task = Task.create({ | |
name: data.name, | |
cron_string: data.cron_string | |
}); | |
return task; | |
}, | |
async update(id, data) { | |
const task = await Task.findByPk(id); | |
task.name = data.name || task.name; | |
task.cron_string = data.cron_string || task.cron_string; | |
task.completed = [true, false].includes(data.completed) ? data.completed : task.completed; | |
await task.save(); | |
return task; | |
}, | |
async del(id) { | |
const task = await Task.destroy({where: {id}}); | |
return id; | |
}, | |
async getIncomplete() { | |
return Task.findAll({where: {completed: false}}); | |
} | |
} | |
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 socket = new Socket(TaskNoteFunctions); | |
const taskSchedule = new TaskSchedule(TaskFunctions, socket); | |
taskSchedule.rebuildSchedule(); | |
const PORT = 3001; | |
const app = express(); | |
app.use(cors()); | |
app.use(bodyParser.json()); | |
app.use(express.static(path.join(__dirname + '/../client'))); | |
app.get('/api/test', (req, res) => { | |
res.send('Hello world!'); | |
}); | |
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) => { | |
const result = await TaskFunctions.create(req.body); | |
await taskSchedule.rebuildSchedule(); | |
res.send({result}); | |
}); | |
app.put('/api/task/:id', async (req, res) => { | |
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.listen(PORT, () => { | |
console.log(`App is live on port ${PORT}`); | |
}); |
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 {WebSocketServer, OPEN} = require('ws'); | |
class Socket { | |
constructor(taskNoteFns) { | |
this.taskNoteFns = taskNoteFns; | |
this.wss = new WebSocketServer({port: 8080}); | |
this.wss.on('connection', ws => console.log('Connected!')); | |
} | |
async sendTask(task) { | |
const taskNotes = await this.taskNoteFns.getByTask(task.id); | |
this.wss.clients.forEach(client => { | |
if (client.readyState === OPEN) { | |
const data = { | |
type: 'task_start', | |
task, | |
taskNotes | |
}; | |
client.send(JSON.stringify(data)); | |
} | |
}); | |
} | |
} | |
module.exports = Socket; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment