diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..d321afb4 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 4471f667..26a19539 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,129 @@ -Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js -=== - -Due: September 9th, by 11:59 AM. - -This assignment will introduce you to creating a prototype two-tiered web application. -Your application will include the use of HTML, CSS, JavaScript, and Node.js functionality, with active communication between the client and the server. - -Baseline Requirements ---- - -There are a range of application areas and possibilities that meet these baseline requirements. -Try to make your application do something useful! A todo list, storing / retrieving high scores for a very simple game... have a little fun with it. - -Your application is required to implement the following functionalities: - -- a `Server` which not only serves files, but also maintains a tabular dataset with 3 or more fields related to your application -- a `Results` functionality which shows the entire dataset residing in the server's memory -- a `Form/Entry` functionality which allows a user to add or delete data items residing in the server's memory -- a `Server Logic` which, upon receiving new or modified "incoming" data, includes and uses a function that adds at least one additional derived field to this incoming data before integrating it with the existing dataset -- the `Derived field` for a new row of data must be computed based on fields already existing in the row. -For example, a `todo` dataset with `task`, `priority`, and `creation_date` may generate a new field `deadline` by looking at `creation_date` and `priority` - -Your application is required to demonstrate the use of the following concepts: - -HTML: -- One or more [HTML Forms](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms), with any combination of form tags appropriate for the user input portion of the application -- A results page displaying all data currently available on the server. You will most likely use a `` tag for this, but `
+ + + + + + + + + + + +
TaskPriorityDeadlineActions
+ + + diff --git a/public/js/main.js b/public/js/main.js deleted file mode 100644 index a569258f..00000000 --- a/public/js/main.js +++ /dev/null @@ -1,27 +0,0 @@ -// FRONT-END (CLIENT) JAVASCRIPT HERE - -const submit = async function( event ) { - // stop form submission from trying to load - // a new .html page for displaying results... - // this was the original browser behavior and still - // remains to this day - event.preventDefault() - - const input = document.querySelector( '#yourname' ), - json = { yourname: input.value }, - body = JSON.stringify( json ) - - const response = await fetch( '/submit', { - method:'POST', - body - }) - - const text = await response.text() - - console.log( 'text:', text ) -} - -window.onload = function() { - const button = document.querySelector("button"); - button.onclick = submit; -} \ No newline at end of file diff --git a/public/script.js b/public/script.js new file mode 100644 index 00000000..e4a9400b --- /dev/null +++ b/public/script.js @@ -0,0 +1,121 @@ +document.addEventListener('DOMContentLoaded', () => { + const form = document.getElementById('todo-form'); + const todoTableBody = document.getElementById('todo-table-body'); + + // Function to fetch and display all todos + const fetchTodos = () => { + fetch('/todos') + .then(response => response.json()) + .then(data => { + renderTodos(data); + }) + .catch(err => console.error('Error fetching todos:', err)); + }; + + // Handle form submission and adding a new task + form.addEventListener('submit', (e) => { + e.preventDefault(); + const task = document.getElementById('task').value; + const priority = document.getElementById('priority').value; + const deadline = document.getElementById('deadline').value; + + fetch('/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ task, priority, deadline }), + }) + .then(response => response.json()) + .then(data => { + form.reset(); + renderTodos(data); // Refresh the todo list with the updated data + }) + .catch(err => console.error('Error adding task:', err)); + }); + + // Delete a task and refresh the UI + window.deleteTask = (id) => { + const confirmed = confirm('Are you sure you want to delete this task?'); + if (!confirmed) return; + + fetch(`/delete/${id}`, { + method: 'DELETE', + }) + .then(response => response.json()) + .then(data => { + renderTodos(data); // Refresh the todo list after deletion + }) + .catch(err => console.error('Error deleting task:', err)); + }; + + // Edit a task + window.editTask = (id) => { + const task = prompt('Enter new task:'); + const priority = prompt('Enter new priority (1-5):'); + const deadline = prompt('Enter new deadline (yyyy-mm-dd):'); + + fetch(`/update/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ task, priority, deadline }), + }) + .then(response => response.json()) + .then(data => { + renderTodos(data); // Refresh the todo list after editing + }) + .catch(err => console.error('Error editing task:', err)); + }; + + // Function to render the list of todos in table format + const renderTodos = (todos) => { + todoTableBody.innerHTML = ''; // Clear the table body + todos.forEach(todo => { + const tr = document.createElement('tr'); + + // Create table cells for task, priority, deadline, and actions + tr.innerHTML = ` + ${todo.task} + ${todo.priority} + ${todo.deadline} + + + + + `; + + // Add initial fade-in class + tr.classList.add('fade-in'); + + // Set a timeout to add the 'show' class after the row is inserted into the DOM + setTimeout(() => { + tr.classList.add('show'); + }, 100); + + // Add class based on priority for color + tr.classList.add(`priority-${todo.priority}`); + + // Check if the deadline is within a week and make text bold + if (isDeadlineSoon(todo.deadline)) { + tr.classList.add('deadline-soon'); + } + + todoTableBody.appendChild(tr); + }); + }; + + // Function to check if a deadline is within the next 7 days + const isDeadlineSoon = (deadline) => { + const today = new Date(); + const deadlineDate = new Date(deadline); + const timeDiff = deadlineDate - today; + const daysDiff = timeDiff / (1000 * 60 * 60 * 24); // Convert to days + + return daysDiff <= 7; + }; + + // Fetch all todos on initial load + fetchTodos(); +}); diff --git a/public/style.css b/public/style.css new file mode 100644 index 00000000..564f5931 --- /dev/null +++ b/public/style.css @@ -0,0 +1,102 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 20px; + background-color: black; /* Set background color to black */ + /* color: orange; /* Default text color for the entire page */ +} + +h1, h2 { + color: orange; /* Keep headings orange */ +} + +form { + margin-bottom: 20px; +} + +input[type="text"], input[type="number"], input[type="date"] { + padding: 10px; + margin-right: 10px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: black; /* Set input background to black */ + color: orange; /* Set input text color to orange */ +} + +button { + padding: 10px 20px; + background-color: #28a745; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +button:hover { + background-color: #218838; +} + +/* Task Table Styles */ +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; +} + +th{ + padding: 10px; + text-align: left; + border: 1px solid #ccc; + background-color: black; /* Keep table background black */ + color: orange; /* Default text color for non-task elements */ +} + +td { + padding: 10px; + text-align: left; + border: 1px solid #ccc; + /* background-color: black; /* Keep table background black */ + /* color: orange; /* Default text color for non-task elements */ +} + +/* Fade-in effect */ +.fade-in { + opacity: 0; + transition: opacity 1s ease-in-out; +} + +.fade-in.show { + opacity: 1; +} + +/* Priority-based colors for rows */ +/* These will override the default orange text color */ +.priority-1 { + background-color: white; /* White background */ + color: black; /* Black text */ +} + +.priority-2 { + background-color: purple; /* Purple background */ + color: white; /* White text */ +} + +.priority-3 { + background-color: pink; /* Pink background */ + color: black; /* Black text */ +} + +.priority-4 { + background-color: blue; /* Blue background */ + color: white; /* White text */ +} + +.priority-5 { + background-color: red; /* Red background */ + color: white; /* White text */ +} + +/* Bold text for tasks due in a week or less */ +.deadline-soon { + font-weight: bold; +} diff --git a/server.js b/server.js index 9ac27fb8..344f24cb 100644 --- a/server.js +++ b/server.js @@ -1,74 +1,125 @@ -const http = require( 'http' ), - fs = require( 'fs' ), - // IMPORTANT: you must run `npm install` in the directory for this assignment - // to install the mime library if you're testing this on your local machine. - // However, Glitch will install it automatically by looking in your package.json - // file. - mime = require( 'mime' ), - dir = 'public/', - port = 3000 - -const appdata = [ - { 'model': 'toyota', 'year': 1999, 'mpg': 23 }, - { 'model': 'honda', 'year': 2004, 'mpg': 30 }, - { 'model': 'ford', 'year': 1987, 'mpg': 14} -] - -const server = http.createServer( function( request,response ) { - if( request.method === 'GET' ) { - handleGet( request, response ) - }else if( request.method === 'POST' ){ - handlePost( request, response ) - } -}) - -const handleGet = function( request, response ) { - const filename = dir + request.url.slice( 1 ) - - if( request.url === '/' ) { - sendFile( response, 'public/index.html' ) - }else{ - sendFile( response, filename ) - } -} - -const handlePost = function( request, response ) { - let dataString = '' - - request.on( 'data', function( data ) { - dataString += data - }) - - request.on( 'end', function() { - console.log( JSON.parse( dataString ) ) - - // ... do something with the data here!!! - - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) - response.end('test') - }) -} - -const sendFile = function( response, filename ) { - const type = mime.getType( filename ) - - fs.readFile( filename, function( err, content ) { - - // if the error = null, then we've loaded the file successfully - if( err === null ) { - - // status code: https://httpstatuses.com - response.writeHeader( 200, { 'Content-Type': type }) - response.end( content ) - - }else{ - - // file not found, error code 404 - response.writeHeader( 404 ) - response.end( '404 Error: File Not Found' ) - - } - }) -} - -server.listen( process.env.PORT || port ) +const express = require('express'); +const app = express(); + + +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const PORT = process.env.PORT || 3000; + +const server = http.createServer(function (request, response) { + let filePath = '.' + request.url; + if (filePath === './') { + filePath = './index.html'; + } + + const extname = String(path.extname(filePath)).toLowerCase(); + const mimeTypes = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpg', + '.gif': 'image/gif', + '.wav': 'audio/wav', + '.mp4': 'video/mp4', + '.woff': 'application/font-woff', + '.ttf': 'application/font-ttf', + '.eot': 'application/vnd.ms-fontobject', + '.otf': 'application/font-otf', + '.svg': 'application/image/svg+xml', + }; + + const contentType = mimeTypes[extname] || 'application/octet-stream'; + + fs.readFile(filePath, function (error, content) { + if (error) { + if (error.code == 'ENOENT') { + response.writeHead(404, { 'Content-Type': 'text/html' }); + response.end('

404 Not Found

', 'utf-8'); + } else { + response.writeHead(500); + response.end('Sorry, check with the site admin for error: ' + error.code + ' ..\n'); + } + } else { + response.writeHead(200, { 'Content-Type': contentType }); + response.end(content, 'utf-8'); + } + }); + }); + + + + + + + + + + + + + +app.use(express.json()); +app.use(express.static(__dirname + '/public')); + +let todos = []; + +// Helper function to generate a deadline based on priority if not provided +const generateDeadline = (priority) => { + const date = new Date(); + date.setDate(date.getDate() + (priority * 2)); + return date.toISOString().split('T')[0]; +}; + +// Get all todos +app.get('/todos', (req, res) => { + res.json(todos); +}); + +// Add a new todo +app.post('/add', (req, res) => { + const { task, priority, deadline } = req.body; + const id = Date.now().toString(); + const generatedDeadline = deadline || generateDeadline(priority); + + const newTodo = { id, task, priority: parseInt(priority), deadline: generatedDeadline }; + todos.push(newTodo); + todos = sortTodos(todos); // Sort after adding + res.json(todos); // Return updated todo list +}); + +// Delete a todo +app.delete('/delete/:id', (req, res) => { + const { id } = req.params; + todos = todos.filter(todo => todo.id !== id); + todos = sortTodos(todos); // Sort after deletion + res.json(todos); // Return updated todo list +}); + +// Update an existing todo +app.put('/update/:id', (req, res) => { + const { id } = req.params; + const { task, priority, deadline } = req.body; + + todos = todos.map(todo => + todo.id === id ? { ...todo, task, priority: parseInt(priority), deadline } : todo + ); + todos = sortTodos(todos); // Sort after editing + res.json(todos); // Return updated todo list +}); + +// Function to sort todos based on priority and deadline +const sortTodos = (todos) => { + return todos.sort((a, b) => { + if (a.priority !== b.priority) { + return b.priority - a.priority; // Higher priority first + } + return new Date(a.deadline) - new Date(b.deadline); // Earlier deadline first + }); +}; + +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`); +});