Time Management Web App: Front Page Agenda and Navigation

Shawn
4 min readOct 18, 2022

--

Here we make the app easier to navigate, and show the day’s unfinished tasks on the home page.

Front Page Agenda

The front page is meant to show the currently unfinished tasks for the day. We can do this directly with the node-schedule package because each job in the schedule comes with a nextInvocation function. This gives a value that can be turned into a Unix timestamp. We can find the Unix timestamps for the start and end of the current day, and fetch all jobs whose next invocation falls between them.

In the TaskSchedule class, add this function. It’s intentionally made a little generic, in case we want to re-use it for things other than the current day.

getTasksForRange(start, end) {
const todayTasks = [];
Object.keys(schedule.scheduledJobs).map(k => {
const job = schedule.scheduledJobs[k];
const next = job.nextInvocation().toDate().getTime();
if (next >= start && next <= end) {
const id = +job.name.split(':')[1];
todayTasks.push(id);
}
});
return todayTasks;
}

This function makes use of how we designed the job names to include the task’s id. We check if the job will run today and, if so, add its id to a list.

The other half of this logic happens over in index.js . First we need a function to get the tasks for the ids, which we add to TaskFunctions:

async getByIds(ids) {
return Task.findAll({where: {id: ids}});
},

Then add the API route:

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});
});

The route fetches the Unix timestamp for when this day starts and ends, calls the getTasksForRange function, and gets the tasks for the ids. It then sends the tasks to the frontend.

The frontend just displays the tasks as a table. At this point we are very familiar with having an API object and a table. This page is even simpler because it’s purely read-only. Nothing about the tasks can be edited here. Here’s home.js:

import {useEffect, useState} from 'react';const url = 'http://localhost:3001/api/task/today';
const headers = {
'Content-Type': 'applicaton/json'
};
const API = {
async getToday() {
const res = await fetch(`${url}`, {
method: 'GET',
headers
});
const {result} = await res.json();
return result;
}
}
const TaskRow = props => {
const {task} = props;
return <tr>
<td>{task.name}</td>
<td>{task.cron_string}</td>
</tr>
}
const Home = props => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
async function getTasks() {
const data = await API.getToday();
setTasks(data);
}
getTasks();
}, []);
return <>
<h1>Today's Incomplete Tasks</h1>
<table border='1'>
<thead>
<tr>
<th>Name</th>
<th>Cron String</th>
</tr>
</thead>
<tbody>
{tasks.map(t => <TaskRow key={t.id} task={t} />)}
</tbody>
</table>
</>
}
export default Home;

In the client index.js file, we import it as a new route:

import Home from './home';...<Route path='' element={<Home />} />

And here’s what it looks like when we go to http://localhost:3000:

Perhaps not the prettiest thing, but nevertheless functional.

Navigation

At this point we have an app that is functional, but impossible to navigate. We remedy this with a simple navigation component. It will show links horizontally across the top of each page. Create nav.js as such:

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='/tasks'>Tasks</Link></li>
</ul>
}
export default Nav;

We need some CSS work to make the list horizontal. Hop over to App.css, clear it out, then add this:

#nav {
list-style-type: none;
margin: 0;
padding: 0 0 0 10px;
}
#nav li {
display: inline;
margin-right: 10px
}

Now go to the client index.js and include the navigation. It should go immediately after the <BrowserRouter>, lest it only appear on some routes.

import Nav from './nav';...<Nav />

And here’s what the front page looks like with the navigation:

Putting It Together

We now have a basic app for scheduling tasks and taking notes on them. We can also see what unfinished tasks we have for the day, and what their cron string is.

But the problem is, cron expressions don’t really mean anything unless you’ve used them a lot. It would be nice if we could schedule the tasks on the frontend, without needing to know cron expressions. In the next section we will do just that.

Here’s what the code should look like at this point. (You may notice the file names in the Gist start with client_ or server_. Those should be client/ and server/, but Gists don’t allow slashes in the names.)

Time Management Web App Series

--

--