version 1.0.0 commit

This commit is contained in:
Soulful Sailer
2026-03-04 21:00:14 -06:00
parent 13061a389f
commit eb0916d90a
17 changed files with 1941 additions and 0 deletions

53
static/event.html Normal file
View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" content="width=device-width, initial-scale=1" name="viewport"/>
<title>Event Tracker - Editor</title>
<link rel="stylesheet" href="styles/main.css"/>
<link rel="stylesheet" href="styles/event.css"/>
<script type='text/javascript' src='scripts/getDateTime.js'></script>
<script type='text/javascript' src='scripts/fetchFile.js'></script>
<script type='text/javascript' src='scripts/editEvent.js'></script>
</head>
<body>
<div id='page-container'>
<div id='header'>
<div id='top-bar'>
<span class='link'><a id='home-link' href='index.html'><h1>Event Tracker</h1></a></span>
</div>
<div class='links'>
<li class=''><span class='link'><a href='index.html'>View</a></span></li>
<li class='current-page'><span class='link'><a href='event.html'>Add</a></span></li>
</div>
</div>
<div id='body-container'>
<form id='event-form' action="/submit-event" method="post" enctype="application/x-www-form-urlencoded">
<input type="hidden" id="event-id" name="event-id" value="-1" />
<label for="name">Event Name: </label>
<input type="text" name="name" id="name" required />
<div id="section">
<input type="radio" name="section" id="proposed" value="proposed" required />
<label for="proposed">Proposed</label>
<input type="radio" name="section" id="accepted" value="accepted"/>
<label for="proposed">Accepted</label>
</div>
<label for="startTime">Start Time: </label>
<input type="datetime-local" name="startTime" id="startTime" class="event-datetime" required />
<label for="endTime">End Time: </label>
<input type="datetime-local" name="endTime" id="endTime" class="event-datetime" required />
<label for="people">People Involved: </label>
<input type="text" name="people" id="people"/>
<label for="desc">Description: </label>
<input type="text" name="desc" id="desc"/>
<input type="submit" id='submit' value="Save"/>
</form>
</div>
<footer class='page-footer'>
<div class='links'>
<li><span class='link'><a href='https://github.com/Soulful-Sailer/Event-Tracker'>Source Code</a></span></li>
</div>
</footer>
</div>
</body>
</html>

41
static/index.html Normal file
View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" content="width=device-width, initial-scale=1" name="viewport"/>
<title>Event Tracker - View</title>
<link rel="stylesheet" href="styles/main.css"/>
<link rel="stylesheet" href="styles/home.css"/>
<script type='text/javascript' src='scripts/getDateTime.js'></script>
<script type='text/javascript' src='scripts/fetchFile.js'></script>
<script type='text/javascript' src='scripts/EventItem.js'></script>
<script type='text/javascript' src='scripts/loadEvents.js'></script>
</head>
<body>
<div id='page-container'>
<div id='header'>
<div id='top-bar'>
<span class='link'><a id='home-link' href='index.html'><h1>Event Tracker</h1></a></span>
</div>
<div class='links'>
<li class='current-page'><span class='link'><a href='index.html'>View</a></span></li>
<li class=''><span class='link'><a href='event.html'>Add</a></span></li>
</div>
</div>
<div id='body-container'>
<h2>Accepted Plans</h2>
<div id='accepted' class='event-section'>
</div>
<h2>Proposed Plans</h2>
<div id='proposed' class='event-section'>
</div>
</div>
<footer class='page-footer'>
<div class='links'>
<li><span class='link'><a href='https://github.com/Soulful-Sailer/Event-Tracker'>Source Code</a></span></li>
</div>
</footer>
</div>
</body>
</html>

153
static/scripts/EventItem.js Normal file
View File

