Skip to content

Instantly share code, notes, and snippets.

@shawnco
Last active January 8, 2023 22:55
Show Gist options
  • Save shawnco/7c11188bdb08250d58bead208684a72d to your computer and use it in GitHub Desktop.
Save shawnco/7c11188bdb08250d58bead208684a72d to your computer and use it in GitHub Desktop.
Time Management Web App section 7
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>
);
import { Link } from 'react-router-dom';
import './App.css';
const Nav = props => {
return <ul id={'nav'}>
<li><Link to='/'>Home</Link></li>
<li><Link to='/workspace'>Workspace</Link></li>
<li><Link to='/projects'>Projects</Link></li>
</ul>
}
export default Nav;
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;
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;
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;
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