diff --git a/index.html b/index.html
new file mode 100644
index 0000000..6061787
--- /dev/null
+++ b/index.html
@@ -0,0 +1,206 @@
+
+
+
+
+
+Demo Budget Request
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+FY2026 Budget Request Form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select an action item for each current position in your department.
+
+
+
+
+
+
+
+
+ Employee Name |
+ Job Name |
+ FY25 Salary |
+ FTEs |
+ Fund |
+ Appropriation |
+ Cost Center |
+ Action |
+
+
+
+
+
+
+ Jane Doe |
+ Assistant Director |
+ $100,000 |
+ 1.0 |
+ 1000 |
+ 12345 |
+ 654321 |
+
+
+
+
+
+
+ |
+
+
+
+ John Doe |
+ TASS III |
+ $80,000 |
+ 1.0 |
+ 1000 |
+ 12345 |
+ 654321 |
+
+
+
+
+
+
+ |
+
+
+
+ Jane Doe |
+ Analyst |
+ $60,000 |
+ 1.0 |
+ 1000 |
+ 12345 |
+ 654321 |
+
+
+
+
+
+
+ |
+
+
+
+
+ Vacant |
+ Administrative Assistant |
+ $50,000 |
+ 1.0 |
+ 1000 |
+ 12345 |
+ 654321 |
+
+
+
+
+
+
+ |
+
+
+
+ Vacant |
+ Analyst II |
+ $80,000 |
+ 1.0 |
+ 1000 |
+ 12345 |
+ 654321 |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/js/excel-export.js b/static/js/excel-export.js
new file mode 100644
index 0000000..e69de29
diff --git a/static/js/main.js b/static/js/main.js
new file mode 100644
index 0000000..4bbb43e
--- /dev/null
+++ b/static/js/main.js
@@ -0,0 +1,199 @@
+import { saveTableData, loadTableData } from './storage-handlers.js';
+
+document.addEventListener('DOMContentLoaded', function () {
+
+ // running tallies of total spend
+ let supp = 0;
+ let current = 0;
+ let target = 240000;
+
+ // Function to format number as currency
+ const formatCurrency = (amount) => {
+ var amount = parseFloat(amount);
+ return '$' + amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
+ } ;
+
+ // Add an event listener for the save button
+ document.getElementById('save').addEventListener('click', () => saveTableData(current, supp));
+
+ // Use button to show table
+ document.getElementById('start-over-btn').addEventListener('click', showTable);
+ document.getElementById('load-saved-data-btn').addEventListener('click', function(event){
+ loadTableData(updateDisplay, formatCurrency);
+ showTable();
+ });
+
+ // unhide table
+ function showTable() {
+ var positionPage = document.getElementById('position-page');
+ positionPage.style.display = 'block';
+ document.getElementById('welcome-page').style.display = "none";
+ }
+
+ // Function to update the display of the current and supp variables
+ function updateDisplay() {
+ document.getElementById('current').textContent = formatCurrency(current);
+ document.getElementById('supps').textContent = formatCurrency(supp);
+ if(current <= target){
+ document.getElementById('current').style.color = "green";
+ }
+ if(current > target){
+ document.getElementById('current').style.color = "red";
+ }
+ }
+
+ // Function to turn the cell back into a textbox when clicked
+ function enableEditingForSalaryCell(cell) {
+ cell.onclick = function() {
+ var currentValue = cell.getAttribute('data-salary') || '';
+ var textbox = document.createElement('input');
+ textbox.type = 'text';
+ textbox.value = currentValue;
+
+ function commitAndRestoreText() {
+ var enteredValue = textbox.value;
+ cell.setAttribute('data-salary', enteredValue);
+ cell.textContent = formatCurrency(parseFloat(enteredValue));
+ updateDisplay();
+
+ // Attach the click event to revert back to textbox upon future clicks
+ cell.onclick = function() {
+ enableEditingForSalaryCell(cell);
+ };
+ }
+
+ textbox.onblur = commitAndRestoreText;
+ textbox.onkeydown = function(event) {
+ if (event.key === 'Enter') {
+ commitAndRestoreText(); // Commit the value and restore the cell text
+ textbox.blur(); // Unfocus the textbox to trigger the blur event
+ }
+ };
+
+ cell.innerHTML = ''; // Clear cell content
+ cell.appendChild(textbox); // Embed textbox
+ cell.onclick = null; // Remove click listener so it doesn't interfere while editing
+
+ textbox.focus();
+ }
+ }
+
+ // The modified addTextboxCell function
+ function addTextboxCell(cell, isSalaryCell = false) {
+ var textbox = document.createElement('input');
+ textbox.type = 'text';
+ textbox.placeholder = isSalaryCell ? 'Enter salary' : 'Type value';
+ textbox.style.width = "100%"; // Modified the width to fit the cell
+ cell.appendChild(textbox); // Add the textbox to the cell
+
+ if (isSalaryCell) {
+ cell.classList.add('salary'); // Ensure the cell has a 'salary' class
+ enableEditingForSalaryCell(cell); // Make the cell editable on click
+ textbox.onblur = function() {
+ var enteredValue = textbox.value;
+ cell.setAttribute('data-salary', enteredValue);
+ updateDisplay();
+ }
+ }
+ }
+
+ var addPositionButton = document.querySelector('.btn-add');
+ function addRow() {
+ var table = document.getElementById("employee-table");
+ var newRow = table.insertRow(-1);
+ newRow.classList.add("no-choice-yet");
+ // var newNameCell = newRow.insertCell(0);
+ // count number of table columns using jQuery
+ let cols = $("#employee-table tr th").length;
+ for (let i = 0; i < cols-1; i++) {
+ var nextCell = newRow.insertCell(i);
+ if (i === 2) { // Check if it's the third cell
+ // Pass true so that the function knows this is a salary cell
+ addTextboxCell(nextCell, true);
+ } else {
+ addTextboxCell(nextCell);
+ };
+ }
+ var lastCell = newRow.insertCell(cols-1);
+ lastCell.innerHTML = `
+
+
+
+ `;
+ }
+ addPositionButton.addEventListener('click', addRow)
+
+ document.getElementById('employee-table').addEventListener('click', function(event) {
+ // Determine what was clicked on within the table
+ var clickedElement = event.target;
+
+ // Check if a delete button was clicked
+ if (clickedElement.matches('.btn-delete')) {
+ var currentRow = clickedElement.closest('tr');
+ // get current class and update it
+ var rowClass = currentRow.className;
+ if (rowClass) {
+ currentRow.classList.remove(rowClass);
+ }
+ currentRow.classList.add("delete");
+ // update variable counters
+ const salary = parseInt(event.target.closest('tr').querySelector('.salary').getAttribute('data-salary'));
+ if (rowClass == "keep"){
+ current -= salary
+ } else if (rowClass == "supp"){
+ supp -= salary;
+ };
+ updateDisplay();
+ }
+ // Check if a supplemental button was clicked
+ else if (clickedElement.matches('.btn-supplemental')) {
+ var currentRow = clickedElement.closest('tr');
+ // get current class and update it
+ var rowClass = currentRow.className;
+ if (rowClass) {
+ currentRow.classList.remove(rowClass);
+ }
+ currentRow.classList.add("supp");
+ // change counters
+ const salary = parseInt(event.target.closest('tr').querySelector('.salary').getAttribute('data-salary'));
+ if (rowClass == "keep"){
+ current -= salary
+ };
+ if (rowClass != "supp"){
+ supp += salary;
+ };
+ updateDisplay();
+ }
+ // Check if a carryover button was clicked
+ else if (clickedElement.matches('.btn-carryover')) {
+ var currentRow = clickedElement.closest('tr');
+ // get current class and update it
+ var rowClass = currentRow.className;
+ if (rowClass) {
+ currentRow.classList.remove(rowClass);
+ }
+ currentRow.classList.add("keep");
+ // update counter
+ const salary = parseInt(event.target.closest('tr').querySelector('.salary').getAttribute('data-salary'));
+ if (rowClass == "supp"){
+ supp -= salary;
+ } ;
+ if (rowClass != "keep"){
+ current += salary;
+ } ;
+ updateDisplay();
+ }
+ });
+
+ // Download data button
+ function downloadTableAsExcel(tableId, filename) {
+ var table = document.getElementById(tableId);
+ var workbook = XLSX.utils.table_to_book(table);
+ XLSX.writeFile(workbook, filename + '.xlsx');
+ }
+ // Add an event listener for the download button
+ document.getElementById('XLSX-download').addEventListener('click', function() {
+ downloadTableAsExcel('employee-table', 'table-export');
+ });
+
+});
\ No newline at end of file
diff --git a/static/js/position-table.js b/static/js/position-table.js
new file mode 100644
index 0000000..e69de29
diff --git a/static/js/position_buttons.js b/static/js/position_buttons.js
new file mode 100644
index 0000000..4fda9f1
--- /dev/null
+++ b/static/js/position_buttons.js
@@ -0,0 +1,261 @@
+document.addEventListener('DOMContentLoaded', function () {
+
+ // running tallies of total spend
+ let supp = 0;
+ let current = 0;
+ let target = 240000;
+
+ // Function to format number as currency
+ const formatCurrency = (amount) => {
+ var amount = parseFloat(amount);
+ return '$' + amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
+ } ;
+
+ // function to save employee data in localStorage (client web cache)
+ function saveTableData() {
+ var table = document.getElementById("employee-table");
+ var rows = table.rows;
+ var tableData = [];
+
+ for (var i = 0; i < rows.length; i++) {
+ var cols = rows[i].cells;
+ var rowData = [];
+ for (var j = 0; j < cols.length; j++) {
+ // for the action buttons, save the chosen action, not the button text
+ if (cols[j].querySelector('button')) {
+ rowData.push(rows[i].className);
+ } else {
+ rowData.push(cols[j].innerText);
+ }
+ }
+ tableData.push(rowData);
+ }
+
+ // Save JSON string to localStorage
+ localStorage.setItem("employeeTableData", JSON.stringify(tableData));
+ // also save counters in sidebar
+ localStorage.setItem('current', current.toString());
+ localStorage.setItem('supp', supp.toString());
+ }
+
+ // Add an event listener for the download button
+ document.getElementById('save').addEventListener('click',saveTableData);
+
+ // Function to load saved data
+ function loadTableData() {
+ var data = localStorage.getItem("employeeTableData");
+ if (data) {
+ var tableData = JSON.parse(data);
+ var table = document.getElementById("employee-table");
+
+ // It's good practice to empty the existing table first
+ while (table.rows.length > 1) {
+ table.deleteRow(1);
+ }
+
+ // Now, add the loaded rows to the table
+ for (var i = 1; i < tableData.length; i++) {
+ var newRow = table.insertRow(-1);
+ var ncols = tableData[i].length;
+ for (var j = 0; j < ncols-1; j++) {
+ var newCell = newRow.insertCell(j);
+ newCell.innerText = tableData[i][j];
+ }
+ // Add the action buttons and load saved class
+ var lastCell = newRow.insertCell(ncols-1);
+ lastCell.innerHTML = `
+
+
+
+ `;
+ newRow.classList.add(tableData[i][ncols-1]);
+ }
+
+ //retrieve counter values
+ current = parseInt(localStorage.getItem('current'), 10);
+ console.log(current);
+ supp = parseInt(localStorage.getItem('supp'), 10);
+ updateDisplay();
+ }
+ }
+
+ // unhide table
+ function showTable() {
+ var positionPage = document.getElementById('position-page');
+ positionPage.style.display = 'block';
+ document.getElementById('welcome-page').style.display = "none";
+ }
+
+ // Use button to show table
+ document.getElementById('start-over-btn').addEventListener('click', showTable);
+ document.getElementById('load-saved-data-btn').addEventListener('click', function(event){
+ loadTableData();
+ showTable();
+ });
+
+ // Function to update the display of the current and supp variables
+ function updateDisplay() {
+ document.getElementById('current').textContent = formatCurrency(current);
+ document.getElementById('supps').textContent = formatCurrency(supp);
+ if(current <= target){
+ document.getElementById('current').style.color = "green";
+ }
+ if(current > target){
+ document.getElementById('current').style.color = "red";
+ }
+ }
+
+ // Function to turn the cell back into a textbox when clicked
+ function enableEditingForSalaryCell(cell) {
+ cell.onclick = function() {
+ var currentValue = cell.getAttribute('data-salary') || '';
+ var textbox = document.createElement('input');
+ textbox.type = 'text';
+ textbox.value = currentValue;
+
+ function commitAndRestoreText() {
+ var enteredValue = textbox.value;
+ cell.setAttribute('data-salary', enteredValue);
+ cell.textContent = formatCurrency(parseFloat(enteredValue));
+ updateDisplay();
+
+ // Attach the click event to revert back to textbox upon future clicks
+ cell.onclick = function() {
+ enableEditingForSalaryCell(cell);
+ };
+ }
+
+ textbox.onblur = commitAndRestoreText;
+ textbox.onkeydown = function(event) {
+ if (event.key === 'Enter') {
+ commitAndRestoreText(); // Commit the value and restore the cell text
+ textbox.blur(); // Unfocus the textbox to trigger the blur event
+ }
+ };
+
+ cell.innerHTML = ''; // Clear cell content
+ cell.appendChild(textbox); // Embed textbox
+ cell.onclick = null; // Remove click listener so it doesn't interfere while editing
+
+ textbox.focus();
+ }
+ }
+
+ // The modified addTextboxCell function
+ function addTextboxCell(cell, isSalaryCell = false) {
+ var textbox = document.createElement('input');
+ textbox.type = 'text';
+ textbox.placeholder = isSalaryCell ? 'Enter salary' : 'Type value';
+ textbox.style.width = "100%"; // Modified the width to fit the cell
+ cell.appendChild(textbox); // Add the textbox to the cell
+
+ if (isSalaryCell) {
+ cell.classList.add('salary'); // Ensure the cell has a 'salary' class
+ enableEditingForSalaryCell(cell); // Make the cell editable on click
+ textbox.onblur = function() {
+ var enteredValue = textbox.value;
+ cell.setAttribute('data-salary', enteredValue);
+ updateDisplay();
+ }
+ }
+ }
+
+ var addPositionButton = document.querySelector('.btn-add');
+ function addRow() {
+ var table = document.getElementById("employee-table");
+ var newRow = table.insertRow(-1);
+ // var newNameCell = newRow.insertCell(0);
+ // count number of table columns using jQuery
+ let cols = $("#employee-table tr th").length;
+ for (let i = 0; i < cols-1; i++) {
+ var nextCell = newRow.insertCell(i);
+ if (i === 2) { // Check if it's the third cell
+ // Pass true so that the function knows this is a salary cell
+ addTextboxCell(nextCell, true);
+ } else {
+ addTextboxCell(nextCell);
+ };
+ }
+ var lastCell = newRow.insertCell(cols-1);
+ lastCell.innerHTML = `
+
+
+
+ `;
+ }
+ addPositionButton.addEventListener('click', addRow)
+
+ document.getElementById('employee-table').addEventListener('click', function(event) {
+ // Determine what was clicked on within the table
+ var clickedElement = event.target;
+
+ // Check if a delete button was clicked
+ if (clickedElement.matches('.btn-delete')) {
+ var currentRow = clickedElement.closest('tr');
+ // get current class and update it
+ var rowClass = currentRow.className;
+ if (rowClass) {
+ currentRow.classList.remove(rowClass);
+ }
+ currentRow.classList.add("delete");
+ // update variable counters
+ const salary = parseInt(event.target.closest('tr').querySelector('.salary').getAttribute('data-salary'));
+ if (rowClass == "keep"){
+ current -= salary
+ } else if (rowClass == "supp"){
+ supp -= salary;
+ };
+ updateDisplay();
+ }
+ // Check if a supplemental button was clicked
+ else if (clickedElement.matches('.btn-supplemental')) {
+ var currentRow = clickedElement.closest('tr');
+ // get current class and update it
+ var rowClass = currentRow.className;
+ if (rowClass) {
+ currentRow.classList.remove(rowClass);
+ }
+ currentRow.classList.add("supp");
+ // change counters
+ const salary = parseInt(event.target.closest('tr').querySelector('.salary').getAttribute('data-salary'));
+ if (rowClass == "keep"){
+ current -= salary
+ };
+ if (rowClass != "supp"){
+ supp += salary;
+ };
+ updateDisplay();
+ }
+ // Check if a carryover button was clicked
+ else if (clickedElement.matches('.btn-carryover')) {
+ var currentRow = clickedElement.closest('tr');
+ // get current class and update it
+ var rowClass = currentRow.className;
+ if (rowClass) {
+ currentRow.classList.remove(rowClass);
+ }
+ currentRow.classList.add("keep");
+ // update counter
+ const salary = parseInt(event.target.closest('tr').querySelector('.salary').getAttribute('data-salary'));
+ if (rowClass == "supp"){
+ supp -= salary;
+ } ;
+ if (rowClass != "keep"){
+ current += salary;
+ } ;
+ updateDisplay();
+ }
+ });
+
+ // Download data button
+ function downloadTableAsExcel(tableId, filename) {
+ var table = document.getElementById(tableId);
+ var workbook = XLSX.utils.table_to_book(table);
+ XLSX.writeFile(workbook, filename + '.xlsx');
+ }
+ // Add an event listener for the download button
+ document.getElementById('XLSX-download').addEventListener('click', function() {
+ downloadTableAsExcel('employee-table', 'table-export');
+ });
+
+});
\ No newline at end of file
diff --git a/static/js/storage-handlers.js b/static/js/storage-handlers.js
new file mode 100644
index 0000000..b788c16
--- /dev/null
+++ b/static/js/storage-handlers.js
@@ -0,0 +1,65 @@
+// function to save employee data in localStorage (client web cache)
+function saveTableData(current, supp) {
+ var table = document.getElementById("employee-table");
+ var rows = table.rows;
+ var tableData = [];
+
+ for (var i = 0; i < rows.length; i++) {
+ var cols = rows[i].cells;
+ var rowData = [];
+ for (var j = 0; j < cols.length; j++) {
+ // for the action buttons, save the chosen action, not the button text
+ if (cols[j].querySelector('button')) {
+ rowData.push(rows[i].className);
+ } else {
+ rowData.push(cols[j].innerText);
+ }
+ }
+ tableData.push(rowData);
+ }
+
+ // Save JSON string to localStorage
+ localStorage.setItem("employeeTableData", JSON.stringify(tableData));
+ // also save counters in sidebar
+ localStorage.setItem('current', current.toString());
+ localStorage.setItem('supp', supp.toString());
+}
+
+
+// Function to load saved data
+function loadTableData(updateDisplay, formatCurrency) {
+ var data = localStorage.getItem("employeeTableData");
+ if (data) {
+ var tableData = JSON.parse(data);
+ var table = document.getElementById("employee-table");
+
+ // It's good practice to empty the existing table first
+ while (table.rows.length > 1) {
+ table.deleteRow(1);
+ }
+
+ // Now, add the loaded rows to the table
+ for (var i = 1; i < tableData.length; i++) {
+ var newRow = table.insertRow(-1);
+ var ncols = tableData[i].length;
+ for (var j = 0; j < ncols-1; j++) {
+ var newCell = newRow.insertCell(j);
+ newCell.innerText = tableData[i][j];
+ }
+ // Add the action buttons and load saved class
+ var lastCell = newRow.insertCell(ncols-1);
+ lastCell.innerHTML = `
+
+
+
+ `;
+ newRow.classList.add(tableData[i][ncols-1]);
+ }
+
+ //retrieve counter values
+ current = parseInt(localStorage.getItem('current'), 10);
+ console.log(current);
+ supp = parseInt(localStorage.getItem('supp'), 10);
+ updateDisplay();
+ }
+}
\ No newline at end of file
diff --git a/static/js/table-fns.js b/static/js/table-fns.js
new file mode 100644
index 0000000..e69de29
diff --git a/static/js/utils.js b/static/js/utils.js
new file mode 100644
index 0000000..e69de29
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..ec5cc42
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,108 @@
+:root {
+ --orange: #FB6523;
+ --yellow: #D99733;
+ --blue: #43748E;
+ --lightBlue: #b7c9e2;
+ --darkGray: #373A37;
+ --lightGray: #D7DAD7;
+ --white: #FBFDFB;
+ --black: #212121;
+ --green: #71BC78;
+ font-family: 'Clear Sans', 'Helvetica Neue', Arial, sans-serif;
+ font-size: 16px;
+}
+
+h1 {
+ text-align: center;
+}
+
+body {
+ margin: 20px;
+}
+
+.sidebar {
+ min-height: 100vh; /* Full height of viewport */
+}
+
+.delete {
+ background-color: #ee9286;
+}
+.delete .btn-delete, .keep .btn-carryover, .supp .btn-supplemental {
+ background-color: #373A37;
+}
+.keep {
+ background-color: #90ee90;
+}
+.supp {
+ background-color: #e0b472;
+}
+
+.btn {
+ cursor:pointer;
+ padding: 10px;
+ border-radius: 10px;
+ background-color: gray;
+ color: white;
+}
+.btn-delete {
+ background-color: var(--orange);
+ color: white;
+}
+.btn-supplemental {
+ background-color: var(--yellow);
+ color: white;
+}
+.btn-carryover {
+ background-color: var(--green);
+ color: white;
+}
+.btn-add {
+ background-color: var(--blue);
+ color: white;
+}
+
+.new-row{
+ margin: 20px;
+ text-align: center;
+}
+.next-button-row {
+ text-align: right;
+}
+
+/* Some additional styling to the table for aesthetics */
+.table-container {
+ margin-top: 20px;
+}
+
+
+thead > tr > th {
+ text-align: left;
+}
+
+th {
+ background-color: var(--lightGray);
+}
+
+tr {
+ border-width: 2px;
+}
+
+tr td {
+ border-bottom: 1px solid black;
+}
+
+.col-employee { min-width: 8em; }
+.col-job { min-width: 8em; }
+.col-salary { min-width: 5em; }
+.col-fund { min-width: 6em; }
+.col-appropriation { min-width: 8em; }
+.col-cost-center { min-width: 5em; }
+.col-actions { min-width: 27em; }
+
+#sidebar {
+ background-color: lightgrey;
+}
+
+#position-page {
+ display: none;
+}
\ No newline at end of file