/**
* @file Contains miscellaneous utilities used throughout
*
* @author Ali Izoyev
* @author Colin Rice
* @version 1.0.0
* @module utils.js
*/
/**
* This method gets the type of schedule from whatever the user is using to add in a schedule (week or month)
* It gets the value from whatever the user has choosen, formats it to a usable date, then returned as a string.
*
* @param {HTMLElement} element In this case, the closest element with the ".generate-new-schedule" tag to insert a new table
* @param {boolean} asJSDateRange Whether to return the schedule date as a string (for table headers) or as an array of Date objects (for table creation)
* @returns {string | Date[]} Returns a string or array of Dates based on if the user chooses the week selection or the month selection
*/
function getScheduleDate(element, asJSDateRange = false)
{
// Find all of the elements in that element passed
const weekArea = element.querySelector(".week-selection");
const weekOf = element.querySelector(".week-of");
const monthArea = element.querySelector(".month-selection");
const monthChoosen = element.querySelector(".month-choosen");
// If the week is showing
if (weekArea.style.display === "flex")
{
// The week picker returns something like:
// "2026-W22"
// Split that into:
// ["2026", "22"]
const splitWeek = weekOf.value.split("-W");
// Grab the year and week number separately
const year = parseInt(splitWeek[0]);
const weekNumber = parseInt(splitWeek[1]);
//Return the schedule week as a string if needed
if (!asJSDateRange)
{
return "Week " + weekNumber + ", " + year;
}
// If we're returning a date range, find the start / end dates of the week
// Start at Jan 1st, then move forward
// by however many weeks were selected
const startDate = new Date(year, 0, 1 + (weekNumber - 1) * 7);
//Move the date back to Monday
while (startDate.getDay() !== 1)
{
startDate.setDate(startDate.getDate() - 1);
}
//End date to return is 6 days after the start
let endDate = new Date(startDate)
endDate.setDate(startDate.getDate() + 6);
return [startDate, endDate];
}
else // Else, the month is showing instead
{
// Make a Date object based on the value. split the value given into two strings (year and month)
// Due to the indexing of the months (0 - 11 instead of 1 - 12), decrement the month by 1
const selectedYear = monthChoosen.value.substring(0, 4);
const selectedMonth = monthChoosen.value.substring(5) - 1;
const monthStartDate = new Date(selectedYear, selectedMonth);
if (!asJSDateRange)
{
// Format the month
const option = {month: "long", year: "numeric"};
// Return that month Date object as a string and formated
return monthStartDate.toLocaleDateString(
"en-US",
option
);
}
else
{
//0th day of the next month is treated as the last day of this month
const monthEndDate = new Date(selectedYear, selectedMonth + 1, 0);
return [monthStartDate, monthEndDate];
}
}
}
/**
* Creates and returns an element with the given type, id, and class.
*
* @param {string} type The type of the element. Must be a valid HTML tag
* @param {string} id The ID to apply to this element. May be an empty string / null if no ID should be applied
* @param {string} classes The classes to apply to this element. May be an empty string / null if no classes should be applied
* @param {string} text The text content to apply to this element. May be omitted if no text content should be applied
* @returns {HTMLElement} A element that contains whatever the programmer has put as the arguments during the usage
*/
function createElement(type, id, classes, text="")
{
const newElement = document.createElement(type);
// If we pass in a id or text, assign it
if (id) { newElement.id = id }
if (classes) { newElement.className = classes }
if (text) { newElement.textContent = text }
return newElement;
}
/**
* This method creates a reusable delete button element with a trash can icon. It also activates a popup behavior that is already
* in the html. When the trash can is clicked, the user will be ask to confirm deletion before removing the target element (element as parameter)
* in the DOM. If you need something else to happen when the button is clicked besides the deletion of the tag, you need to implement it inside of this function,
* inside of the event listener
*
* @param {HTMLElement} element The element that will be used for deletion when the user approves there desision.
* @returns {HTMLDivElement} A reusable div with a delete button, trash can icon, and some behavior tied to the button / trash can
*/
function generateDeleteDiv(element)
{
// Create a container (div) that will hold everything
const deleteContainer = createElement("div", undefined, "delete-container", undefined);
// Create a button that will be used for clicking
const deleteButton = createElement("button", undefined, "delete-button", undefined);
// Create a event listener for the button for clicked
deleteButton.addEventListener("click", (event) =>
{
// Using what triggered the button, we will find the closest tag given as a paramenter
// Note: The closest method goes up the DOM to find the element
const closestElement = event.target.closest(element);
// Get all of the other tags needed
const yesButton = document.querySelector(".yes-button");
const noButton = document.querySelector(".no-button");
const confirmSection = document.querySelector(".confirm-section");
const confirmText = document.querySelector(".confirm-section").querySelector("p");
const overlay = document.querySelector(".overlay");
confirmSection.style.display = "block";
overlay.style.display = "block";
if (element === "tr")
{
confirmText.textContent = "Are you sure you want to delete this row?"
}
if (element === ".section")
{
confirmText.textContent = "Are you sure you want to delete this radio station?"
}
// Event listener for the yes button
yesButton.onclick = () =>
{
// If the element given is a tr (meaning we will delete a row from a table)
if (element === "tr")
{
// Get the closest table element
const closestTable = closestElement.closest("table");
// Plus the associated ScheduleTable
const closestScheduleTable = activeScheduleTables.get(closestTable);
// If the last daypart is being removed
if (closestScheduleTable.height - 1 == 2)
{
// Remove the entire table
closestTable.parentElement.remove();
activeScheduleTables.delete(closestTable);
} else {
// Remove the row and decrease table height
closestElement.remove();
closestScheduleTable.height -= 1;
// Re-calculate the totals
closestScheduleTable.getAllTotals();
}
}
else if (element === ".section") {
// Remove the associated ScheduleTable
// .section -> .table-container -> table
const closestTable = closestElement.children[1].children[1];
activeScheduleTables.delete(closestTable);
// Then remove the section from the DOM
// TODO: recalculate running totals after section removal!
closestElement.remove();
}
else // Else, just remove the element
{
closestElement.remove();
}
// Hide the confirm and overlay elements
confirmSection.style.display = "none";
overlay.style.display = "none";
}
// If no was clicked, then just hide it
noButton.onclick = () =>
{
confirmSection.style.display = "none";
overlay.style.display = "none"
}
})
// Make a img tag with it's src to the trash can
const trashCanImage = createElement("img", undefined, "trash-can-image", undefined);
trashCanImage.src = "images/trash_can.svg";
// Append the trash can to the button and button to container
deleteButton.append(trashCanImage);
deleteContainer.append(deleteButton);
// Return the div
return deleteContainer;
}
/**
* Creates a reusable div that holds a UI image for making draggable calendar events
*
* @returns {HTMLDivElement} Container that creates draggable calendar events
*/
function generateCreateEvent()
{
// Container for the div that will hold all of the cells
const container = document.querySelector("#table-draggable-cells");
// Make a div that will hold the button and image
const eventContainer = createElement("div", undefined, "event-container", undefined);
// Make button
const button = createElement("button", undefined, "create-event-button", undefined)
// Make image and give it the src location of the image
const img = createElement("img", undefined, "calendar-add-image", undefined)
img.src = "images/calendar_add.svg"
// Append all of this to the div
button.append(img);
eventContainer.append(button);
const x = document.querySelector(".check-section");
const overlay = document.querySelector(".overlay");
// Event listener when clicked
button.addEventListener("click", (event) =>
{
const stationName = event.target.closest(".section").querySelector(".client-name");
const closestDayField = event.target.closest("tr").querySelector(".daypart-input");
const a = document.querySelector(".check-section").querySelector("p");
console.log(closestDayField);
if (stationName.value === "" || closestDayField.value === "")
{
x.style.display = "block";
overlay.style.display = "block"
if (stationName.value === "")
{
a.textContent = "The Station Name field cannot be empty!";
return;
}
if (closestDayField.value === "")
{
a.textContent = "The DayPart field cannot be empty!";
return;
}
}
// Find the closest time slot
const closestTimeSlot = event.target.closest("tr").querySelector(".daypart-input");
// Find the closest ratio station
const closestRadioStation = event.target.closest(".section").querySelector(".client-name")
// Make a div that will be a event cell
const eventCell = createElement("div", undefined, "event-cell", undefined);
// Set the inner text to the radio station name and slot
eventCell.innerText = closestRadioStation.value + " " + closestTimeSlot.value;
const getClosestScheduleButton = event.target.closest(".section").querySelector(".generate-new-schedule");
const dates = getScheduleDate(getClosestScheduleButton, true);
dates[1].setDate(dates[1].getDate() + 1)
calendar.addEvent({
title: eventCell.innerText,
start: dates[0].toISOString(),
end: dates[1].toISOString(),
}
)
// // Append the event cell into the container
// container.append(eventCell);
})
return eventContainer;
}