Task management & time tracking script – Javascript

We are going to create customized task management and time tracking script using simple HTML and Javascript. Since we are creating our own script, so we can customize it as much as we need. You can check the demo from here.

Video tutorial

Include libraries

First, we need to include all the libraries that we are going to use. We will be using Bootstrap for design and SweetAlert for displaying pop-up messages.

<!-- to make responsive -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<!-- include bootstrap css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css" />

<!-- include jquery and bootstrap js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"></script>

<!-- include sweetalert for displaying dialog messages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/2.1.2/sweetalert.min.js"></script>

Add project

Then we are going to create a button that when clicked will display a Bootstrap modal to add a task.

<div class="container" style="margin-top: 50px; margin-bottom: 50px;">
    
    <!-- button to add task -->
    <div class="row" style="margin-bottom: 50px;">
        <div class="col-md-12">
            <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addTaskModal">Add Task</button>
        </div>
    </div>

</div>

Then we are going to create a Bootstrap modal. It will have a form with project and task name fields. It will also have a dropdown from which the user can select his already created projects.

<!-- modal to add project and task -->
<div class="modal fade" id="addTaskModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Add Task</h5>
                <button class="close" type="button" data-dismiss="modal">x</button>
            </div>

            <div class="modal-body">
                <form method="POST" onsubmit="return taskObj.addTask(this);" id="form-task-hour-calculator">
                    
                    <!-- select project from already created -->
                    <div class="form-group">
                        <label>Project</label>
                        <select name="project" id="add-task-project" class="form-control" required></select>
                    </div>

                    <!-- create new project -->
                    <div class="form-group">
                        <label>New Project</label>
                        <input type="text" name="new_project" id="add-project" class="form-control" placeholder="Project Name">

                        <button type="button" onclick="taskObj.addProject();" class="btn btn-primary" style="margin-top: 10px;">Add Project</button>
                    </div>

                    <!-- enter task -->
                    <div class="form-group">
                        <label>Task</label>
                        <input type="text" name="task" class="form-control" placeholder="What are you going to do ?" required />
                    </div>
                </form>
            </div>

            <!-- form submit button -->
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                <button type="submit" form="form-task-hour-calculator" class="btn btn-primary">Add Task</button>
            </div>
        </div>
    </div>
</div>

Then we need to create a Javascript function “addProject” inside the “taskObj” object. It will first check if there is any project selected, if not, then it will show an error message. Then it will create an empty array in local storage if not already created. Then it will create a new project object with empty “tasks” array. Finally, it will push the project into an array and update the local storage value, and also it will reload the projects and tasks.

// main object
var taskObj = {

    // local storage key
    key: "projects",

    // add project
    addProject: function () {

        // check if project is selected
        if (document.getElementById("add-project").value == "") {
            swal("Please enter project name");
            return false;
        }

        // initialize local storage if not already initialized
        var option = "";
        if (localStorage.getItem(this.key) == null) {
            localStorage.setItem(this.key, "[]");
        }

        // get stored object from local storage
        var data = JSON.parse(localStorage.getItem(this.key));

        // project object
        var project = {
            id: data.length,
            name: document.getElementById("add-project").value,
            tasks: []
        };

        // push new project in local storage
        data.push(project);
        localStorage.setItem(this.key, JSON.stringify(data));

        // re-load all projects
        this.loadAllProjects();

        // show all tasks
        this.showAllTasks();
    },

};

To load all the projects, we will be using the following 2 functions inside this object:

// get all stored projects
getAllProjects: function() {
    if (localStorage.getItem(this.key) == null) {
        localStorage.setItem(this.key, "[]");
    }
    return JSON.parse(localStorage.getItem(this.key))
},

// load all projects in dropdown
loadAllProjects: function () {
    var projects = taskObj.getAllProjects();
    projects = projects.reverse();
    var html = "<option value=''>Select Project</option>";
    for (var a = 0; a < projects.length; a++) {
        html += "<option value='" + projects[a].id + "'>" + projects[a].name + "</option>";
    }
    document.getElementById("add-task-project").innerHTML = html;
    document.getElementById("form-task-hour-calculator-all-projects").innerHTML = html;
},