@@ -0,0 +1,153 @@
class EventItem {
constructor(eventObject) {
this.id = eventObject["id"];
this.name = eventObject["name"];
this.startTime = new Date(Date.parse(eventObject["starttime"]));
this.endTime = new Date(Date.parse(eventObject["endtime"]));
this.people = eventObject["people"];
this.desc = eventObject["description"];
this.constructElem();
}
editEvent() {
window.location.replace(`./event.html?id=${this.id}`);
}
/**
* Creates the HTML Element for this Event using the standard format
*
* <div id='event-#' class='event'>
* <span class='event-topLine'>
* <h3>Example Event</h3>
* <span class='event-buttons'>
* <button class='event-edit'>Edit</button>
* </span>
* </span>
* <this.sameDayElem() or this.bothDaysElem()>
* <li class='event-people'>People Involved</li>
* <li class='event-desc'>Description</li>
* </div>
*/
constructElem() {
this.elem = document.createElement("div");
this.elem.id = `event-${this.id}`;
this.elem.classList.add("event");
//top line includes event name and button section
let topLine = document.createElement("span");
topLine.classList.add("event-topLine");
//event name
let title = document.createElement("h3");
title.innerText = this.name;
topLine.append(title);
//button section as edit button
let buttons = document.createElement("span");
buttons.classList.add("event-buttons");
//edit button
let editButton = document.createElement("button");
editButton.classList.add("event-edit");
editButton.innerText = "Edit";
buttons.append(editButton);
topLine.append(buttons);
this.elem.append(topLine);
//topLine end
//datetime includes event date and time
//if start and end time are less than 24h apart, only show the day for the start time
if ((this.endTime.getTime() - this.startTime.getTime()) / 3600000 < 24)
this.elem.append(this.sameDayElem());
else
this.elem.append(this.bothDaysElem());
//people involved in event
let eventPeople = document.createElement("li");
eventPeople.classList.add("event-people");
eventPeople.innerText = this.people;
this.elem.append(eventPeople);
//description of event
let eventDesc = document.createElement("li");
eventDesc.classList.add("event-desc");
eventDesc.innerText = this.desc;
this.elem.append(eventDesc);
//elem end
editButton.addEventListener("click", () => this.editEvent());
}
/**
* Creates the HTML Element for datetime section of the Event using the standard format
* When only the day is shown for the start time
*
* <li class='event-datetime'>
* DayOfWeek, Month ##<br/>
* <time datetime='YYYY-MM-DD HH:MM'>HH:MM AM/PM</time> - <time datetime='YYYY-MM-DD HH:MM'>HH:MM AM/PM</time>
* </li>
*
* @returns {HTMLElement} datetime - li that contains start date and time info
*/
sameDayElem() {
let datetime = document.createElement("li");
datetime.classList.add = "event-datetime";
//Day for starting time
datetime.append(getDay(this.startTime));
//linebreak
datetime.append(document.createElement('br'));
//starting time within HTML timestamp
let start = document.createElement("TIME");
start.setAttribute("datetime", this.startTime.toISOString().slice(0, 16).replace('T', ' '));
start.innerText = getTime(this.startTime);
datetime.append(start);
//seperator
datetime.append(" - ");
//ending time within HTML timestamp
let end = document.createElement("TIME");
end.setAttribute("datetime", this.endTime.toISOString().slice(0, 16).replace('T', ' '));
end.innerText = getTime(this.endTime);
datetime.append(end);
return datetime;
}
/**
* Creates the HTML Element for datetime section of the Event using the standard format
* When the day is shown for both start and end times
*
* <li class='event-datetime'>
* <time datetime='2023-04-28 21:30'>Friday, April 28 9:30 PM</time><br/>
* <time datetime='2023-04-29 22:30'>Saturday, April 28 10:30 PM</time>
* </li>
*
* @returns {HTMLElement} datetime - li that contains end date and time info
*/
bothDaysElem() {
let datetime = document.createElement("li");
datetime.classList.add("event-datetime");
//starting day and time within HTML timestamp
let start = document.createElement("TIME");
start.setAttribute("datetime", this.startTime.toISOString().slice(0, 16).replace('T', ' '));
start.innerText = `${getDay(this.startTime)} ${getTime(this.startTime)}`;
datetime.append(start);
//linebreak
datetime.append(document.createElement('br'));
//ending day and time within HTML timestamp
let end = document.createElement("TIME");
end.setAttribute("datetime", this.endTime.toISOString().slice(0, 16).replace('T', ' '));
end.innerText = `${getDay(this.endTime)} ${getTime(this.endTime)}`;
datetime.append(end);
return datetime;
}
}

110
static/scripts/editEvent.js Normal file
View File

