const DISPLAY_TASK_COUNT = 2;
const RATING_FILES_NAMES = ["1angry.png", "2upset.png", "3neutral.png", "4happy.png", "5overjoyed.png"];
const PRODUCTIVITY_FILES_NAMES = ["1-icon.svg", "2-icon.svg", "3-icon.svg", "4-icon.svg", "5-icon.svg"];
// Wait for window to load
window.addEventListener('DOMContentLoaded', init);
// Get current date globals
var currDate = new Date();
var month = currDate.getMonth();
var year = currDate.getFullYear();
// Defines confetti
const confetti = window.confetti;
/**
* Updates the global date variables to the current date.
*/
function updateDateGlobals() {
month = currDate.getMonth();
year = currDate.getFullYear();
}
/**
* Initializes the page when the DOM content is fully loaded.
*/
function init() {
// Initiaze the jump buttons
displayJump(year - 6, year + 5);
taskListViewHandler();
// Initially display the calendar, calendar header, and task colors
calendarHeader();
displayCalendar();
// Initialize the buttons
initButtons();
loadTasks();
}
/**
* Initializes the buttons for adding tasks, navigating months, and other functionalities.
*/
function initButtons() {
const addTaskBtn = document.querySelector(".add-task-btn");
addTaskBtn.addEventListener("click", () => {
addTask();
});
// Previous month button
let prevBtn = document.querySelector(".prev-date-btn");
prevBtn.addEventListener('click', prev);
// Next month button
let nextBtn = document.querySelector(".next-date-btn");
nextBtn.addEventListener('click', next);
// Jump header buttons
// List of months
let monthJumpBtn = document.querySelectorAll(".month-btn");
monthJumpBtn.forEach(btn => {
btn.addEventListener("click", () => {
let monthValue = btn.getAttribute("value");
jump(monthValue, year);
});
});
// List of years
let yearJumpBtn = document.querySelectorAll(".year-btn");
yearJumpBtn.forEach(btn => {
btn.addEventListener("click", () => {
let yearValue = btn.getAttribute("value");
jump(month, yearValue);
});
});
// Resize window for responsiveness
window.addEventListener('resize', windowWidth);
// Add left/right arrows to goto prev/next months
window.addEventListener('keydown', function(event) {
if (event.key === "ArrowLeft") {
prev();
} else if (event.key === "ArrowRight") {
next();
}
});
}
/**
* Updates the global currDate to the next date and displays the next month.
*/
function next(){
// Increment the month
currDate.setMonth(currDate.getMonth() + 1);
updateDateGlobals();
displayCalendar();
}
/**
* Updates the global currDate to the previous month and displays the previous month's calendar.
*/
function prev() {
// Decrement the month
currDate.setMonth(currDate.getMonth() - 1);
updateDateGlobals();
displayCalendar();
}
/**
* Adds a task to the task list upon "Add Task" button click.
*
* @param {boolean} [loadTask=false] - Indicates whether the task is being loaded from storage.
* @returns {HTMLElement} - The newly created task element.
*/
function addTask(loadTask = false) {
// Add a task to an element of task container
const taskList = document.querySelector(".task-container");
const task = document.createElement("li");
task.className = "task";
// Add it at the first row
task.insertAdjacentHTML("beforeend", `
<div class="check-input-wrap">
<button id="task1" class="task-checkbox" aria-label="Task Checkbox"></button>
<div contenteditable="true" class="task-input" placeholder="Add a task..." onkeypress="return this.innerText.length <= 180;"></div>
</div>
<div class="color-buttons">
<button id="purple" class="color-button" aria-label="Purle"></button>
<button id="green" class="color-button" aria-label="Green"></button>
<button id="blue" class="color-button" aria-label="Blue"></button>
<button id="pink" class="color-button" aria-label="Pink"></button>
<button id="grey" class="color-button" aria-label="Grey"></button>
</div>
<img class="trash-icon" src="../icons/trash-icon.svg" alt="Remove">
`);
task.querySelector(".task-input").addEventListener("input", saveTasks);
taskList.append(task);
// Listener to stop editing when user presses enter
const task_name = task.querySelector(".task-input");
task_name.addEventListener('keydown', function (event) {
// Shift + Enter pressed, insert a line break
if (event.key == 'Enter') {
// Enter pressed, end editing
if (!event.shiftKey) {
// Prevent default behavior of Enter key
event.preventDefault();
// Remove focus from the element
task_name.blur();
}
}
});
// Auto click into the task name text box
if (loadTask == false){
setTimeout(() => {
task_name.focus();
const selection = document.getSelection();
if (selection.rangeCount > 0) {
selection.collapseToEnd();
}
}, 0);
}
// Add functionality to task buttons
taskButtonsFunctionality(task);
return task;
}
/**
* Adds button functionality to a task upon creation.
*
* @param {HTMLElement} task - The task element to add functionality to.
*/
function taskButtonsFunctionality(task) {
// Implement color changing functionality
const colorBtns = task.querySelectorAll(".color-button");
colorBtns.forEach(btn => {
btn.addEventListener('click', function () {
let color;
switch (btn.id) {
case "purple":
color = "#C380CC";
break;
case "green":
color = "#91DC79";
break;
case "blue":
color = "#6BB1D9";
break;
case "pink":
color = "#EEBAE9";
break;
default:
color = "var(--main-color)";
}
task.style['background-color'] = color;
saveTasks();
});
});
// Trash icon delete functionality
const deleteIcon = task.querySelector(".trash-icon");
deleteIcon.addEventListener("click", () => {
task.remove();
saveTasks();
});
// Checkbox move to completed tasks functionality
const checkbox = task.querySelector(".task-checkbox");
checkbox.addEventListener('click', function () {
// Add or remove completed from class name
if (task.className.includes('complete')) {
task.classList.remove('complete');
const taskContainer = document.querySelector('.task-container');
taskContainer.appendChild(task);
task.addEventListener("blur", saveTasks);
saveTasks();
}
else {
task.classList.add('complete');
saveCompleted(task);
task.remove()
saveTasks();
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 }
});
}
});
}
/**
* Expands task list from collapsed state.
*/
function taskListViewHandler() {
const taskList = document.querySelector('.task-list');
const taskWrap = document.querySelector('.task-wrapper');
const outSide = document.querySelector('.full-calendar');
taskList.addEventListener('click', function(event) {
if (event.target === taskList) {
if (window.innerWidth <= 800) {
taskList.classList.toggle('active');
taskWrap.classList.toggle('active');
}
}
});
outSide.addEventListener('click', function(){
if (window.innerWidth <= 800) {
taskList.classList.remove('active');
taskWrap.classList.remove('active');
}
});
}
/**
* Saves the completed tasks for a specific day.
*
* @param {HTMLElement} completedTaskElement - The task element that was completed.
*/
function saveCompleted(completedTaskElement) {
let data = getJournal();
let dateText = new Date().toLocaleDateString();
let completedTask = loadFromStorage(data, dateText, "completedTasks") || [];
let taskName = completedTaskElement.querySelector('.task-input').textContent;
let taskColor = completedTaskElement.style['background-color']
completedTask.push({
text: taskName,
color: taskColor,
});
saveToStorage(data, dateText, "completedTasks", completedTask);
localStorage.setItem("journals", JSON.stringify(data));
displayCalendar();
}
/**
* Displays the calendar for the current month.
*/
function displayCalendar() {
// Get body and clear current calendar
let tbody = document.getElementById("tbody-calendar");
tbody.innerHTML = "";
// Initialize list of months
let allMonths = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
// Get day of the week of first day in given month
let currCalendarMonth = new Date(year, month, 1);
let today = new Date();
let dayOffset = -(currCalendarMonth.getDay());
// Get month and year header
let monthHeader = document.getElementById("month");
let yearHeader = document.getElementById("year");
monthHeader.textContent = allMonths[parseInt(month, 10)];
yearHeader.textContent = year;
// Build Calendar
// Loop through number of rows
let currDay;
for (let i = 0; i < 6; i++) {
// Create rows
let row = document.createElement("tr");
// Loop through number of columns
for (let j = 0; j < 7; j++) {
// Create data for each table cell in the row
let cellData = document.createElement("td");
// Create span for cell number
let cellNum = document.createElement('span');
// Add offset to first date in calendar
if (i === 0 && j === 0) {
currCalendarMonth.setDate(currCalendarMonth.getDate() + dayOffset);
currDay = currCalendarMonth.getDate();
}
// Increment date by one
else {
currCalendarMonth.setDate(currCalendarMonth.getDate() + 1);
currDay = currCalendarMonth.getDate();
}
// Add number and class to cellNum
cellNum.textContent = currDay;
cellNum.className = "cell-date";
let cellDate = new Date(currCalendarMonth);
// If current month
if (currCalendarMonth.getMonth() === currDate.getMonth()) {
cellData.classList.add("curr-month-date-num");
}
// If other month
else {
cellData.classList.add("other-month-date-num");
}
// If cell is today
if (currCalendarMonth.toDateString() === today.toDateString()) {
cellData.classList.add("current-date");
}
// If cell is in the past
else if (currCalendarMonth < today) {
cellData.classList.add("past-date");
}
// If cell is in the future
else {
cellData.classList.add("future-date");
}
// Add cell number to calendar cell
cellData.appendChild(cellNum);
if (cellDate <= today) {
loadCellDataTest(cellData, currCalendarMonth);
}
// Append new cell to row
row.appendChild(cellData);
}
// Append row to table
tbody.appendChild(row);
}
// Change the header if the window size is too small
windowWidth();
// Get the width of month and align the year
let monthWidth = document.getElementById('month-dropdown').offsetWidth;
document.getElementById('year-dropdown').style.left = monthWidth + 5 + 'px';
}
/**
* Loads cell data such as rating, productivity, and tasks for a specific date in the calendar.
*
* @param {HTMLElement} cellData - The table cell element to populate with data.
* @param {Date} currCalendarMonth - The current month being displayed in the calendar.
*/
function loadCellDataTest(cellData, currCalendarMonth) {
let journals = getJournal();
let dateText = currCalendarMonth.toLocaleDateString();
let rating = loadFromStorage(journals, dateText, "rating");
let productivity = loadFromStorage(journals, dateText, "productivity");
let tasks = loadFromStorage(journals, dateText, "completedTasks");
if (rating != null) {
// Add sentiment icon
let sentimentIcon = document.createElement("img");
sentimentIcon.src = `../icons/${RATING_FILES_NAMES[rating - 1]}`;
sentimentIcon.alt = "sentiment icon";
sentimentIcon.className = "sentiment-icon";
// Append sentiment icon to new cell
cellData.appendChild(sentimentIcon);
}
if (productivity != null) {
// Add productivity icon
let productivityIcon = document.createElement("img");
productivityIcon.src = `../icons/${PRODUCTIVITY_FILES_NAMES[productivity - 1 - 5]}`;
productivityIcon.alt = "productivity icon";
productivityIcon.className = "productivity-icon";
// Append sentiment icon to new cell
cellData.appendChild(productivityIcon);
}
// Add tasklist in calendar cell
// Create tasklist div
let taskDiv = document.createElement("div");
taskDiv.className = "task-div";
// Create unordered list
let taskList = document.createElement("ul");
taskList.className = "task-ul";
if (tasks != null) {
for (let i = 0; i < tasks.length && i < DISPLAY_TASK_COUNT; i++) {
let taskItem = document.createElement("li");
taskItem.textContent = tasks[i]["text"];
taskItem.className = "task-item";
taskItem.style.setProperty('--task-color', tasks[i]["color"]);
taskList.appendChild(taskItem);
}
if (tasks.length > DISPLAY_TASK_COUNT) {
// Handle extra tasks in calendar view
let taskExtra = document.createElement("li");
taskExtra.textContent = `${tasks.length - DISPLAY_TASK_COUNT} more tasks`;
taskExtra.className = "task-indicator";
taskList.appendChild(taskExtra);
}
}
// Append taskList to task div;
taskDiv.appendChild(taskList);
// Append tasklist div to new cell
cellData.appendChild(taskDiv);
// Create buttons that link to speciic homepage and extract selected date
let aLink = document.createElement("a");
let dayLink = currCalendarMonth.getDate();
let monthLink = currCalendarMonth.getMonth();
let yearLink = currCalendarMonth.getFullYear()
// Query is in format ?date=month-day-year
aLink.href = `../homepage/homepage.html?date=${monthLink}-${dayLink}-${yearLink}`;
aLink.className = "a-link";
aLink.setAttribute("aria-label", `Link to details for ${monthLink + 1}/${dayLink}/${yearLink}`);
cellData.appendChild(aLink);
}
/**
* Generates a dropdown for year and month selection.
*
* @param {number} startYear - The start year for the dropdown range.
* @param {number} endYear - The end year for the dropdown range.
*/
function displayJump(startYear, endYear) {
// Years
let yearDropdown = document.getElementById("year-dropdown")
// Loop through year range and append to list
for (let yr = startYear; yr < endYear + 1; yr++) {
let yearJump = document.createElement("button");
yearJump.value = yr;
yearJump.textContent = yr;
yearJump.className = "year-btn";
yearDropdown.appendChild(yearJump);
}
// Months
let allMonths = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
// Loop through months and append to list
let monthDropdown = document.getElementById("month-dropdown")
for (let mnth = 0; mnth < 12; mnth++) {
let monthJump = document.createElement("button");
monthJump.value = mnth;
monthJump.textContent = allMonths[parseInt(mnth, 10)];
monthJump.className = "month-btn";
monthDropdown.appendChild(monthJump);
}
}
/**
* Function to jump to a specific month and year.
*
* @param {number} mnth - month to jump to
* @param {number} yr - year to jump to
*/
function jump(mnth, yr) {
currDate = new Date(yr, mnth)
updateDateGlobals();
displayCalendar();
}
/**
* Creates header of the calendar.
*/
function calendarHeader(){
// Initialize list of days of the week
let allDays = ["Sun", "Mon", "Tue", "Wed","Thu", "Fri", "Sat"];
// Header of Days of the week
let thead = document.getElementById("thead-weekheadings");
let headerRow = document.createElement("tr");
// Loop through allDays list and append day of week to row
for (let dow of allDays) {
let headerData = document.createElement("th");
headerData.textContent = dow;
headerRow.appendChild(headerData);
}
thead.appendChild(headerRow);
}
/**
* Adjusts the month header text based on the window width.
*/
function windowWidth() {
if (window.innerWidth < 920) {
// Initialize list of abbreviated months
let allMonths = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sept", "Oct", "Nov", "Dec"
];
let monthHeader = document.getElementById("month");
monthHeader.textContent = allMonths[parseInt(month, 10)];
}
else {
// Initialize list of months
let allMonths = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
let monthHeader = document.getElementById("month");
monthHeader.textContent = allMonths[parseInt(month, 10)];
}
// Get the width of month and align the year
let monthWidth = document.getElementById('month-dropdown').offsetWidth;
document.getElementById('year-dropdown').style.left = monthWidth + 5 + 'px';
}
/* ********** Storage and Population ********** */
// Get the all relevent elements from page
const tasks = document.querySelector(".task-container");
// Save journal entry and tasks to local storage on page unload
window.onbeforeunload = function () {
saveTasks()
}
/**
* Save journal entry to local storage.
*
* @param {string} data - Journal entry text in parsed json format.
* @param {string} dateText - Date of the journal entry in locale date string format.
* @param {string} key - Key to store the value under.
* @param {string} value - Value to store.
*/
function saveToStorage(data, dateText, key, value) {
if (!(dateText in data)) {
data[dateText] = {}
}
data[dateText][key] = value;
}
/**
* Load journal entry from local storage.
*
* @param {Object} data - Journal entry text in parsed JSON format.
* @param {string} dateText - Date of the journal entry in locale date string format.
* @param {string} key - Key to get the value from.
* @returns {string|null} - Value of the key in the data or null if not found.
*/
function loadFromStorage(data, dateText, key) {
if (!(dateText in data)) {
return null;
}
return data[dateText][key];
}
/**
* Get journal entry from local storage.
*
* @returns {Object} - Journal entry text in parsed json format.
*/
function getJournal() {
let data = JSON.parse(localStorage.getItem("journals"))
if (data == null) {
data = {}
}
return data;
}
/**
* Save tasks to local storage.
*/
function saveTasks() {
let tasks = [];
document.querySelectorAll('.task-container li').forEach(task => {
let taskName = task.querySelector('.task-input').textContent;
let taskColor = task.style['background-color']
tasks.push({
text: taskName,
color: taskColor,
});
});
localStorage.setItem("tasks", JSON.stringify(tasks));
}
/**
* Get tasks from local storage.
*
* @returns {string} - Tasks in parsed json format or empty array if no tasks.
*/
function getTasks() {
let storedTasks = localStorage.getItem("tasks");
return storedTasks ? JSON.parse(storedTasks) : [];
}
/**
* Load tasks from local storage.
*/
function loadTasks() {
let tasks = getTasks();
if (tasks.length > 0) {
tasks.forEach(task => {
let curLi = addTask(true);
curLi.querySelector(".task-input").textContent = task['text']
curLi.style['background-color'] = task['color']
});
}
}
// Save journal entry and tasks to local storage on events.
tasks.addEventListener("blur", saveTasks)
tasks.addEventListener("change", saveTasks)