We will do the “showAllTasks” function later. Now, when the page loads, we need to fetch all the projects and tasks and display them.

// when page loads
window.addEventListener("load", function () {
    // show all projects and tasks
    taskObj.loadAllProjects();
    taskObj.showAllTasks();
});

At this point, you will be able to create projects and see them in the dropdown. Now we need to add tasks.

Add task

To add a task, we will get the project and task name. Then we will push the newly created task object into the “tasks” array. Finally, we will update the local storage value, hide the Bootstrap modal and reload the tasks.

// add new task
addTask: function (form) {

    // get selected project and entered task
    var project = form.project.value;
    var task = form.task.value;

    // add task in project's array
    var projects = this.getAllProjects();
    for (var a = 0; a < projects.length; a++) {
        if (projects[a].id == project) {
            var taskObj = {
                id: projects[a].tasks.length,
                name: task,
                status: "Progress", // Progress, Completed
                isStarted: false,
                logs: [],
                started: this.getCurrentTimeInTaskStartEndFormat(),
                ended: ""
            }
            projects[a].tasks.push(taskObj);
            break;
        }
    }

    // update local storage
    localStorage.setItem(this.key, JSON.stringify(projects));

    // hide modal
    jQuery("#addTaskModal").modal("hide");
    jQuery('.modal-backdrop').remove();

    // re-load all tasks
    this.showAllTasks();

    // prevent form from submitting
    return false;
},

You might have see the function “getCurrentTimeInTaskStartEndFormat”. Our task management script will save the date and time in format YYYY-MM-DD HH:mm:ss e.g. 2021-06-15 20:53:15.

// get current datetime in proper format (e.g. 2021-06-15 20:53:15)
getCurrentTimeInTaskStartEndFormat() {
    let current_datetime = new Date();
    var date = current_datetime.getDate();
    date = (date < 10) ? "0" + date : date;
    var month = (current_datetime.getMonth() + 1);
    month = (month < 10) ? "0" + month : month;
    var hours = current_datetime.getHours();
    hours = (hours < 10) ? "0" + hours : hours;
    var minutes = current_datetime.getMinutes();
    minutes = (minutes < 10) ? "0" + minutes : minutes;
    var seconds = current_datetime.getSeconds();
    seconds = (seconds < 10) ? "0" + seconds : seconds;
    let formatted_date = current_datetime.getFullYear() + "-" + month + "-" + date + " " + hours + ":" + minutes + ":" + seconds;
    return formatted_date;
},

Show all tasks

To show all tasks, we need to create a table where we will display all tasks created.

<!-- show all tasks -->
<table class="table">
    <caption class="text-center">All Tasks</caption>
        <tr>
            <th>Task</th>
            <th>Project</th>
            <th>Status</th>
            <th>Duration</th>
            <th>Date</th>
            <th>Action</th>
        </tr>

    <tbody id="all-tasks"></tbody>
</table>

Now is the time to create a function “showAllTasks” in our “taskObj” object. This will be a long function, so we will explain the working of this function step-by-step:

// show all tasks in table
showAllTasks: function () {
    var html = "";

    // get all projects
    var projects = this.getAllProjects();
    for (var a = 0; a < projects.length; a++) {
        projects[a].tasks = projects[a].tasks.reverse();

        // get tasks in each project
        for (var b = 0; b < projects[a].tasks.length; b++) {
            html += "<tr>";
                html += "<td>" + projects[a].tasks[b].name + "</td>";
                html += "<td>" + projects[a].name + "</td>";
                if (projects[a].tasks[b].isStarted) {
                    html += "<td><label class='started'>Started</label></td>";
                } else {
                    if (projects[a].tasks[b].status == "Completed") {
                        html += "<td><label class='completed'>" + projects[a].tasks[b].status + "</label></td>";
                    } else {
                        html += "<td>" + projects[a].tasks[b].status + "</td>";
                    }
                }

                // get total duration of each task using it's logs
                var duration = 0;
                for (var c = 0; c < projects[a].tasks[b].logs.length; c++) {
                    var log = projects[a].tasks[b].logs[c];
                    if (log.endTime > 0) {
                        duration += log.endTime - log.startTime;
                    }
                }

                // convert millisecond into hours, minutes and seconds
                duration = Math.abs((duration / 1000).toFixed(0));
                var hours = Math.floor(duration / 3600) % 24;
                hours = (hours < 10) ? "0" + hours : hours;
                // var days = Math.floor(diff / 86400);
                var minutes = Math.floor(duration / 60) % 60;
                minutes = (minutes < 10) ? "0" + minutes : minutes;
                var seconds = duration % 60;
                seconds = (seconds < 10) ? "0" + seconds : seconds;

                // show timer if task is already started
                if (projects[a].tasks[b].isStarted) {
                    var dataStartedObj = {
                        "duration": duration,
                        "project": projects[a].id,
                        "task": projects[a].tasks[b].id
                    };
                    html += "<td data-started='" + JSON.stringify(dataStartedObj) + "'>" + hours + ":" + minutes + ":" + seconds + "</td>";
                } else {
                    html += "<td>" + hours + ":" + minutes + ":" + seconds + "</td>";
                }

                // show task duration if completed
                if (projects[a].tasks[b].status == "Completed") {
                    html += "<td>" + projects[a].tasks[b].started + "<br><span style='margin-left: 30px;'>to</span><br>" + projects[a].tasks[b].ended + "</td>";
                } else {
                    html += "<td>" + projects[a].tasks[b].started + "</td>";
                }

                // form to change task status
                html += "<td>";
                    html += "<form method='POST' id='form-change-task-status-" + projects[a].id + projects[a].tasks[b].id + "'>";
                        html += "<input type='hidden' name='project' value='" + projects[a].id + "'>";
                        html += "<input type='hidden' name='task' value='" + projects[a].tasks[b].id + "'>";
                        html += "<select class='form-control' name='status' onchange='taskObj.changeTaskStatus(this);' data-form-id='form-change-task-status-" + projects[a].id + projects[a].tasks[b].id + "'>";
                            html += "<option value=''>Change status</option>";
                            if (projects[a].tasks[b].isStarted) {
                                html += "<option value='stop'>Stop</option>";
                            } else {
                                html += "<option value='start'>Start</option>";
                            }
                            if (projects[a].tasks[b].status == "Progress") {
                                html += "<option value='complete'>Mark as Completed</option>";
                            } else {
                                html += "<option value='progress'>Make in Progress Again</option>";
                            }
                            html += "<option value='delete'>Delete</option>";
                        html += "</select>";
                    html += "</form>";
                html += "</td>";
            html += "</tr>";
        }
    }
    document.getElementById("all-tasks").innerHTML = html;
},

The step number corresponds to the highlighted line above.

  1. First, we are looping through all projects and each project’s tasks.
  2. Displaying the task name and project name.
  3. If the task status is “started” then displaying a started message. (we will do this later).
  4. And if the task status is “completed”, then displaying a message for the completed task.
  5. Then we will get the total duration of each task.
  6. The duration will be in milliseconds, so we will convert that into days, hours, minutes, and seconds.
  7. If the task is started, then we will attach an attribute “data-started” to the <td> tag. This will allow us to continuously update the timer every second.
  8. And if the task is completed, then we will display the start and end times of the task. So you can know when the task started and when it is finished.
  9. And finally, we are creating a form from which we can change the status of the task.

Change task status

Now we need to create a function to change the status of the task. Again, this will be a long function, we will explain that step-by-step too.