@@ -0,0 +1,110 @@
let newEvent = true; //false if editing exisiting event
let currentURL = new URL(window.location.href);
let id = currentURL.searchParams.get("id"); //id of event being edited
/** load event data **/
/**
* Checks if we are editing event, and sets up page for it if so
*/
function editEvent() {
if (id) {
fetchEventData(id, populateData);
newEvent = false;
indicateEditing();
}
}
/**
* requests data from server about an event with specified id
* @param {number} id - id of event
* @param {function} callback
* @returns {Object} eventData - data about requested event
*/
function fetchEventData(id, callback) {
fetchFile("event.json", "json", undefined, { "id": id }).then((eventData) => {
//If server replies with requested data, return it. Else, alert user
if (eventData) {
callback(eventData);
}
else {
alert("Could not retrieve event data");
window.location.replace("./index.html");
}
});
}
/**
* populates data about the event into the form's fields
* @param {Object} eventData - data about requested event
*/
function populateData(eventData) {
document.getElementById("event-id").value = id;
for (let item in eventData) {
//the key for each item should equal the id of the field
let itemElem = document.getElementById(item);
if (itemElem) {
//map data to expected value for that field type
switch (itemElem.type) {
case "datetime-local":
let datetime = new Date(Date.parse(eventData[item]));
datetime = datetime.toISOString().slice(0, 16);
itemElem.defaultValue = datetime;
break;
default:
itemElem.defaultValue = eventData[item];
}
}
else console.log("Could not find field " + item);
}
//set section radio button in included
if (eventData["section"]) document.getElementById(eventData["section"]).checked = true;
}
/**
* Adds an item on the navigation bar to show the user is editing an event instead of adding one
*/
function indicateEditing() {
document.querySelector(".current-page").classList.remove('current-page');
//creates html element:
//<li class='current-page'><a href='event.html?id=EventID'><span class='link'>Editing</span></a></li>
let elem = document.createElement("li");
elem.classList.add('current-page');
let a = document.createElement('a');
a.href = window.location.href;
let span = document.createElement('span');
span.classList.add('link');
span.append('Editing');
a.append(span);
elem.append(a);
document.getElementById("links").append(elem); //add elem to navigation bar
//creates delete button
//<button id="delete-button">Delete</button>
let deleteButton = document.createElement("button");
deleteButton.id = "delete-button";
deleteButton.innerText = "Delete";
deleteButton.addEventListener("click", deleteEvent);
document.getElementById("body-container").append(deleteButton);
}
/**
* Sents post request to delete current event
*/
function deleteEvent() {
console.log(id);
if (confirm("Delete this event?")) {
postData("delete-event.json",
JSON.stringify({"id":id}),
"application/json")
.then(res => {
if (!res || !res.ok) alert("delete failed");
else window.location.replace("./index.html");
});
}
}
document.addEventListener("DOMContentLoaded", editEvent);

View File

@@ -0,0 +1,73 @@
let eventServer = new URL("http://" + window.location.host);
/**
* sends the specified GET request to the server and returns the response
* @param {string} filePath - path to requested file on server
* @param {string} dataType - type of data requested (text, json, blob)
* @param {Object} [headers] - request headers if needed
* @param {Object} [parameters] - Search Parameters
* @returns {Response} response - data returned by the server, decoded as indended
*/
function fetchFile(filePath, dataType, headers={}, parameters={}) {
//setup URL
let fileURL = eventServer;
fileURL.pathname = `/${filePath}`;
for (p in parameters) fileURL.searchParams.append(p, parameters[p]);
headers["method"] = "GET";
return fetch(fileURL, headers)
.then(response => {
//If server replies with requested file, return it. Else log HTTP error
if (response.ok) {
//decode the response as indended
switch (dataType) {
case "text": return response.text().then(data => { return data }); break;
case "json": return response.json().then(data => { return data }); break;
case "blob": return response.blob().then(data => { return data }); break;
}
}
else {
console.log(`HTTP-Error: ${events.status}\n${fileURL}`);
return undefined;
}
})
//if request is unable to be sent, log error
.catch(error => {
console.log(error.message);
return undefined;
});
}
/**
* sends specified data as a POST request to the server
* @param {string} filePath - path to requested file on server
* @param {string} data - stringified data to be sent to server
* @param {string} contentType - content type header for POST request
* @param {Object} [headers] - request headers if needed
* @returns {Response} response - server's response to the request
*/
function postData(filePath, data, contentType, headers={}) {
//setup URL
let fileURL = eventServer;
fileURL.pathname = `/${filePath}`;
headers["method"] = "POST";
headers["headers"] = {"Content-Type" : contentType};
headers["body"] = data;
return fetch(fileURL, headers)
.then(response => {
//return the server's response, log if there is an HTTP error
if (response.ok)
return response;
else {
console.log(`HTTP-Error: ${response.status}\n${fileURL}`);
return response;
}
})
//if request is unable to be sent, log error
.catch(error => {
console.log(error.message);
return undefined;
});
}

View File

@@ -0,0 +1,98 @@
/**
* returns string of Date object in format: HH:MM AM/PM
* @param {Date} time
* @returns {string} time - HH:MM AM/PM
*/
function getTime(time) {
let hours = time.getHours();
let meridiem;
if (hours > 12) {
hours -= 12;
meridiem = "PM";
}
else {
meridiem = "AM";
}
return `${hours}:${time.getMinutes()} ${meridiem}`;
}
/**
* returns string of Date object in format: DayOfWeek, Month DD
* @param {Date} time
* @returns {string} date - DayOfWeek, Month DD
*/
function getDay(time) {
let day;
switch(time.getDay()) {
case 0:
day = 'Sunday';
break;
case 1:
day = 'Monday';
break;
case 2:
day = 'Tuesday';
break;
case 3:
day = 'Wednesday';
break;
case 4:
day = 'Thursday';
break;
case 5:
day = 'Friday';
break;
case 6:
day = 'Saturday';
break;
default:
day = 'Invalid';
}
day = day + ', ';
switch(time.getMonth()) {
case 0:
day = day + "January";
break;
case 1:
day = day + "February";
break;
case 2:
day = day + "March";
break;
case 3:
day = day + "April";
break;
case 4:
day = day + "May";
break;
case 5:
day = day + "June";
break;
case 6:
day = day + "July";
break;
case 7:
day = day + "August";
break;
case 8:
day = day + "September";
break;
case 9:
day = day + "October";
break;
case 10:
day = day + "November";
break;
case 11:
day = day + "December";
break;
default:
day = day + "Invalid";
}
day = day + " " + time.getDate();
return day;
}

View File

@@ -0,0 +1,29 @@
/**
* Retrieves the list of events from the server and adds them respective to their section
*/
function loadEvents() {
fetchFile("events.json", "json").then((events) => {
//if server replies with requested data
if (events) {
for (let header in events) {
events[header].forEach((eventData) => {
addEvent(eventData, header);
});
}
}
else console.log("Could not retrieve event data");
});
}
/**
* Adds event to specified header via creating a new instance of EventItem
* @param {Object} eventData - contains required information about the event
* @param {string} header - specifies under which section to add the new event item
*/
function addEvent(eventData, header) {
let item = new EventItem(eventData);
document.getElementById(header).append(item.elem);
}
document.addEventListener("DOMContentLoaded", loadEvents);

27
static/styles/event.css Normal file
View File

@@ -0,0 +1,27 @@
#body-container {
margin-top: 20px;
width: 350px;
}
#event-form {
display: flex;
flex-direction: column;
}
#event-form input, #delete-button {
margin-bottom: 10px;
padding: 5px;
border: none;
color: var(--main-color);
background-color: var(--background-accent);
}
#delete-button {
width: 100%;
}
#submit:active, #delete-button:active {
background-color: var(--main-color);
color: var(--background-color);
}

44
static/styles/home.css Normal file
View File

@@ -0,0 +1,44 @@
.event-section {
display: flex;
flex-wrap: wrap;
}
/**
* Events
*/
.event {
margin: 10px;
width: 300px;
padding: 10px;
display: flex;
flex-direction: column;
list-style-type: none;
border: var(--main-color) 2px solid;
}
.event h3 {
margin: 0;
}
.event li {
border-top: var(--main-color) 1px solid;
padding: 5px 0;
}
.event-topLine {
display: flex;
justify-content: space-between;
padding-bottom: 5px;
}
.event-topLine button {
background-color: var(--background-color);
border: var(--main-color) 1px solid;
color: inherit;
}
.event-topLine button:active {
background-color: var(--main-color);
color: var(--background-color);
}

99
static/styles/main.css Normal file
View File

@@ -0,0 +1,99 @@
:root {
--main-color: white;
--background-color: #333A51;
--background-accent: #315378;
}
* {
box-sizing: border-box;
}
body {
color: var(--main-color);
background-color: var(--background-color);
margin: 0;
}
#page-container {
box-sizing: border-box;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.links {
background-color: var(--background-accent);
list-style-type: none;
font-size: 1.1em;
display: flex;
justify-content: flex-start;
}
.links.link {
height: 100%;
padding: 5px 10px;
display: flex;
align-items: center;
justify-content: center;
}
.link {
text-decoration: none;
height: 100%;
padding: 5px 10px;
}
.link a {
text-decoration: none;
color: var(--main-color);
}
/**
* Header Section
*/
/*#header a {
text-decoration: none;
color: var(--main-color);
}*/
#top-bar {
display: flex;
justify-content: flex-start;
width: 100%;
background-color: var(--background-accent);
border-bottom: 2px solid var(--main-color);
}
#home-link {
padding: 10px;
}
#home-link h1 {
margin: 0;
}
.current-page a {
border-bottom: var(--main-color) 1px solid;
}
/**
* Body Section
*/
#body-container {
flex-basis: 100%;
margin-left: 10px;
flex-grow: 1;
}
#body-container h2 {
margin-bottom: 0;
}
.page-footer {
width: 100%;
background-color: var(--background-accent);
}