// change task status
changeTaskStatus: function (self) {

    // if task is not selected
    if (self.value == "") {
        return;
    }

    // loop through all projects
    var formId = self.getAttribute("data-form-id");
    var form = document.getElementById(formId);
    var projects = this.getAllProjects();
    for (var a = 0; a < projects.length; a++) {

        // if project matches
        if (projects[a].id == form.project.value) {

            // loop through all tasks of that project
            for (var b = 0; b < projects[a].tasks.length; b++) {

                // if task matches
                if (projects[a].tasks[b].id == form.task.value) {

                    // if the status is set to delete
                    if (self.value == "delete") {

                        // ask for confirmation
                        swal({
                            title: "Are you sure?",
                            text: "Deleting the task will delete its hours too.",
                            icon: "warning",
                            buttons: true,
                            dangerMode: true,
                        })
                        .then((willDelete) => {
                            if (willDelete) {

                                // remove task from array
                                projects[a].tasks.splice(b, 1);

                                // update local storage
                                localStorage.setItem(this.key, JSON.stringify(projects));

                                // re-load all tasks
                                this.showAllTasks();
                            } else {

                                // reset dropdown
                                self.value = "";
                            }
                        });
                    } else if (self.value == "complete") {
                        // mark as completed
                        projects[a].tasks[b].status = "Completed";

                        // stop the timer
                        projects[a].tasks[b].isStarted = false;

                        // log end time
                        projects[a].tasks[b].ended = this.getCurrentTimeInTaskStartEndFormat();
                        for (var c = 0; c < projects[a].tasks[b].logs.length; c++) {
                            if (projects[a].tasks[b].logs[c].endTime == 0) {
                                projects[a].tasks[b].logs[c].endTime = new Date().getTime();
                                break;
                            }
                        }
                    } else if (self.value == "progress") {
                        // mark as in progress
                        projects[a].tasks[b].status = "Progress";

                        // stop the timer
                        projects[a].tasks[b].isStarted = false;
                    } else if (self.value == "start") {
                        // ask for confirmation
                        swal({
                            title: "Are you sure?",
                            text: "This will start the timer.",
                            icon: "warning",
                            buttons: true,
                            dangerMode: true,
                        })
                        .then((doStart) => {
                            if (doStart) {
                                
                                // mark as started
                                projects[a].tasks[b].isStarted = true;

                                // add in log
                                var logObj = {
                                    id: projects[a].tasks[b].logs.length,
                                    startTime: new Date().getTime(),
                                    endTime: 0
                                };
                                projects[a].tasks[b].logs.push(logObj);

                                // update local storage
                                localStorage.setItem(this.key, JSON.stringify(projects));

                                // re-load all tasks
                                this.showAllTasks();
                            } else {

                                // reset dropdown
                                self.value = "";
                            }
                        });
                    } else if (self.value == "stop") {

                        // ask for confirmation
                        swal({
                            title: "Are you sure?",
                            text: "This will stop the timer.",
                            icon: "warning",
                            buttons: true,
                            dangerMode: true,
                        })
                        .then((doStop) => {
                            if (doStop) {

                                // mark as stopped
                                projects[a].tasks[b].isStarted = false;

                                // update end time in log
                                for (var c = 0; c < projects[a].tasks[b].logs.length; c++) {
                                    if (projects[a].tasks[b].logs[c].endTime == 0) {
                                        projects[a].tasks[b].logs[c].endTime = new Date().getTime();
                                        break;
                                    }
                                }

                                // update local storage
                                localStorage.setItem(this.key, JSON.stringify(projects));

                                // re-load tasks
                                this.showAllTasks();
                            } else {

                                // reset dropdown
                                self.value = "";
                            }
                        });
                    }
                    break;
                }
            }
            break;
        }
    }

    // delete, start and stop are already handled above
    if (self.value == "delete"
        || self.value == "start"
        || self.value == "stop") {
        //
    } else {
        // update local storage and re-load tasks
        localStorage.setItem(this.key, JSON.stringify(projects));
        this.showAllTasks();
    }
},

Comments have been added with each line for an explanation. If you still find it difficult, please feel free to mention it in the comments section below.

At this point, you will be able to start and stop the timer. Upon stopping, you will be able to see the start and end time of the task along with the total duration of the task.

Now, we need to update the timer each second if the task is started. You need to write the following lines in your window “load” event listener:

// call this function each second
setInterval(function () {

    // increment 1 second in all running tasks
    var dataStarted = document.querySelectorAll("td[data-started]");
    for (var i = 0; i < dataStarted.length; i++) {
        var dataStartedObj = dataStarted[i].getAttribute("data-started");
        var dataStartedObj = JSON.parse(dataStartedObj);
        dataStartedObj.duration++;

        // convert timestamp into readable format
        var hours = Math.floor(dataStartedObj.duration / 3600) % 24;
        hours = (hours < 10) ? "0" + hours : hours;
        // var days = Math.floor(diff / 86400);
        var minutes = Math.floor(dataStartedObj.duration / 60) % 60;
        minutes = (minutes < 10) ? "0" + minutes : minutes;
        var seconds = dataStartedObj.duration % 60;
        seconds = (seconds < 10) ? "0" + seconds : seconds;
        dataStarted[i].innerHTML = hours + ":" + minutes + ":" + seconds;

        // update log end time
        var projects = taskObj.getAllProjects();
        for (var a = 0; a < projects.length; a++) {
            if (projects[a].id == dataStartedObj.project) {
                for (var b = 0; b < projects[a].tasks.length; b++) {
                    if (projects[a].tasks[b].id == dataStartedObj.task) {
                        for (var c = 0; c < projects[a].tasks[b].logs.length; c++) {
                            if (c == projects[a].tasks[b].logs.length - 1) {
                                projects[a].tasks[b].logs[c].endTime = new Date().getTime();

                                // update local storage
                                window.localStorage.setItem(taskObj.key, JSON.stringify(projects));

                                // update timer
                                dataStarted[i].setAttribute("data-started", JSON.stringify(dataStartedObj));

                                break;
                            }
                        }
                        break;
                    }
                }
                break;
            }
        }
    }
}, 1000);

For explanation, comments have been added. Feel free to ask if you are having trouble understanding something.

Delete project

You are now able to create projects, now is the time to have the ability to delete them. So create a simple form that displays a list of all created projects in a dropdown:

<!-- form to delete project -->
<form method="POST" onsubmit="return taskObj.deleteProject(this);" style="display: contents;">
    <select name="project" class="form-control" style="display: initial; width: 200px; margin-left: 5px; margin-right: 5px;" id="form-task-hour-calculator-all-projects"></select>
    <input type="submit" class="btn btn-danger" value="Delete Project">
</form>

Then we need to create a function “deleteProject” in our “taskObj” object that will delete the project from local storage and refresh the projects and tasks UI.

// delete project
deleteProject: function (self) {

    // check if any project is selected
    if (self.project.value == "") {
        swal("Please select a project to delete");
        return false;
    }

    // ask for confirmation
    swal({
        title: "Are you sure?",
        text: "Deleting the project will delete its tasks too.",
        icon: "warning",
        buttons: true,
        dangerMode: true,
    })
    .then((willDelete) => {
        if (willDelete) {

            // remove from array and update local storage
            var projects = taskObj.getAllProjects();
            for (var a = 0; a < projects.length; a++) {
                if (projects[a].id == self.project.value) {
                    projects.splice(a, 1);
                    localStorage.setItem(taskObj.key, JSON.stringify(projects));

                    // re-load data
                    taskObj.loadAllProjects();
                    taskObj.showAllTasks();

                    break;
                }
            }
        } else {

            // reset project dropdown
            self.project.value = "";
        }
    });
    return false;
}

Applying CSS Styles

This may not be the major concern for you, but just to highlight the task when it is started and when it is completed, we are applying some CSS styles to it.

/* style when project is started */
.started {
    color: white;
    font-weight: bold;
    background: green;
    padding: 5px;
    border-radius: 5px;
}

/* style when project is completed */
.completed {
    color: white;
    font-weight: bold;
    background: greenyellow;
    padding: 5px;
    border-radius: 5px;
}

Congratulations ! You just created your own task management and time tracking script in Javascript. Feel free to add more features in it as per your needs.

[wpdm_package id=’1219′]