-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+ + +
-
-
-
+
+ + + +
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+
-
-
- - -
+- - -
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
Summary
++
Baseline
+
+
+ FY26 target:
+
-
-
-
-
-
-
+
+ Projected revenue:
+
-
-
-
-
-
-
-
-
+
-
-
- - -
- -
- - -
+ Personnel cost:
+
+
+
+ Non-personnel cost:
+
+
+
+ Total baseline:
+
-
- )
+ for (let i = 0; i < headerCells.length; i++) {
+ if (headerCells[i].textContent.trim() === headerName) {
+ headerCellIndex = i;
+ break;
+ }
+ }
+
+ if (headerCellIndex === -1) {
+ console.error(`No header found with name "${headerName}"`);
+ return;
+ }
+
+ // Assign the class to each cell in the specified column index within the tbody
+ let tbody = table.tBodies[0];
+ if (tbody) {
+ let bodyRows = tbody.rows;
+ for (let row of bodyRows) {
+ if (row.cells[headerCellIndex]) {
+ row.cells[headerCellIndex].classList.add(className);
+ }
+ }
+ }
+ }
+
+function addCostClass(headerName){
+ assignClassToColumn( headerName, 'cost');
+
+ // Get all the cells with the specified class name
+ const cells = document.querySelectorAll(`.cost`);
+
+ cells.forEach(cell => {
+ // Get the current text content of the cell and assign it to 'value' attribute
+ if (!cell.getAttribute('value')){
+ const cellValue = cell.textContent.trim();
+ cell.setAttribute('value', cellValue);
+
+ // Now format the text content like currency and replace it in the cell
+ const formattedCurrency = formatCurrency(parseFloat(cellValue));
+ cell.textContent = formattedCurrency;
+ }
+
+ });
+
+}
+
+function assignColumnClasses(columnDefinitions) {
+ columnDefinitions.forEach(column => {
+ // Assign class to column
+ assignClassToColumn(column.title, column.className);
+
+ // If the column is a cost column, add the specific cost class
+ if (column.isCost) {
+ addCostClass(column.title);
+ }
+ });
+}
+
+
+const Column = {
+ add: function(position, htmlContent, headerTitle) {
+ return addCol(position, htmlContent, headerTitle);
+ },
+ addAtEnd: function(htmlContent, headerTitle) {
+ return addColToEnd(htmlContent, headerTitle);
+ },
+ assignClasses: function(column_definitions) {
+ return assignColumnClasses(column_definitions);
+ }
+};
+
+export default Column;
\ No newline at end of file
diff --git a/js/components/table/subcomponents/data.js b/js/components/table/subcomponents/data.js
new file mode 100644
index 0000000..4c50d3c
--- /dev/null
+++ b/js/components/table/subcomponents/data.js
@@ -0,0 +1,47 @@
+import { fetchJSON } from "../../../utils/data_utils/JSON_data_handlers.js";
+
+async function loadJSONIntoTable(jsonFilePath) {
+ const data = await fetchJSON(jsonFilePath);
+ try {
+ if(Array.isArray(data)) {
+ const table = document.getElementById('main-table');
+ const thead = table.querySelector('thead');
+ const tbody = table.querySelector('tbody');
+
+ // clear existing data
+ thead.innerHTML = '';
+ tbody.innerHTML = '';
+
+ // Create table header row
+ const headerRow = document.createElement('tr');
+ Object.keys(data[0]).forEach(key => {
+ const header = document.createElement('th');
+ header.textContent = key;
+ headerRow.appendChild(header);
+ });
+ thead.appendChild(headerRow);
+
+ // Create table body rows
+ data.forEach(item => {
+ const row = document.createElement('tr');
+ Object.values(item).forEach(val => {
+ const cell = document.createElement('td');
+ cell.textContent = val;
+ row.appendChild(cell);
+ });
+ tbody.appendChild(row);
+ });
+
+ } else {
+ console.error('The provided JSON file does not contain an array of objects.');
+ }
+ } catch(error) {
+ console.error('Failed to load and parse the JSON file:', error);
+ }
+}
+
+export const Data = {
+ loadFromJSON : loadJSONIntoTable
+}
+
+export default Data;
\ No newline at end of file
diff --git a/js/components/table/subcomponents/headers.js b/js/components/table/subcomponents/headers.js
new file mode 100644
index 0000000..1304c8f
--- /dev/null
+++ b/js/components/table/subcomponents/headers.js
@@ -0,0 +1,30 @@
+function addTableHeaders(header_array){
+
+ // Get the table element by its ID
+ const table = document.getElementById('main-table');
+
+ // Create a table header row element
+ const headerRow = document.createElement('tr');
+
+ for (const headerText of header_array) {
+
+ // Create a header cell element
+ const headerCell = document.createElement('th');
+ headerCell.textContent = headerText;
+
+ // Append the header cell to the header row
+ headerRow.appendChild(headerCell);
+ }
+
+ // Append the header row to the table header
+ let thead = table.querySelector('thead');
+ thead.appendChild(headerRow);
+}
+
+const Header = {
+ add: function(header_array){
+ addTableHeaders(header_array)
+ }
+};
+
+export default Header;
\ No newline at end of file
diff --git a/js/components/table/subcomponents/rows.js b/js/components/table/subcomponents/rows.js
new file mode 100644
index 0000000..32d3870
--- /dev/null
+++ b/js/components/table/subcomponents/rows.js
@@ -0,0 +1,66 @@
+import Header from "./headers.js";
+import { formatCurrency } from "../../../utils/common_utils.js";
+
+function addNewRow(data_dictionary){
+ // Get the table element by its ID
+ const table = document.getElementById('main-table');
+
+ // check if header has already been added
+ let header_row = table.querySelector('thead tr');
+ if (!header_row) {
+ Header.add(Object.keys(data_dictionary));
+ }
+
+ // add row of data
+ const new_row = document.createElement('tr');
+ const cell_data_array = Object.values(data_dictionary);
+
+ for (const cell_data of cell_data_array) {
+ // Create new cell and add it to the row
+ const newCell = document.createElement('td');
+ newCell.textContent = cell_data;
+ new_row.appendChild(newCell);
+ }
+
+ console.log(new_row);
+
+ // Append the new row to the table body
+ let tbody = table.querySelector('tbody');
+ tbody.appendChild(new_row);
+}
+
+function saveRowEdits(row){
+ var cells = row.querySelectorAll('td')
+ cells.forEach( cell => {
+ // save dropdown values
+ if (cell.querySelector('select')){
+ var serviceSelector = cell.querySelector('select');
+ cell.textContent = serviceSelector.value;
+ } else if (cell.querySelector('input')) {
+ // save new entered value in textbox
+ var textbox = cell.querySelector('input');
+ var enteredValue = textbox.value;
+ // update display and format with currency if relevant
+ if ( cell.classList.contains('cost') ){
+ // if cost, remove commas first
+ enteredValue = enteredValue.replaceAll(',', '');
+ cell.textContent = formatCurrency(enteredValue);
+ } else {
+ cell.textContent = enteredValue;
+ }
+ // set value attribute to the new user input
+ cell.setAttribute('value', enteredValue);
+ }
+ })
+}
+
+const Rows = {
+ add : function(data_dictionary){
+ addNewRow(data_dictionary)
+ },
+ saveEdits : function(row){
+ saveRowEdits(row)
+ }
+}
+
+export default Rows;
\ No newline at end of file
diff --git a/js/components/table/table.css b/js/components/table/table.css
index df26837..08b4992 100644
--- a/js/components/table/table.css
+++ b/js/components/table/table.css
@@ -8,6 +8,7 @@ th {
tr {
border-width: 2px;
+ background-color: white;
}
tr td {
@@ -19,14 +20,21 @@ input {
width: 100%;
}
-.table-container {
+div.table-container {
display: table;
- margin: auto;
+ overflow-x: auto; /* Scroll horizontally if the table content is wider than the container */
+ overflow-y: auto;
+ padding-left: max(100px, 10vh);
+ padding-right: max(100px, 10vh);
+ max-width: calc(100vw - var(--sidebar-width));
+ margin: auto;
+ min-height: 120px;
+
}
#main-table {
- width: auto;
- margin: auto;
+ font-size: calc(0.6vw + 0.5em);
+ margin: auto;
}
/* Add new row button */
@@ -36,6 +44,10 @@ input {
display: none;
}
+.btn-delete {
+ background-color: var(--orange);
+}
+
#add-btn-div {
display: flex;
justify-content: center; /* Aligns horizontally */
@@ -47,10 +59,23 @@ input {
background-color: var(--spiritgreen);
}
-.active-editing {
+.active-editing, .selected {
background-color: var(--palegreen);
}
+.selected {
+ font-weight: bold;
+}
+
.btn-confirm {
display: none;
+}
+
+.confirm-btn:hover {
+ background-color: var(--green);
+}
+
+.hover-effect:hover {
+ cursor: pointer;
+ background-color: var(--verypalegreen); /* You can choose any color you like */
}
\ No newline at end of file
diff --git a/js/components/table/table.js b/js/components/table/table.js
index 190a372..4e3a8b7 100644
--- a/js/components/table/table.js
+++ b/js/components/table/table.js
@@ -1,211 +1,46 @@
-import { formatCurrency } from "../../utils/utils.js";
-
-export function addTableHeaders(table_id, header_array){
-
- // Get the table element by its ID
- const table = document.getElementById(table_id);
-
- // Create a table header row element
- const headerRow = document.createElement('tr');
-
- for (const headerText of header_array) {
-
- // Create a header cell element
- const headerCell = document.createElement('th');
- headerCell.textContent = headerText;
-
- // Append the header cell to the header row
- headerRow.appendChild(headerCell);
- }
-
- // Append the header row to the table header
- let thead = table.querySelector('thead');
- thead.appendChild(headerRow);
-}
-
-export function addNewRow(table_id, data_dictionary){
- // Get the table element by its ID
- const table = document.getElementById(table_id);
-
- // check if header has already been added
- let header_row = table.querySelector('thead tr');
- if (!header_row) {
- addTableHeaders(table_id, Object.keys(data_dictionary));
- }
-
- // add row of data
- const new_row = document.createElement('tr');
- const cell_data_array = Object.values(data_dictionary);
-
- for (const cell_data of cell_data_array) {
- // Create new cell and add it to the row
- const newCell = document.createElement('td');
- newCell.textContent = cell_data;
- new_row.appendChild(newCell);
- }
-
- // Append the new row to the table body
- let tbody = table.querySelector('tbody');
- tbody.appendChild(new_row);
-
-}
-
-export function adjustTableWidth(table_id, width_pct){
- const table = document.getElementById(table_id);
+import Buttons from './subcomponents/buttons.js'
+import Cell from './subcomponents/cells.js'
+import Columns from './subcomponents/columns.js'
+import Header from './subcomponents/headers.js'
+import Rows from './subcomponents/rows.js'
+import Data from './subcomponents/data.js'
+
+function adjustTableWidth(width_pct){
+ const table = document.getElementById('main-table');
table.style.width = width_pct;
}
-export function clearTable(table_id){
- const table = document.getElementById(table_id);
+function clearTable(){
+ const table = document.getElementById('main-table');
table.querySelector('thead').innerHTML = '';
- table.querySelector('tbody').
- innerHTML = '';
-}
-
-// Add button functions
-export function hideAddButton(){
- document.getElementById('add-btn').style.display = 'none';
-}
-
-export function showAddButton(){
- document.getElementById('add-btn').style.display = 'block';
-}
-
-export function updateAddButtonText(text){
- document.getElementById('add-btn').textContent = text;
-}
-
-// Show and hide table
-
-export function hideTable(table_id){
- const table = document.getElementById(table_id);
- table.style.display = 'none';
- hideAddButton();
+ table.querySelector('tbody').innerHTML = '';
}
-export function showTable(table_id){
- const table = document.getElementById(table_id);
+function showTable(){
+ const table = document.getElementById('main-table');
table.style.display = 'table';
}
-// position is index at which new column will be inserted
-export function addCol(tableId, position, htmlContent = '', headerTitle = '') {
- // Get the table element by its ID
- let table = document.getElementById(tableId);
-
- if (!table) {
- console.error(`Table with ID ${tableId} not found.`);
- return;
- }
-
- // Validate position
- let maxPosition = table.rows[0].cells.length;
- if (position < 0 || position > maxPosition) {
- console.error(`Position ${position} is out of bounds.`);
- return;
- }
-
- // Insert the header if provided
- let thead = table.tHead;
- if (headerTitle && thead) {
- let th = document.createElement('th');
- th.innerHTML = headerTitle; // Use innerHTML to insert HTML content
- thead.rows[0].insertBefore(th, thead.rows[0].cells[position]);
- }
-
- // Insert new cells into each row of the table body
- let tbody = table.tBodies[0];
- if (tbody) {
- for (let i = 0; i < tbody.rows.length; i++) {
- let row = tbody.rows[i];
- let td = document.createElement('td');
- td.innerHTML = htmlContent; // Use innerHTML to insert HTML content
- row.insertBefore(td, row.cells[position]);
- }
- }
-}
-
-function ncols(tableId){
- const table = document.getElementById(tableId);
- // Ensure that the row exists before counting the columns
- return table.rows[0].cells.length;
-}
-
-export function addColToEnd(tableId, htmlContents = [], headerTitle = ''){
- // count columns and add new column to the end
- const position = ncols(tableId);
- addCol(tableId, position, htmlContents, headerTitle);
-}
-
-// functions for editing rows
-function editButton() {
- var edit_btn = '';
- var confirm_btn = '';
- return edit_btn + confirm_btn;
-};
-
-export function addEditCol(tableId){
- addColToEnd(tableId, editButton(), ' ');
-}
-
-export function assignClassToColumn(tableId, headerName, className) {
- // Get the table element by its ID
- let table = document.getElementById(tableId);
-
- // Find the index of the column by its header name
- const thead = table.tHead;
- if (!thead || thead.rows.length === 0) {
- console.error('The table header is not found or has no rows.');
- return;
- }
-
- let headerCellIndex = -1;
- const headerCells = thead.rows[0].cells; // Assuming the first row contains header cells ( )
- for (let i = 0; i < headerCells.length; i++) {
- if (headerCells[i].textContent.trim() === headerName) {
- headerCellIndex = i;
- break;
- }
- }
-
- if (headerCellIndex === -1) {
- console.error(`No header found with name "${headerName}"`);
- return;
- }
-
- // Assign the class to each cell in the specified column index within the tbody
- let tbody = table.tBodies[0];
- if (tbody) {
- let bodyRows = tbody.rows;
- for (let row of bodyRows) {
- if (row.cells[headerCellIndex]) {
- row.cells[headerCellIndex].classList.add(className);
- }
- }
- }
- }
-
-export function AddCostClass(tableId, headerName){
- assignClassToColumn(tableId, headerName, 'cost');
-
- // Get all the cells with the specified class name
- const cells = document.querySelectorAll(`.cost`);
-
- cells.forEach(cell => {
- // Get the current text content of the cell and assign it to 'value' attribute
- if (!cell.getAttribute('value')){
- const cellValue = cell.textContent.trim();
- cell.setAttribute('value', cellValue);
-
- // Now format the text content like currency and replace it in the cell
- const formattedCurrency = formatCurrency(parseFloat(cellValue));
- cell.textContent = formattedCurrency;
- }
-
- });
-
-}
-
-export function updateCellValue(cell, newValue){
- pass;
-}
\ No newline at end of file
+function hideTable(){
+ const table = document.getElementById('main-table');
+ table.style.display = 'none';
+ Buttons.AddRow.hide();
+}
+
+const Table = {
+ Buttons : Buttons,
+ Cell : Cell,
+ Columns : Columns,
+ Header : Header,
+ Rows : Rows,
+ Data : Data,
+ // functions
+ adjustWidth : function(width_pct){
+ adjustTableWidth(width_pct)
+ },
+ clear : clearTable,
+ hide : hideTable,
+ show : showTable
+}
+
+export default Table;
\ No newline at end of file
diff --git a/js/components/welcome/welcome.css b/js/components/welcome/welcome.css
index 23b5108..7237dd6 100644
--- a/js/components/welcome/welcome.css
+++ b/js/components/welcome/welcome.css
@@ -1,28 +1,27 @@
/* Welcome page (index.html) */
.step {
- width: 90%;
- height: 100px;
+ width: 60%;
+ height: 80px;
font-size: 1.75em;
- /* margin-top: 10px; */
- margin-bottom: 10px;
+ margin-bottom: 0px; /* Adds spacing between buttons */
+ margin-left: 20%;
+ border-color: var(--citygreen);
+ border-width: 2;
+ color: var(--citygreen);
+ background-color: white;
}
-#step-upload { background-color: var(--blue);}
-#step-initiatives { background-color: var(--orange); }
-#step-revenue {background-color: var(--green);}
-#step-personnel {background-color: var(--citygreen);}
-#step-nonpersonnel {background-color: var(--yellow);}
-#step-finish {background-color: var(--spiritgreen);}
-
.step:hover {
color: white;
- opacity: 50%;;
+ background-color: var(--spiritgreen);
}
#welcome-page {
- display: flex;
- justify-content: center; /* Center horizontally */
- align-items: center; /* Center vertically */
- flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ padding-top: 20px;
+}
+.step.disabled {
+ opacity: 50%;
}
\ No newline at end of file
diff --git a/js/components/welcome/welcome.js b/js/components/welcome/welcome.js
index eebfb33..e1a65d3 100644
--- a/js/components/welcome/welcome.js
+++ b/js/components/welcome/welcome.js
@@ -1,7 +1,14 @@
// Hide and unhide welcome buttons
-export function unhideWelcomeButtons(){
- document.getElementById("welcome-page").style.display = "flex";
+function unhideWelcomeButtons(){
+ document.getElementById("welcome-page").style.display = "block";
}
-export function hideWelcomeButtons(){
+function hideWelcomeButtons(){
document.getElementById("welcome-page").style.display = "none";
}
+
+export const Welcome = {
+ show: unhideWelcomeButtons,
+ hide : hideWelcomeButtons
+}
+
+export default Welcome;
\ No newline at end of file
diff --git a/js/init.js b/js/init.js
index 38e5886..55c4ec8 100644
--- a/js/init.js
+++ b/js/init.js
@@ -1,39 +1,24 @@
// import functions
-import { initializeWelcomePage } from './pages/00_welcome/main.js';
-import { loadNewInitiatives } from './pages/02_new_initiatives/main.js'
-import { loadRevenuePage } from './pages/03_revenue/main.js'
-import { loadPageState } from './utils/storage-handlers.js'
-import { initializeNavButtons } from './components/nav_buttons/nav_buttons.js';
-import { loadPersonnelPage } from './pages/04_personnel/main.js';
-import { addTarget } from './components/sidebar/sidebar.js';
+import { loadPageState } from './utils/data_utils/local_storage_handlers.js'
+import { visitPage } from './views/view_logic.js'
// path for my laptop
-//export let DATA_ROOT = '../../../data/law_dept_sample/'
+export let DATA_ROOT = '../../../data/law_dept_sample/'
// github path
-export let DATA_ROOT = '../../budget-request-demo/data/law_dept_sample/'
+// export let DATA_ROOT = '../../budget-request-demo/data/law_dept_sample/'
export let REVENUE = 0;
+export let TARGET = 2000000;
+export var FISCAL_YEAR = '26';
-document.addEventListener('DOMContentLoaded', function () {
+// variables on the salary
+export var fringe = 0.36
+export var cola = 0.02
+export var merit = 0.02
- var page_state = loadPageState();
- initializeNavButtons();
- addTarget(2000000);
- switch (page_state){
- case 'welcome':
- initializeWelcomePage();
- break;
- case 'new-inits':
- loadNewInitiatives();
- break;
- case 'revenue':
- loadRevenuePage();
- break;
- case 'personnel':
- loadPersonnelPage();
- break;
- };
-
+document.addEventListener('DOMContentLoaded', function () {
+ var page_state = loadPageState();
+ visitPage(page_state);
+});
-});
\ No newline at end of file
diff --git a/js/pages/00_welcome/helpers.js b/js/pages/00_welcome/helpers.js
deleted file mode 100644
index 1313f4c..0000000
--- a/js/pages/00_welcome/helpers.js
+++ /dev/null
@@ -1,28 +0,0 @@
-
-import { hidePrompt } from '../../components/prompt/prompt.js'
-import { hideNavButtons } from '../../components/nav_buttons/nav_buttons.js'
-import { hideSideBar } from '../../components/sidebar/sidebar.js'
-import { hideTable } from '../../components/table/table.js'
-import { updateSubtitle } from '../../components/header/header.js'
-import { unhideWelcomeButtons } from '../../components/welcome/welcome.js'
-import { loadNewInitiatives } from '../02_new_initiatives/main.js'
-import { loadRevenuePage } from '../03_revenue/main.js'
-import { loadPersonnelPage } from '../04_personnel/main.js'
-
-export function initializePageView(){
- // page set up
- hideTable('main-table');
- hideSideBar();
- updateSubtitle("Welcome");
- unhideWelcomeButtons();
- hidePrompt();
- hideNavButtons();
-}
-
-export function addLinks(){
- // initialize links in buttons
- document.getElementById('step-initiatives').addEventListener('click', loadNewInitiatives)
- document.getElementById('step-revenue').addEventListener('click', loadRevenuePage)
- document.getElementById('step-personnel').addEventListener('click', loadPersonnelPage)
-
-}
diff --git a/js/pages/02_new_initiatives/helpers.js b/js/pages/02_new_initiatives/helpers.js
deleted file mode 100644
index 91b8267..0000000
--- a/js/pages/02_new_initiatives/helpers.js
+++ /dev/null
@@ -1,78 +0,0 @@
-
-import { hideWelcomeButtons } from '../../components/welcome/welcome.js'
-import { updateSubtitle } from '../../components/header/header.js'
-import { hidePrompt, showPrompt, updatePrompt, updatePromptButtons, addPromptButtonAction } from '../../components/prompt/prompt.js'
-import { showNavButtons } from '../../components/nav_buttons/nav_buttons.js'
-import { loadRevenuePage } from '../03_revenue/main.js'
-import { addModalLink, updateModalTitle, clearModal, hideModal } from '../../components/modal/modal.js'
-import { fetchAllResponses, addTextarea, addTextInput, addNumericInput, addSubmitButtonToForm, addForm } from '../../components/form/form.js'
-import { adjustTableWidth, hideTable, clearTable, updateAddButtonText, addNewRow, showTable, showAddButton } from '../../components/table/table.js'
-import { hideSideBar } from '../../components/sidebar/sidebar.js'
-
-export function initializePageView() {
- // Load text
- updateSubtitle('New Initiatives');
- updatePrompt('Do you have any new initiatives for FY26?');
- updatePromptButtons('Yes', 'No');
-
- // Prepare page view
- hideWelcomeButtons();
- showNavButtons();
- hideSideBar();
- showPrompt();
- hideTable('main-table');
-}
-
-export function setUpModal() {
- // Initialize modal
- clearModal();
- addModalLink('option1', 'main-modal');
- updateModalTitle('New initiative');
- addModalLink('add-btn', 'main-modal');
-}
-
-export function setUpForm() {
- // Set up form
- addForm();
- addTextInput('Initiative Name:', 'Initiative Name', true); // Add required field
- addTextarea('Explain why this initiative is necessary and describe its potential impact.', 'Explanation', true);
- addNumericInput('Roughly how additional money would this initiative require?', 'Cost', true);
- addSubmitButtonToForm();
- // Initialize form submission to table data
- handleFormSubmissions();
-}
-
-export function setUpTable() {
- // Set up table
- clearTable('main-table');
- adjustTableWidth('main-table', '70%');
- updateAddButtonText('Add another new initiative');
-}
-
-export function handleNavigation() {
- // clicking 'No' (no new initiatives) will also take us to the next page
- addPromptButtonAction('option2', loadRevenuePage);
-}
-
-export function handleFormSubmissions(event){
- // initialize form submission
- const modal = document.getElementById('main-modal');
- modal.addEventListener('submit', function(event) {
- // get answers from form, hide form, show answers in table
- const responses = fetchAllResponses(event);
- // make sure it's not an empty response
- if (Object.values(responses)[0] != ''){
- // change page view
- hideModal('main-modal');
- hidePrompt();
-
- // add data to table
- addNewRow('main-table', responses);
- showTable('main-table');
- showAddButton();
- // TODO: save table data
- // TODO: edit cost to show currency correctly
- }
-
- })
-}
diff --git a/js/pages/02_new_initiatives/main.js b/js/pages/02_new_initiatives/main.js
deleted file mode 100644
index 63d636b..0000000
--- a/js/pages/02_new_initiatives/main.js
+++ /dev/null
@@ -1,14 +0,0 @@
-
-import { initializePageView, setUpModal, setUpForm, setUpTable, handleNavigation } from './helpers.js'
-import { updatePageState } from '../../utils/storage-handlers.js'
-
-
-// set up page and initialize all buttons
-export function loadNewInitiatives() {
- updatePageState('new-inits');
- initializePageView();
- setUpModal();
- setUpForm();
- setUpTable();
- handleNavigation();
-}
diff --git a/js/pages/03_revenue/main.js b/js/pages/03_revenue/main.js
deleted file mode 100644
index 7d1c494..0000000
--- a/js/pages/03_revenue/main.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { updatePageState } from '../../utils/storage-handlers.js'
-import { hideWelcomeButtons } from '../../components/welcome/welcome.js'
-import { updateSubtitle } from '../../components/header/header.js'
-import { showPrompt, updatePrompt, updatePromptButtons, addPromptButtonAction } from '../../components/prompt/prompt.js'
-import { showNavButtons } from '../../components/nav_buttons/nav_buttons.js'
-import { loadNewInitiatives } from '../02_new_initiatives/main.js'
-import { hideTable } from '../../components/table/table.js'
-import { hideSideBar } from '../../components/sidebar/sidebar.js'
-import { formatCurrency } from '../../utils/utils.js'
-
-import { REVENUE } from '../../init.js'
-
-export function loadRevenuePage() {
-
- //update page state
- updatePageState('revenue');
-
- // prepare page view
- hideWelcomeButtons();
- showPrompt();
- showNavButtons();
- hideTable('main-table');
- hideSideBar();
-
- // update page text
- updateSubtitle('Revenue Projections');
- // TODO: update to make dynamic
- updatePrompt(`Your revenue projection for FY26 is ${formatCurrency(REVENUE, true)}`);
- updatePromptButtons('Confirm and continue.', "This doesn't look right");
-
- // clicking 'confirm and continue' will also take us to the next page
- addPromptButtonAction('option1', loadNewInitiatives);
-
-}
\ No newline at end of file
diff --git a/js/pages/04_personnel/archive/main_archived.js b/js/pages/04_personnel/archive/main_archived.js
deleted file mode 100644
index 54e9d6e..0000000
--- a/js/pages/04_personnel/archive/main_archived.js
+++ /dev/null
@@ -1,47 +0,0 @@
-document.addEventListener('DOMContentLoaded', function () {
-
- // // Load from last local storage
- // loadTableData("employeeTableData");
-
- // // Add an event listener for the save button
- // document.getElementById('save').addEventListener('click', function() {
- // saveTableData("employeeTableData");
- // });
-
- // // Add an event listener for the download button
- // document.getElementById('XLSX-download').addEventListener('click', function() {
- // saveTableData("employee-table");
- // downloadTableAsExcel('employeeTableData', 'Personnel', 'table-export');
- // });
-
- // Mark row to be edited on edit button click
- var editButtons = document.getElementsByClassName('btn-edit');
- for (var i = 0; i < editButtons.length; i++) {
- editButtons[i].addEventListener('click', handleAccountEdit);
- };
- // Remove edit marker when finished
- document.getElementById('modal-close-x').addEventListener('click', exitAccountEditModal);
- document.getElementById('modal-done-btn').addEventListener('click', exitAccountEditModal);
-
- // Update account string based on info in modal dropdowns
- document.getElementById('dropdown-fund').addEventListener("change", function(event){
- updateAccountString('dropdown-fund', 'fund-string');
- });
- document.getElementById('dropdown-approp').addEventListener("change", function(event){
- updateAccountString('dropdown-approp', 'approp-string');
- });
- document.getElementById('dropdown-cc').addEventListener("change", function(event){
- updateAccountString('dropdown-cc', 'cc-string');
- });
-
- // Make FTEs editable
- applyEditableCells('.ftes', 'value', null, updateDisplayandTotals, validateNumber)
-
- // Initialize continue button
- document.getElementById('continue-btn').addEventListener('click', continueToNonPersonnel);
-
-});
-
-
-
-
diff --git a/js/pages/04_personnel/archive/rollup-helpers.js b/js/pages/04_personnel/archive/rollup-helpers.js
deleted file mode 100644
index 931e08f..0000000
--- a/js/pages/04_personnel/archive/rollup-helpers.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// variables on the salary
-fringe = 0.36
-cola = 0.02
-merit = 0.02
-
-
-function updateAccountString(dropdown_id, string_class){
- var dropdown = document.getElementById(dropdown_id);
- var account_num = dropdown.options[dropdown.selectedIndex].value;
- // find correct string to update
- var rowToEdit = document.getElementById('editing');
- var account_span = rowToEdit.getElementsByClassName(string_class)[0];
- account_span.textContent = account_num;
-}
-
-function handleAccountEdit(event) {
- // Determine what was clicked on within the table
- var rowToEdit = event.target.closest('tr');
- // mark row as being edited
- rowToEdit.id = 'editing';
- var job_name = rowToEdit.cells[0].textContent;
- document.getElementById('job-name').textContent = job_name;
-}
-
-function exitAccountEditModal() {
- // remove marker that row is being actively edited
- document.getElementById('editing').removeAttribute('id');
-}
-
-
-// check if all service boxes are filled
-function validateServiceSelections(){
- let service_dropdowns = document.querySelectorAll(".service")
- let validated = true;
- service_dropdowns.forEach(function(dropdown) {
- if (!dropdown.value || dropdown.value.trim() === "") {
- // Found a dropdown with an empty value, return false
- validated = false;
- }
- });
- // All dropdowns have a non-empty value
- return validated;
-}
-
-function showServiceErrors(){
- let service_dropdowns = document.querySelectorAll(".service")
- service_dropdowns.forEach(function(dropdown) {
-
- const cell = dropdown.parentElement;
-
- // Clear any previous error message
- const existingErrorMessage = cell.querySelector('.error-message');
- if (existingErrorMessage) {
- cell.removeChild(existingErrorMessage);
- }
-
- if (!dropdown.value || dropdown.value.trim() === "") {
- // Create a new span element for the error message
- const errorMessage = document.createElement('span');
- errorMessage.textContent = "This field is required";
- errorMessage.classList.add('error-message');
-
- // Append the error message span to the cell
- cell.appendChild(errorMessage);
- }
- });
-}
-
-// function to happen on click of continue button
-function continueToNonPersonnel(){
- if (validateServiceSelections()){
- saveCounters();
- saveTableData(table_id = "rollup-table", save_as = "rollup_table");
- window.location.href = "05_nonpersonnel.html";
- } else {
- showServiceErrors();
- }
-}
\ No newline at end of file
diff --git a/js/pages/04_personnel/helpers.js b/js/pages/04_personnel/helpers.js
deleted file mode 100644
index 206c537..0000000
--- a/js/pages/04_personnel/helpers.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import { hideWelcomeButtons } from "../../components/welcome/welcome.js";
-import { hidePromptButtons, showPrompt, updatePrompt } from "../../components/prompt/prompt.js";
-import { showNavButtons } from "../../components/nav_buttons/nav_buttons.js";
-import { updateSubtitle } from "../../components/header/header.js";
-import { loadJSONIntoTable } from "../../utils/data-handlers.js";
-import { AddCostClass, addCol, addColToEnd, addEditCol, adjustTableWidth, assignClassToColumn, showTable } from "../../components/table/table.js";
-import { incrementSidebarStat, showSideBar } from "../../components/sidebar/sidebar.js";
-import { formatCurrency } from "../../utils/utils.js";
-import { DATA_ROOT } from "../../init.js"
-import { createDropdownFromJSON } from "../../components/form/form.js";
-
-// variables on the salary
-var fringe = 0.36
-var cola = 0.02
-var merit = 0.02
-
-export function preparePageView(){
- // prepare page view
- hideWelcomeButtons();
- showPrompt();
- showNavButtons();
- showSideBar();
- hidePromptButtons();
- adjustTableWidth('main-table', '90%');
-
- // update page text
- updateSubtitle('Personnel');
- updatePrompt('For each job in your department, select the service and request the number of baseline and supplemental FTEs.');
-}
-
-export async function initializePersonnelTable(){
- // load table data from json
- await loadJSONIntoTable(DATA_ROOT + 'personnel_data.json', 'main-table');
- //after table is loaded, fill it
- showTable('main-table');
- addCol('main-table', 3, '', 'Service');
- addColToEnd('main-table', '0', 'Total Cost (Baseline)');
- addColToEnd('main-table', '0', 'Total Cost (Supplementary)');
- addEditCol('main-table');
- // assign cost classes
- assignClassToColumn('main-table', 'Current Average Salary', 'avg-salary');
- AddCostClass('main-table', 'Current Average Salary');
- assignClassToColumn('main-table', 'Total Cost (Baseline)', 'total-baseline');
- AddCostClass('main-table', 'Total Cost (Baseline)');
- assignClassToColumn('main-table', 'Total Cost (Supplementary)', 'total-supp');
- AddCostClass('main-table', 'Total Cost (Supplementary)');
- // assign other classes
- assignClassToColumn('main-table', 'Job Name', 'job-name');
- assignClassToColumn('main-table', 'Baseline FTEs', 'baseline-ftes');
- assignClassToColumn('main-table', 'Supplemental FTEs', 'supp-ftes');
- assignClassToColumn('main-table', 'Service', 'service');
- // manage edit buttons
- handleRowEdit();
-}
-
-export function handleRowEdit(){
- // attach an event listener to each edit button in every row
- var editButtons = document.getElementsByClassName('btn-edit');
- for (var i = 0; i < editButtons.length; i++) {
- editButtons[i].addEventListener('click', async function(event) {
- // Determine what was clicked on within the table
- var rowToEdit = event.target.closest('tr');
- // mark row as being edited
- rowToEdit.classList.add('active-editing');
-
- // turn relevant entries into textboxes
- createEditableCell('baseline-ftes');
- createEditableCell('supp-ftes');
- // add service dropdown
- const serviceDropdown = await createDropdownFromJSON(DATA_ROOT + 'services.json');
- rowToEdit.querySelector('.service').innerHTML = serviceDropdown;
-
- // hide edit buttons
- var editButtons = document.getElementsByClassName('btn-edit');
- for (var i = 0; i < editButtons.length; i++) {
- editButtons[i].style.display = 'none';
- }
-
- initializeConfirmButton(rowToEdit);
- });
- };
-}
-
-
-function createEditableCell(cellClass, attribute = 'value'){
- // get cell
- const cell = document.querySelector(`.active-editing td.${cellClass}`);
- // Create an input element to edit the value
- var textbox = document.createElement('input');
- textbox.type = 'text';
- textbox.value = cell.textContent;
- // Clear the current content and append the textbox to the cell
- cell.innerHTML = '';
- cell.appendChild(textbox);
- //cell.appendChild(feedback);
-}
-
-
-function initializeConfirmButton(rowToEdit){
- // get element and add listener for click
- const confirm_btn = rowToEdit.querySelector(".btn-confirm");
- // show confirm button
- confirm_btn.style.display = 'block';
- confirm_btn.addEventListener('click', function(event){
- // get current row
- const rowToEdit = event.target.closest('tr');
- var textboxes = rowToEdit.querySelectorAll('input');
- // save all text in textboxes
- textboxes.forEach( textbox => {
- var enteredValue = textbox.value;
- var cell = textbox.parentElement;
- cell.textContent = enteredValue;
- cell.setAttribute('value', enteredValue);
- })
- // set service selection
- const serviceSelector = rowToEdit.querySelector('select');
- var cell = serviceSelector.parentElement;
- cell.textContent = serviceSelector.value;
- //set service value
-
-
- // update values in sidebar
- updateDisplayandTotals();
-
- // make row no longer green
- rowToEdit.classList.remove('active-editing');
-
- // show edit buttons
- var editButtons = document.getElementsByClassName('btn-edit');
- for (var i = 0; i < editButtons.length; i++) {
- editButtons[i].style.display = 'block';
- }
-
- // hide confirm button
- confirm_btn.style.display = 'none';
- });
-}
-
-function getCellValue(row, className){
- var cellValue = row.querySelector(`.${className}`).getAttribute('value');
- return parseFloat(cellValue);
-}
-
-function calculateTotalCost(ftes, avg_salary, fringe, cola, merit){
- return ftes * avg_salary * (1 + fringe) * (1 + cola) * (1 + merit);
-}
-
-export function updateTableCell(row, col_class, new_value){
- const cell = row.querySelector(`.${col_class}`);
- cell.setAttribute('value', new_value);
- cell.textContent = formatCurrency(new_value);
-}
-
-// update sidebar and also cost totals when the FTEs are edited
-function updateDisplayandTotals(){
- // get row
- const row = document.querySelector('.active-editing');
- // fetch values for calculations
- let avg_salary = getCellValue(row, 'avg-salary');
- let baseline_ftes = getCellValue(row, 'baseline-ftes');
- let supp_ftes = getCellValue(row, 'supp-ftes');
-
- // calcuate #FTEs x average salary + COLA adjustments + merit adjustments + fringe
- let total_baseline_cost = calculateTotalCost(baseline_ftes, avg_salary, fringe, cola, merit);
- let total_supp_cost = calculateTotalCost(supp_ftes, avg_salary, fringe, cola, merit);
-
- // update counters
- incrementSidebarStat('baseline-personnel', total_baseline_cost);
- incrementSidebarStat('supp-personnel', total_supp_cost);
-
- // update totals in table
- updateTableCell(row, 'total-baseline', total_baseline_cost);
- updateTableCell(row, 'total-supp', total_supp_cost);
-}
diff --git a/js/pages/04_personnel/main.js b/js/pages/04_personnel/main.js
deleted file mode 100644
index c067081..0000000
--- a/js/pages/04_personnel/main.js
+++ /dev/null
@@ -1,12 +0,0 @@
-
-import { updatePageState } from "../../utils/storage-handlers.js";
-import { preparePageView, initializePersonnelTable } from "./helpers.js";
-
-export function loadPersonnelPage(){
-
- updatePageState('personnel');
- preparePageView();
- initializePersonnelTable();
-
-}
-
diff --git a/js/pages/05_nonpersonnel/main.js b/js/pages/05_nonpersonnel/main.js
deleted file mode 100644
index 670e45f..0000000
--- a/js/pages/05_nonpersonnel/main.js
+++ /dev/null
@@ -1,24 +0,0 @@
-document.addEventListener('DOMContentLoaded', function () {
-
- // Load from last local storage
- loadTableData("employeeTableData");
- loadCounters();
-
- // Add new row to the position table
- document.querySelector('.btn-add').addEventListener('click', addRow);
-
- // Event listener for the action buttons
- document.getElementById('employee-table').addEventListener('click', handleActionClick);
-
- // Add an event listener for the save button
- // document.getElementById('save').addEventListener('click', function() {
- // saveTableData("employee-table", 'employeeTableData');
- // });
-
- // Add an event listener for the download button
- document.getElementById('XLSX-download').addEventListener('click', function() {
- saveTableData('employee-table', 'employeeTableData');
- downloadTableAsExcel('employeeTableData', 'Personnel', 'table-export');
- });
-
-});
\ No newline at end of file
diff --git a/js/pages/05_nonpersonnel/nonpersonnel-table.js b/js/pages/05_nonpersonnel/nonpersonnel-table.js
deleted file mode 100644
index b8a7dd4..0000000
--- a/js/pages/05_nonpersonnel/nonpersonnel-table.js
+++ /dev/null
@@ -1,93 +0,0 @@
-
-// Manage clicks in the action area of the personnel table
-function handleActionClick(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('.cost').getAttribute('value'));
- if (rowClass == "keep"){
- nonpersonnel_baseline -= salary;
- } else if (rowClass == "supp"){
- nonpersonnel_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('.cost').getAttribute('value'));
- if (rowClass == "keep"){
- nonpersonnel_baseline -= salary
- };
- if (rowClass != "supp"){
- nonpersonnel_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('.cost').getAttribute('value'));
- if (rowClass == "supp"){
- nonpersonnel_supp -= salary;
- } ;
- if (rowClass != "keep"){
- nonpersonnel_baseline += salary;
- } ;
- updateDisplay();
- }
-}
-
-// Add row for personnel table
-function addRow() {
- let table_id = "employee-table"
- var table = document.getElementById(table_id);
- var newRow = table.insertRow(-1);
- // var newNameCell = newRow.insertCell(0);
- // count number of table columns using jQuery
- let key = "#" + table_id + " tr th";
- let cols = $(key).length;
- for (let i = 0; i < cols-2; i++) {
- var nextCell = newRow.insertCell(i);
- createEditableCell(nextCell, 'value');
- }
-
- // Cost cell will always be second to last
- var costCell = newRow.insertCell(cols-2);
- createEditableCell(costCell, 'value', formatCurrency, updateDisplay, validateNumber);
- costCell.classList.add('cost')
-
- // Last cell is the action cell with 3 buttons
- var actionCell = newRow.insertCell(cols-1);
- actionCell.innerHTML = `
-
-
- -
-
-
+
diff --git a/js/components/body/body.css b/js/components/body/body.css
new file mode 100644
index 0000000..c669b50
--- /dev/null
+++ b/js/components/body/body.css
@@ -0,0 +1,4 @@
+body {
+ background-color: var(--lightGray);
+ margin: 0;
+}
\ No newline at end of file
diff --git a/js/components/body/body.js b/js/components/body/body.js
new file mode 100644
index 0000000..5f1eced
--- /dev/null
+++ b/js/components/body/body.js
@@ -0,0 +1,26 @@
+import Welcome from '../../components/welcome/welcome.js'
+import Modal from '../modal/modal.js';
+import NavButtons from '../nav_buttons/nav_buttons.js';
+import Prompt from '../prompt/prompt.js';
+import Sidebar from '../sidebar/sidebar.js';
+import Table from '../table/table.js';
+
+function resetPage() {
+ // hide everything in the body
+ Welcome.hide();
+ Modal.clear();
+ Modal.hide();
+ NavButtons.hide();
+ Prompt.hide();
+ Table.hide();
+ Sidebar.hide();
+ // disable next button
+ NavButtons.Next.disable();
+ Prompt.Buttons.reset();
+}
+
+export const Body = {
+ reset : resetPage
+}
+
+export default Body;
\ No newline at end of file
diff --git a/js/components/form/form.js b/js/components/form/form.js
index 9081a0f..083a20a 100644
--- a/js/components/form/form.js
+++ b/js/components/form/form.js
@@ -1,81 +1,8 @@
-// function to add questions to forms
-// type is 'input' or 'textarea'
-// inputType is for validation ('number' or 'text', etc)
-function appendFormElement(type, label, inputId, required, inputType, form_id = 'new-form', cost = false) {
+import Dropdown from "./subcomponents/dropdown.js";
+import NewField from "./subcomponents/fields.js";
+import SubmitButton from "./subcomponents/submit.js";
- // change if we want forms elsewhere
- const form = document.getElementById(form_id);
-
- // create outer wrapper for element
- const wrapper = document.createElement('div');
-
- // label question
- const labelEl = document.createElement('label');
- labelEl.textContent = label;
-
- // set type (input or textarea)
- let inputEl;
- if (type === 'input') {
- inputEl = document.createElement('input');
- inputEl.type = inputType;
- } else if (type === 'textarea') {
- inputEl = document.createElement('textarea');
- } else {
- throw new Error('Unsupported element type');
- }
-
- // mark as required if applicable
- inputEl.required = required;
-
- // If an ID is provided, set it on the element
- if (inputId) {
- inputEl.id = inputId;
- }
-
- // add elements
- wrapper.appendChild(labelEl);
- wrapper.appendChild(inputEl);
- form.appendChild(wrapper);
-}
-
-
-
-// Individual functions for each type of input.
-export function addTextInput(label, inputId, required = false, form_id = 'new-form', cost = false) {
- appendFormElement('input', label, inputId, required, 'text', form_id);
-}
-
-export function addNumericInput(label, inputId, required = false, form_id = 'new-form', cost = true) {
- appendFormElement('input', label, inputId, required, 'number', form_id);
-}
-
-export function addTextarea(label, inputId, required = false, form_id = 'new-form', cost = false) {
- appendFormElement('textarea', label, inputId, required, form_id);
-}
-
-export function addSubmitButtonToForm(form_id = 'new-form') {
- // Find the form by its ID
- const form = document.getElementById(form_id);
-
- // Create the container `div` for the button
- const buttonContainer = document.createElement('div');
- buttonContainer.id = 'submit-btn-container';
-
- // Create the submit input
- const submitInput = document.createElement('input');
- submitInput.className = 'btn btn-submit'; // Use appropriate class for your design
- submitInput.type = 'submit';
- submitInput.value = 'Submit';
-
- // Append the submit input to the container
- buttonContainer.appendChild(submitInput);
-
- // Append the container to the form
- form.appendChild(buttonContainer);
-}
-
-export function fetchAllResponses(event) {
- event.preventDefault(); // Prevent the default form submission
+function fetchAllResponses(event) {
// Assuming `event.target` is the form itself
const form = event.target;
@@ -102,7 +29,7 @@ export function fetchAllResponses(event) {
return formData;
}
-export function addForm(element_id = 'modal-body', form_id = 'new-form') {
+function addForm(element_id = 'modal-body', form_id = 'new-form') {
const target_elem = document.getElementById(element_id);
@@ -115,22 +42,12 @@ export function addForm(element_id = 'modal-body', form_id = 'new-form') {
}
-export async function createDropdownFromJSON(json_path) {
- // Fetch JSON data from a file asynchronously
- const response = await fetch(json_path);
- const dataArray = await response.json();
-
- // Creating a select element
- const selectElement = document.createElement('select');
-
- // Looping through the array and creating an option for each element
- dataArray.forEach(item => {
- const optionElement = document.createElement('option');
- optionElement.value = item.id; // Setting the option value to the item id
- optionElement.textContent = item.name; // Setting the display text to the item name
- selectElement.appendChild(optionElement); // Appending the option to the select
- });
+export const Form = {
+ new : function(parent_elem_id) { addForm(parent_elem_id, 'new-form') },
+ fetchAllResponses : function(event) { return fetchAllResponses(event) },
+ NewField : NewField,
+ Dropdown : Dropdown,
+ SubmitButton : SubmitButton
+}
- // Return the select element so it can be appended to the document
- return selectElement.outerHTML;
-}
\ No newline at end of file
+export default Form;
\ No newline at end of file
diff --git a/js/components/form/subcomponents/dropdown.js b/js/components/form/subcomponents/dropdown.js
new file mode 100644
index 0000000..5f64600
--- /dev/null
+++ b/js/components/form/subcomponents/dropdown.js
@@ -0,0 +1,25 @@
+async function createDropdownFromJSON(json_path) {
+ // Fetch JSON data from a file asynchronously
+ const response = await fetch(json_path);
+ const dataArray = await response.json();
+
+ // Creating a select element
+ const selectElement = document.createElement('select');
+
+ // Looping through the array and creating an option for each element
+ dataArray.forEach(item => {
+ const optionElement = document.createElement('option');
+ optionElement.value = item.id; // Setting the option value to the item id
+ optionElement.textContent = item.name; // Setting the display text to the item name
+ selectElement.appendChild(optionElement); // Appending the option to the select
+ });
+
+ // Return the select element so it can be appended to the document
+ return selectElement;
+}
+
+export const Dropdown = {
+ createFromJSON : function(json_path){ return createDropdownFromJSON(json_path) }
+}
+
+export default Dropdown;
\ No newline at end of file
diff --git a/js/components/form/subcomponents/fields.js b/js/components/form/subcomponents/fields.js
new file mode 100644
index 0000000..1b65087
--- /dev/null
+++ b/js/components/form/subcomponents/fields.js
@@ -0,0 +1,53 @@
+// function to add questions to forms
+// type is 'input' or 'textarea'
+// inputType is for validation ('number' or 'text', etc)
+function appendFormElement(type, label, inputId, required, inputType, form_id = 'new-form', cost = false) {
+
+ // change if we want forms elsewhere
+ const form = document.getElementById(form_id);
+
+ // create outer wrapper for element
+ const wrapper = document.createElement('div');
+
+ // label question
+ const labelEl = document.createElement('label');
+ labelEl.textContent = label;
+
+ // set type (input or textarea)
+ let inputEl;
+ if (type === 'input') {
+ inputEl = document.createElement('input');
+ inputEl.type = inputType;
+ } else if (type === 'textarea') {
+ inputEl = document.createElement('textarea');
+ } else {
+ throw new Error('Unsupported element type');
+ }
+
+ // mark as required if applicable
+ inputEl.required = required;
+
+ // If an ID is provided, set it on the element
+ if (inputId) {
+ inputEl.id = inputId;
+ }
+
+ // add elements
+ wrapper.appendChild(labelEl);
+ wrapper.appendChild(inputEl);
+ form.appendChild(wrapper);
+}
+
+export const NewField = {
+ shortText : function(label, inputId, required = false, form_id = 'new-form', cost = false) {
+ appendFormElement('input', label, inputId, required, 'text', form_id);
+ },
+ longText : function(label, inputId, required = false, form_id = 'new-form', cost = false) {
+ appendFormElement('textarea', label, inputId, required, form_id);
+ },
+ numericInput: function(label, inputId, required = false, form_id = 'new-form', cost = true) {
+ appendFormElement('input', label, inputId, required, 'number', form_id);
+ }
+}
+
+export default NewField;
\ No newline at end of file
diff --git a/js/components/form/subcomponents/submit.js b/js/components/form/subcomponents/submit.js
new file mode 100644
index 0000000..3a47d55
--- /dev/null
+++ b/js/components/form/subcomponents/submit.js
@@ -0,0 +1,26 @@
+function addSubmitButtonToForm(form_id) {
+ // Find the form by its ID
+ const form = document.getElementById(form_id);
+
+ // Create the container `div` for the button
+ const buttonContainer = document.createElement('div');
+ buttonContainer.id = 'submit-btn-container';
+
+ // Create the submit input
+ const submitInput = document.createElement('input');
+ submitInput.className = 'btn btn-submit'; // Use appropriate class for your design
+ submitInput.type = 'submit';
+ submitInput.value = 'Submit';
+
+ // Append the submit input to the container
+ buttonContainer.appendChild(submitInput);
+
+ // Append the container to the form
+ form.appendChild(buttonContainer);
+}
+
+export const SubmitButton = {
+ add : function() { addSubmitButtonToForm('new-form') }
+}
+
+export default SubmitButton;
\ No newline at end of file
diff --git a/js/components/header/header.css b/js/components/header/header.css
index ccd9d76..7a5dec9 100644
--- a/js/components/header/header.css
+++ b/js/components/header/header.css
@@ -4,6 +4,25 @@ h1 {
}
h2 {
- color: var(--citygreen);
+ color: var(--darkGray);
text-align: center;
+}
+
+header {
+ align-items: center;
+ background-color: white;
+ padding: 5px;
+ border-bottom: 1px solid var(--citygreen);
+ /* border: 1px solid var(--citygreen); */
+ height: var(--header-height);
+ /* width: calc(100vw - var(--sidebar-width)); */
+ padding-left: 20px;
+}
+
+/* Logo styling */
+#logo {
+ height: 50px; /* Or your desired size */
+ margin-right: 20px; /* Optional: space between logo and title */
+ margin-bottom: -50px;
+ margin-top: 20px;
}
\ No newline at end of file
diff --git a/js/components/header/header.js b/js/components/header/header.js
index d8e0e6f..825c48c 100644
--- a/js/components/header/header.js
+++ b/js/components/header/header.js
@@ -1,3 +1,12 @@
-export function updateSubtitle(subtitle){
- document.getElementById("subtitle").textContent = subtitle;
-}
\ No newline at end of file
+export const Subtitle = {
+ update : function(subtitle){
+ // get current fund
+ var fund = localStorage.getItem("fund");
+ if (fund){
+ var subtitle = `${subtitle}: ${fund}`;
+ }
+ document.getElementById("subtitle").textContent = subtitle;
+ }
+}
+
+export default Subtitle;
\ No newline at end of file
diff --git a/js/components/modal/modal.js b/js/components/modal/modal.js
index 7f0275f..75f5f5e 100644
--- a/js/components/modal/modal.js
+++ b/js/components/modal/modal.js
@@ -1,22 +1,49 @@
-export function hideModal(modal_id) {
+
+function clearModal(){
+ updateModalTitle('');
+ document.getElementById('modal-body').innerHTML = '';
+ //removeAllModalLinks()
+}
+
+// function removeAllModalLinks(){
+// TODO
+// }
+
+function hideModal(modal_id) {
$('#' + modal_id).modal('hide');
}
-export function showModal(modal_id) {
+function showModal(modal_id) {
$('#' + modal_id).modal('show');
}
-export function addModalLink(button_id, modal_id){
- document.getElementById(button_id).addEventListener('click', function() {
- showModal(modal_id);
- });
+function showModalHandler() {
+ showModal('main-modal');
+}
+
+const Link = {
+ add : function(button_id){
+ document.getElementById(button_id).addEventListener('click', showModalHandler)
+ },
+ remove : function(button_id){
+ document.getElementById(button_id).removeEventListener('click', showModalHandler)
+ }
}
-export function updateModalTitle(title){
+function updateModalTitle(title) {
document.getElementById('modal-title').textContent = title;
}
-export function clearModal(){
- updateModalTitle('');
- document.getElementById('modal-body').innerHTML = '';
-}
\ No newline at end of file
+const Title = {
+ update : function(title) { updateModalTitle(title) }
+}
+
+export const Modal = {
+ hide : function() { hideModal('main-modal') },
+ show : function() { showModal('main-modal') },
+ clear : clearModal,
+ Title : Title,
+ Link : Link
+}
+
+export default Modal;
\ No newline at end of file
diff --git a/js/components/nav_buttons/nav_buttons.css b/js/components/nav_buttons/nav_buttons.css
index 8560007..fdf0b37 100644
--- a/js/components/nav_buttons/nav_buttons.css
+++ b/js/components/nav_buttons/nav_buttons.css
@@ -1,12 +1,24 @@
#nav-btns {
margin: 20px;
text-align: center;
+ /* margin-top: 100px; */
+ /* position: absolute; or 'absolute' depending on use-case */
+ /* top: 100px; Distance from the top of the viewport or the closest positioned ancestor */
}
#btn-next, #btn-last {
- background-color: var(--blue);
+ background-color: var(--darkGray);
}
#btn-next:hover, #btn-last:hover {
background-color: var(--yellow);
+ color: var(--darkGray);
+}
+
+/* Add style for when you cannot click the next button */
+#btn-next.disabled, #btn-last.disabled,
+#btn-next.disabled:hover, #btn-last.disabled:hover {
+ background-color: gray;
+ color: white;
+ pointer-events: none;
}
\ No newline at end of file
diff --git a/js/components/nav_buttons/nav_buttons.js b/js/components/nav_buttons/nav_buttons.js
index ddce93f..1be8de5 100644
--- a/js/components/nav_buttons/nav_buttons.js
+++ b/js/components/nav_buttons/nav_buttons.js
@@ -1,64 +1,47 @@
+import { nextPage, lastPage } from '../../views/view_logic.js'
-import { initializeWelcomePage } from '../../pages/00_welcome/main.js';
-import { loadNewInitiatives } from '../../pages/02_new_initiatives/main.js'
-import { loadRevenuePage } from '../../pages/03_revenue/main.js'
-import { loadPersonnelPage } from '../../pages/04_personnel/main.js';
-import { loadPageState } from '../../utils/storage-handlers.js'
-
-
-let pages = {'welcome' : initializeWelcomePage,
- 'new-inits' : loadNewInitiatives,
- 'revenue' : loadRevenuePage,
- 'personnel' : loadPersonnelPage }
+function initializeNavButtons(){
+ // initialize last button
+ const last_btn = document.getElementById('btn-last');
+ last_btn.addEventListener('click', lastPage);
+ // initialize next button
+ const next_btn = document.getElementById('btn-next');
+ next_btn.addEventListener('click', nextPage);
+ disable('btn-next');
+}
-export function hideNavButtons() {
+function hideNavButtons() {
document.getElementById('nav-btns').style.display = 'none';
}
-export function showNavButtons() {
+function showNavButtons() {
document.getElementById('nav-btns').style.display = 'block';
+ initializeNavButtons();
}
-// imputs next and last should be functions to render the appropriate pages
-export function initializeNavButtons(){
- // initialize last button
- const last_btn = document.getElementById('btn-last');
- last_btn.addEventListener('click', lastPage);
- // initialize next button
- const next_btn = document.getElementById('btn-next');
- next_btn.addEventListener('click', nextPage);
+function disable(button_id) {
+ document.getElementById(button_id).classList.add('disabled');
}
-function nextPage(page_state){
+function enable(button_id) {
+ document.getElementById(button_id).classList.remove('disabled');
+}
+
+const Next = {
+ disable : function() { disable('btn-next') },
+ enable : function() { enable('btn-next') }
+}
- var page_state = loadPageState();
- const keys = Object.keys(pages);
-
- // Find the index of the current key
- const currentIndex = keys.indexOf(page_state);
-
- // Check if there is a next key
- if (currentIndex >= 0 && currentIndex < keys.length - 1) {
- // Get the next key
- const nextKey = keys[currentIndex + 1];
- const nextFn = pages[nextKey];
- nextFn();
- }
+const Last = {
+ disable : function() { disable('btn-last') },
+ enable : function() { enable('btn-last') }
}
-function lastPage(){
+export const NavButtons = {
+ hide : hideNavButtons,
+ show : showNavButtons,
+ Next : Next,
+ Last : Last
+}
- var page_state = loadPageState();
- const keys = Object.keys(pages);
-
- // Find the index of the current key
- const currentIndex = keys.indexOf(page_state);
-
- // Check if there is a next key
- if (currentIndex >= 1) {
- // Get the next key
- const lastKey = keys[currentIndex - 1];
- const lastFn = pages[lastKey];
- lastFn();
- }
-}
\ No newline at end of file
+export default NavButtons;
\ No newline at end of file
diff --git a/js/components/prompt/prompt.css b/js/components/prompt/prompt.css
index 968f9eb..361de58 100644
--- a/js/components/prompt/prompt.css
+++ b/js/components/prompt/prompt.css
@@ -1,14 +1,31 @@
#prompt-div {
display: none;
text-align: center;
- width: 60%;
- margin-left: 20%;
+ width: 80%;
+ margin: auto;
}
-#prompt {
+h3#prompt {
text-align: center;
+ font-size : 1.5em;
}
-#option1 { background-color: var(--green);}
-#option2 {background-color: var(--orange);}
-#option1, #option2 { font-size: 1.5em; }
\ No newline at end of file
+#option1, #option2 {
+ font-size: 1.5em;
+ border-color: var(--citygreen);
+ border-width: 2px;
+ background-color: var(--white);
+ color: var(--citygreen);
+}
+
+#option1:hover, #option2:hover {
+ background-color: var(--spiritgreen);
+ color: white;
+}
+
+#option2.clicked, #option1.clicked {
+ font-weight: bold;
+ background-color: var(--spiritgreen);
+ color: white;
+ border-width: 3;
+}
diff --git a/js/components/prompt/prompt.js b/js/components/prompt/prompt.js
index 0c38dbc..0f25585 100644
--- a/js/components/prompt/prompt.js
+++ b/js/components/prompt/prompt.js
@@ -1,29 +1,18 @@
-export function showPrompt(){
- document.getElementById("prompt-div").style.display = "block";
-}
-
-export function hidePrompt(){
- document.getElementById('prompt-div').style.display = 'none';
-}
-
-export function updatePrompt(prompt){
- document.getElementById('prompt').textContent = prompt;
-}
-
-export function updatePromptButtons(option1, option2){
- document.getElementById('option1').textContent = option1;
- document.getElementById('option2').textContent = option2;
- // make buttons visible
- document.getElementById('option1').style.display = 'inline';
- document.getElementById('option2').style.display = 'inline';
-}
+import Text from "./subcomponents/text.js";
+import Buttons from "./subcomponents/buttons.js";
-export function addPromptButtonAction(button_id, action_fn){
- document.getElementById(button_id).addEventListener('click', action_fn);
+export const Prompt = {
+ Text : Text,
+ Buttons : Buttons,
+ hide : function(){
+ Text.hide();
+ Buttons.hide();
+ },
+ show : function(){
+ Text.show();
+ Buttons.show();
+ }
}
-export function hidePromptButtons(){
- document.getElementById('option1').style.display = 'none';
- document.getElementById('option2').style.display = 'none';
-}
\ No newline at end of file
+export default Prompt
\ No newline at end of file
diff --git a/js/components/prompt/subcomponents/buttons.js b/js/components/prompt/subcomponents/buttons.js
new file mode 100644
index 0000000..88b564a
--- /dev/null
+++ b/js/components/prompt/subcomponents/buttons.js
@@ -0,0 +1,67 @@
+function showPromptButton(id){
+ // make buttons visible
+ document.getElementById(id).style.display = 'inline';
+}
+
+function updatePromptButton(id, text){
+ document.getElementById(id).textContent = text;
+ showPromptButton(id);
+}
+
+function hidePromptButton(id){
+ document.getElementById(id).style.display = 'none';
+}
+
+function unclickAll(){
+ document.getElementById('option1').classList.remove('clicked');
+ document.getElementById('option2').classList.remove('clicked');
+}
+
+function applyClickedStyle(button){
+ unclickAll();
+ button.classList.add('clicked');
+}
+
+function addPromptButtonAction(button_id, action_fn){
+ const buttonElement = document.getElementById(button_id);
+ buttonElement.addEventListener('click', action_fn);
+ buttonElement.addEventListener('click', function(){
+ applyClickedStyle(this);
+ });
+}
+
+function removePromptButtonAction(button_id, action_fn){
+ document.getElementById(button_id).removeEventListener('click', action_fn);
+}
+
+export const Left = {
+ show : function() { showPromptButton('option1') },
+ hide : function() { hidePromptButton('option1') },
+ updateText : function(text) { updatePromptButton('option1', text) },
+ addAction : function(action_fn) { addPromptButtonAction('option1', action_fn) },
+ removeAction : function(action_fn) { removePromptButtonAction('option1', action_fn) }
+}
+
+export const Right = {
+ show : function() { showPromptButton('option2') },
+ hide : function() { hidePromptButton('option2') },
+ updateText : function(text) { updatePromptButton('option2', text) },
+ addAction : function(action_fn) { addPromptButtonAction('option2', action_fn) },
+ removeAction : function(action_fn) { removePromptButtonAction('option2', action_fn) }
+}
+
+export const Buttons = {
+ Left : Left,
+ Right : Right,
+ show : function() {
+ showPromptButton('option1');
+ showPromptButton('option2');
+ },
+ hide : function() {
+ hidePromptButton('option1');
+ hidePromptButton('option2');
+ },
+ reset : unclickAll
+}
+
+export default Buttons;
\ No newline at end of file
diff --git a/js/components/prompt/subcomponents/text.js b/js/components/prompt/subcomponents/text.js
new file mode 100644
index 0000000..b693ad8
--- /dev/null
+++ b/js/components/prompt/subcomponents/text.js
@@ -0,0 +1,21 @@
+function showPrompt(){
+ document.getElementById("prompt-div").style.display = "block";
+}
+
+function hidePrompt(){
+ document.getElementById('prompt-div').style.display = 'none';
+}
+
+
+function updatePrompt(prompt){
+ document.getElementById('prompt').textContent = prompt;
+ showPrompt();
+}
+
+export const Text = {
+ show : showPrompt,
+ hide : hidePrompt,
+ update : function(text) { updatePrompt(text) }
+}
+
+export default Text;
\ No newline at end of file
diff --git a/js/components/sidebar/sidebar.css b/js/components/sidebar/sidebar.css
index 6f795d2..b4aaca8 100644
--- a/js/components/sidebar/sidebar.css
+++ b/js/components/sidebar/sidebar.css
@@ -9,4 +9,36 @@
.stat {
font-weight: bold;
+}
+
+#sidebar-panel {
+ height: 100%; /* Full height of the viewport */
+ position: fixed; /* Fixed Sidebar (stay in place on scroll) */
+ z-index: 1; /* Stay on top */
+ top: 0; /* Stay at the top */
+ right: 0; /* Sidebar appears on the left */
+ background-color: #FFFFFF; /* White background color */
+ overflow-x: hidden; /* Disable horizontal scroll */
+ padding: 20px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Add shadow for some depth */
+ border-right: 2px solid #DDDDDD; /* Right border */
+ border-radius: 15px 0px 0px 15px; /* Rounded corners on the left */
+ /* margin-top: var(--header-height); */
+ width: var(--sidebar-width);
+ margin: 0px;
+ }
+
+.sidebar a {
+ padding: 10px 15px;
+ text-decoration: none;
+ font-size: 18px;
+ color: #818181;
+ display: block;
+ transition: 0.3s;
+}
+
+#sidebar-title {
+ color: var(--darkGray);
+ font-weight: bold;
+ border-bottom: 1px solid var(--citygreen);
}
\ No newline at end of file
diff --git a/js/components/sidebar/sidebar.js b/js/components/sidebar/sidebar.js
index a48a90e..0680695 100644
--- a/js/components/sidebar/sidebar.js
+++ b/js/components/sidebar/sidebar.js
@@ -1,14 +1,38 @@
-import { formatCurrency } from "../../utils/utils.js";
+import { formatCurrency } from "../../utils/common_utils.js";
+import { TARGET } from "../../init.js";
-export function hideSideBar(){
+// Assuming you have a CSS variable --main-color defined on the :root
+const root = document.documentElement;
+const sideBarWidth = getComputedStyle(root).getPropertyValue('--sidebar-width').trim();
+
+function hideSidebar() {
document.getElementById('sidebar-panel').style.display = 'none';
- document.getElementById('main-panel').className = 'col-md-12';
+ document.getElementById('main-panel').style.width = '100%';
+ document.querySelector('header').style.width = '100%'
+}
+
+function showSidebar() {
+ const sidebar = document.getElementById('sidebar-panel');
+ const mainPanel = document.getElementById('main-panel');
+ const header = document.querySelector('header');
+
+ sidebar.style.display = 'block'; // Show the sidebar
+
+ // Calculate the remaining width for the main panel and header
+ var contentWidth = document.documentElement.clientWidth;
+ mainPanel.style.width = `${contentWidth - parseInt(sideBarWidth, 10)}px`;
+ header.style.width = `${contentWidth - parseInt(sideBarWidth, 10)}px`;
+
+ // add target to sidebar
+ addTarget(TARGET);
+
+ // add event listener to resize content if window is adjusted
+ window.addEventListener('resize', showSidebar);
}
-export function showSideBar(){
- document.getElementById('sidebar-panel').className = 'col-md-2';
- document.getElementById('sidebar-panel').style.display = 'block';
- document.getElementById('main-panel').className = 'col-md-10';
+
+function updateSidebarTitle(new_title){
+ document.getElementById('sidebar-title').textContent = new_title;
}
function updateSidebarStat(stat_id, new_figure){
@@ -23,17 +47,17 @@ function replaceSidebarStat(stat_id, new_figure){
span.textContent = formatCurrency(new_figure);
}
-export function incrementSidebarStat(stat_id, new_figure){
+function incrementSidebarStat(stat_id, new_figure){
updateSidebarStat(stat_id, fetchStat(stat_id) + new_figure)
}
-export function fetchStat(stat_id){
+function fetchStat(stat_id){
const stat = document.querySelector(`#${stat_id} .stat`);
return parseFloat(stat.getAttribute('value')) || 0;
}
// Function to update the display of the current and supp variables
-export function updateTotals() {
+function updateTotals() {
// update bottom lines
let supp_total = -fetchStat('supp-revenue') +
fetchStat('supp-personnel') +
@@ -50,10 +74,26 @@ export function updateTotals() {
document.querySelector('#baseline-total .stat').style.color = "green";
}
if(baseline_total > target){
- document.getElementById('#baseline-total .stat').style.color = "red";
+ document.querySelector('#baseline-total .stat').style.color = "red";
}
}
-export function addTarget(target){
+function addTarget(target){
replaceSidebarStat('target', target);
-}
\ No newline at end of file
+}
+
+function updateTitle(title){
+ document.querySelector('#sidebar-title').textContent = title;
+}
+
+const Sidebar = {
+ hide: hideSidebar,
+ show: showSidebar,
+ updateTitle: updateSidebarTitle,
+ updateStat: updateSidebarStat,
+ incrementStat: incrementSidebarStat,
+ addTarget: addTarget,
+ updateTitle: updateTitle
+};
+
+export default Sidebar;
\ No newline at end of file
diff --git a/js/components/table/subcomponents/buttons.js b/js/components/table/subcomponents/buttons.js
new file mode 100644
index 0000000..8481474
--- /dev/null
+++ b/js/components/table/subcomponents/buttons.js
@@ -0,0 +1,107 @@
+import Rows from './rows.js'
+
+function hideButton(className){
+ return function() {
+ var buttons = document.getElementsByClassName(className);
+ for (var i = 0; i < buttons.length; i++) {
+ buttons[i].style.display = 'none';
+ }
+ }
+}
+
+function showButton(className){
+ return function() {
+ var buttons = document.getElementsByClassName(className);
+ for (var i = 0; i < buttons.length; i++) {
+ buttons[i].style.display = 'inline';
+ }
+ }
+}
+
+function updateButtonText(className, text){
+ document.querySelector(`.${className}`).textContent = text;
+}
+
+// EDIT button
+
+function handleRowEdit(makeRowEditable, updateCallback){
+ // attach an event listener to each edit button in every row
+ var editButtons = document.getElementsByClassName('btn-edit');
+ for (var i = 0; i < editButtons.length; i++) {
+ editButtons[i].addEventListener('click', async function(event) {
+ // Determine what was clicked on within the table
+ var rowToEdit = event.target.closest('tr');
+ // mark row as being edited
+ rowToEdit.classList.add('active-editing');
+
+ // turn relevant entries into textboxes
+ makeRowEditable();
+
+ // hide edit buttons
+ Edit.hide();
+ initializeConfirmButton(updateCallback);
+
+ });
+ };
+}
+
+// Confirm button
+
+function initializeConfirmButton(updateCallback){
+ // get element and add listener for click
+ var rowToEdit = document.querySelector('.active-editing');
+ const confirm_btn = rowToEdit.querySelector(".btn-confirm");
+ // show the row's confirm button
+ confirm_btn.style.display = 'block';
+ confirm_btn.addEventListener('click', function(){;
+ // save row edits
+ Rows.saveEdits(rowToEdit);
+ // update values in sidebar
+ updateCallback();
+ // make row no longer green
+ rowToEdit.classList.remove('active-editing');
+ // show edit buttons and hide confirm buttons
+ Edit.show();
+ Confirm.hide();
+ });
+}
+
+const Edit = {
+ html: '',
+ hide: hideButton('btn-edit'),
+ show: showButton('btn-edit'),
+ init : function(makeRowEditable, updateCallback){
+ handleRowEdit(makeRowEditable, updateCallback)
+ }
+};
+
+const Delete = {
+ html: '',
+ hide: hideButton('btn-delete'),
+ show: showButton('btn-delete')
+};
+
+const Confirm = {
+ html: '',
+ hide: hideButton('btn-confirm'),
+ show: showButton('btn-confirm')
+};
+
+const AddRow = {
+ hide: hideButton('btn-add'),
+ show: showButton('btn-add'),
+ updateText: function(text){
+ updateButtonText('btn-add', text);
+ }
+};
+
+export const Buttons = {
+ Delete: Delete,
+ Edit : Edit,
+ Confirm : Confirm,
+ AddRow : AddRow,
+ edit_confirm_btns : Edit.html + Confirm.html ,
+ all_btns : Delete.html + Edit.html + Confirm.html
+}
+
+export default Buttons;
\ No newline at end of file
diff --git a/js/components/table/subcomponents/cells.js b/js/components/table/subcomponents/cells.js
new file mode 100644
index 0000000..82d6404
--- /dev/null
+++ b/js/components/table/subcomponents/cells.js
@@ -0,0 +1,64 @@
+import { formatCurrency } from "../../../utils/common_utils.js";
+import Dropdown from "../../form/subcomponents/dropdown.js";
+
+// return cell value attribute or 0 if it does not exist
+function getCellValue(row, className) {
+ var cell = row.querySelector(`.${className}`);
+ var cellValue = cell ? cell.getAttribute('value') : null;
+ return cellValue ? parseFloat(cellValue) : 0;
+}
+
+// return text in cell
+function getCellText(row, className) {
+ var cell = row.querySelector(`.${className}`);
+ return cell.textContent;
+}
+
+function updateTableCell(row, col_class, new_value){
+ const cell = row.querySelector(`.${col_class}`);
+ cell.setAttribute('value', new_value);
+ cell.textContent = formatCurrency(new_value);
+}
+
+function createEditableCell(cellClass){
+ // get cell
+ const cell = document.querySelector(`.active-editing td.${cellClass}`);
+ // Create an input element to edit the value
+ var textbox = document.createElement('input');
+ textbox.type = 'text';
+ textbox.value = cell.textContent;
+ // Clear the current content and append the textbox to the cell
+ cell.innerHTML = '';
+ cell.appendChild(textbox);
+}
+
+async function createSelectCell(cellClass, json_filepath){
+ // get cell
+ const cell = document.querySelector(`.active-editing td.${cellClass}`);
+ // add service dropdown
+ const serviceDropdown = await Dropdown.createFromJSON(json_filepath);
+ serviceDropdown.value = cell.textContent;
+ // Clear the current content and append the textbox to the cell
+ cell.innerHTML = '';
+ cell.appendChild(serviceDropdown);
+}
+
+const Cell = {
+ getValue: function(row, className) {
+ return getCellValue(row, className);
+ },
+ getText: function(row, className) {
+ return getCellText(row, className);
+ },
+ updateValue: function(row, col_class, new_value) {
+ updateTableCell(row, col_class, new_value);
+ },
+ createTextbox : function(className) {
+ createEditableCell(className)
+ },
+ createDropdown : function(className, json_filepath){
+ createSelectCell(className, json_filepath);
+ },
+};
+
+export default Cell;
\ No newline at end of file
diff --git a/js/components/table/subcomponents/columns.js b/js/components/table/subcomponents/columns.js
new file mode 100644
index 0000000..02faf57
--- /dev/null
+++ b/js/components/table/subcomponents/columns.js
@@ -0,0 +1,130 @@
+import { formatCurrency } from "../../../utils/common_utils.js";
+
+// position is index at which new column will be inserted
+function addCol(position, htmlContent = '', headerTitle = '') {
+ // Get the table element by its ID
+ let table = document.getElementById('main-table');
+
+ // Validate position
+ let maxPosition = table.rows[0].cells.length;
+ if (position < 0 || position > maxPosition) {
+ console.error(`Position ${position} is out of bounds.`);
+ return;
+ }
+
+ // Insert the header if provided
+ let thead = table.tHead;
+ if (headerTitle && thead) {
+ let th = document.createElement('th');
+ th.innerHTML = headerTitle; // Use innerHTML to insert HTML content
+ thead.rows[0].insertBefore(th, thead.rows[0].cells[position]);
+ }
+
+ // Insert new cells into each row of the table body
+ let tbody = table.tBodies[0];
+ if (tbody) {
+ for (let i = 0; i < tbody.rows.length; i++) {
+ let row = tbody.rows[i];
+ let td = document.createElement('td');
+ td.innerHTML = htmlContent; // Use innerHTML to insert HTML content
+ row.insertBefore(td, row.cells[position]);
+ }
+ }
+}
+
+function ncols(){
+ const table = document.getElementById('main-table');
+ // Ensure that the row exists before counting the columns
+ return table.rows[0].cells.length;
+}
+
+function addColToEnd(htmlContents = [], headerTitle = ''){
+ // count columns and add new column to the end
+ const position = ncols('main-table');
+ addCol(position, htmlContents, headerTitle);
+}
+
+function assignClassToColumn(headerName, className) {
+ // Get the table element by its ID
+ let table = document.getElementById('main-table');
+
+ // Find the index of the column by its header name
+ const thead = table.tHead;
+ if (!thead || thead.rows.length === 0) {
+ console.error('The table header is not found or has no rows.');
+ return;
+ }
+
+ let headerCellIndex = -1;
+ const headerCells = thead.rows[0].cells; // Assuming the first row contains header cells (- -
-
Baseline
-
-
-
- FY26 target:
-
-
-
- Projected revenue:
-
-
-
- Personnel cost:
-
-
-
- Non-personnel cost:
-
-
-
- Total baseline:
-
-
- -
Supplemental
-
-
+
- Revenue:
-
-
-
- Personnel cost:
-
-
-
- Non-personnel cost:
-
-
-
- Total supplemental:
-
-
- +
Supplemental
+
+
+ Revenue:
+
+
+
+ Personnel cost:
+
+
+
+ Non-personnel cost:
+
+
+
+ Total supplemental:
+
-
-
-
-
- `;
-}
\ No newline at end of file
diff --git a/js/pages/05_nonpersonnel/personnel-table.js b/js/pages/05_nonpersonnel/personnel-table.js
deleted file mode 100644
index 6b3abc7..0000000
--- a/js/pages/05_nonpersonnel/personnel-table.js
+++ /dev/null
@@ -1,93 +0,0 @@
-
-// Manage clicks in the action area of the personnel table
-function handleActionClick(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('.cost').getAttribute('value'));
- if (rowClass == "keep"){
- personnel_baseline -= salary;
- } else if (rowClass == "supp"){
- personnel_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('.cost').getAttribute('value'));
- if (rowClass == "keep"){
- personnel_baseline -= salary
- };
- if (rowClass != "supp"){
- personnel_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('.cost').getAttribute('value'));
- if (rowClass == "supp"){
- personnel_supp -= salary;
- } ;
- if (rowClass != "keep"){
- personnel_baseline += salary;
- } ;
- updateDisplay();
- }
-}
-
-// Add row for personnel table
-function addRow() {
- let table_id = "employee-table"
- var table = document.getElementById(table_id);
- var newRow = table.insertRow(-1);
- // var newNameCell = newRow.insertCell(0);
- // count number of table columns using jQuery
- let key = "#" + table_id + " tr th";
- let cols = $(key).length;
- for (let i = 0; i < cols-2; i++) {
- var nextCell = newRow.insertCell(i);
- createEditableCell(nextCell, 'value');
- }
-
- // Cost cell will always be second to last
- var costCell = newRow.insertCell(cols-2);
- createEditableCell(costCell, 'value', formatCurrency, updateDisplay, validateNumber);
- costCell.classList.add('cost')
-
- // Last cell is the action cell with 3 buttons
- var actionCell = newRow.insertCell(cols-1);
- actionCell.innerHTML = `
-
-
-
-
-
- `;
-}
\ No newline at end of file
diff --git a/js/utils/utils.js b/js/utils/archive/archived_fns.js
similarity index 83%
rename from js/utils/utils.js
rename to js/utils/archive/archived_fns.js
index 6c05a7e..2450097 100644
--- a/js/utils/utils.js
+++ b/js/utils/archive/archived_fns.js
@@ -1,27 +1,3 @@
-// Function to format number as currency
-export const formatCurrency = (amount, return_zero = false) => {
- var amount = parseFloat(amount);
- if (amount == NaN){
- return "$ -"
- }
- if (amount < 0){
- return '($' + amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,') + ')';
- } else if (amount == 0) {
- if (return_zero){
- return '$0';
- }
- return "$ -"
- }
- return '$' + amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
-} ;
-
-// function to convert formatted number to a float
-const unformatCurrency = (formattedAmount) => {
- // Remove any currency symbols and commas
- let numericalPart = formattedAmount.replace(/[^0-9.-]+/g, "");
- return parseFloat(numericalPart);
-};
-
/**
* Transforms a specified cell into an editable element by attaching an input field.
* Once the editing is committed, the new value is saved in the specified attribute
diff --git a/js/utils/common_utils.js b/js/utils/common_utils.js
new file mode 100644
index 0000000..7c5452e
--- /dev/null
+++ b/js/utils/common_utils.js
@@ -0,0 +1,35 @@
+// Function to format number as currency
+export const formatCurrency = (amount, return_zero = false) => {
+ var amount = Math.round(parseFloat(amount));
+ if (amount == NaN){
+ return "$ -"
+ }
+ if (amount < 0){
+ return '($' + amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ')';
+ } else if (amount == 0) {
+ if (return_zero){
+ return '$0';
+ }
+ return "$ -"
+ }
+ return '$' + amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+} ;
+
+// function to convert formatted number to a float
+export const unformatCurrency = (formattedAmount) => {
+ // Remove any currency symbols and commas
+ let numericalPart = formattedAmount.replace(/[^0-9.-]+/g, "");
+ return parseFloat(numericalPart);
+};
+
+export function displayWithCommas(value) {
+ return formatCurrency(value).replace('$', '');
+}
+
+function delay(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+export async function pauseExecution(seconds) {
+ await delay(seconds * 1000); // convert to milliseconds
+}
\ No newline at end of file
diff --git a/js/utils/data-handlers.js b/js/utils/data-handlers.js
deleted file mode 100644
index 4d00f1f..0000000
--- a/js/utils/data-handlers.js
+++ /dev/null
@@ -1,47 +0,0 @@
-export function loadJSONIntoTable(jsonFilePath, tableId) {
- return fetch(jsonFilePath)
- .then(response => {
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.json();
- })
- .then(data => {
- if(Array.isArray(data)) {
- const table = document.getElementById(tableId);
- const thead = table.querySelector('thead');
- const tbody = table.querySelector('tbody');
-
- // Clear any existing content
- thead.innerHTML = '';
- tbody.innerHTML = '';
-
- // Create table header row
- const headerRow = document.createElement('tr');
- Object.keys(data[0]).forEach(key => {
- const header = document.createElement('th');
- header.textContent = key;
- headerRow.appendChild(header);
- });
- thead.appendChild(headerRow);
-
- // Create table body rows
- data.forEach(item => {
- const row = document.createElement('tr');
- Object.values(item).forEach(val => {
- const cell = document.createElement('td');
- cell.textContent = val;
- row.appendChild(cell);
- });
- tbody.appendChild(row);
- });
-
- } else {
- console.error('The provided JSON file does not contain an array of objects.');
- }
- })
- .catch(error => {
- console.error('Failed to load and parse the JSON file:', error);
- });
- }
-
\ No newline at end of file
diff --git a/js/utils/data_utils/JSON_data_handlers.js b/js/utils/data_utils/JSON_data_handlers.js
new file mode 100644
index 0000000..205a97a
--- /dev/null
+++ b/js/utils/data_utils/JSON_data_handlers.js
@@ -0,0 +1,12 @@
+export async function fetchJSON(jsonFilePath) {
+ return fetch(jsonFilePath)
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ return response.json();
+ });
+}
+
+
+
\ No newline at end of file
diff --git a/js/utils/excel-export.js b/js/utils/data_utils/excel_export.js
similarity index 100%
rename from js/utils/excel-export.js
rename to js/utils/data_utils/excel_export.js
diff --git a/js/utils/storage-handlers.js b/js/utils/data_utils/local_storage_handlers.js
similarity index 100%
rename from js/utils/storage-handlers.js
rename to js/utils/data_utils/local_storage_handlers.js
diff --git a/js/views/00_welcome/helpers.js b/js/views/00_welcome/helpers.js
new file mode 100644
index 0000000..3053bb8
--- /dev/null
+++ b/js/views/00_welcome/helpers.js
@@ -0,0 +1,24 @@
+import Subtitle from '../../components/header/header.js'
+import Welcome from '../../components/welcome/welcome.js'
+import Body from '../../components/body/body.js'
+
+import { loadNewInitiatives } from '../06_new_initiatives/main.js'
+import { loadSummaryPage } from '../07_summary/main.js'
+import { loadBaselineLandingPage } from '../02_baseline_landing_page/main.js'
+import { loadUploadPage } from '../01_upload/main.js'
+
+export function initializePageView(){
+ // page set up
+ Body.reset();
+ Subtitle.update("Welcome");
+ Welcome.show();
+}
+
+export function addLinks(){
+ // initialize links in buttons
+ document.getElementById('step-upload').addEventListener('click', loadUploadPage)
+ document.getElementById('step-initiatives').addEventListener('click', loadNewInitiatives)
+ document.getElementById('step-revenue').addEventListener('click', loadBaselineLandingPage)
+ document.getElementById('step-finish').addEventListener('click', loadSummaryPage)
+
+}
diff --git a/js/pages/00_welcome/main.js b/js/views/00_welcome/main.js
similarity index 68%
rename from js/pages/00_welcome/main.js
rename to js/views/00_welcome/main.js
index 401addc..49441b1 100644
--- a/js/pages/00_welcome/main.js
+++ b/js/views/00_welcome/main.js
@@ -1,5 +1,5 @@
-import { updatePageState } from '../../utils/storage-handlers.js'
+import { updatePageState } from '../../utils/data_utils/local_storage_handlers.js'
import { initializePageView, addLinks } from './helpers.js'
export function initializeWelcomePage(){
diff --git a/js/views/01_upload/helpers.js b/js/views/01_upload/helpers.js
new file mode 100644
index 0000000..1ee56ca
--- /dev/null
+++ b/js/views/01_upload/helpers.js
@@ -0,0 +1,27 @@
+import Subtitle from '../../components/header/header.js'
+import Prompt from '../../components/prompt/prompt.js'
+import NavButtons from '../../components/nav_buttons/nav_buttons.js'
+import Body from "../../components/body/body.js";
+
+export function initializePageView() {
+
+ // remove fund selection
+ localStorage.setItem("fund", '');
+
+ // prepare page view
+ Body.reset();
+ NavButtons.show();
+
+ // update page text
+ Subtitle.update('Excel Upload');
+
+ // TODO: update to make upload actually work
+ Prompt.Text.update(`Placeholder for Excel Upload`);
+ Prompt.Buttons.Left.updateText('Upload');
+ Prompt.Buttons.Left.show();
+ Prompt.Buttons.Left.addAction(uploadExcelAction);
+}
+
+function uploadExcelAction() {
+ NavButtons.Next.enable();
+}
\ No newline at end of file
diff --git a/js/views/01_upload/main.js b/js/views/01_upload/main.js
new file mode 100644
index 0000000..cd4e55a
--- /dev/null
+++ b/js/views/01_upload/main.js
@@ -0,0 +1,9 @@
+import { updatePageState } from "../../utils/data_utils/local_storage_handlers.js";
+import { initializePageView } from "./helpers.js";
+
+export function loadUploadPage(){
+ //update page state
+ updatePageState('upload');
+ initializePageView();
+
+}
\ No newline at end of file
diff --git a/js/views/02_baseline_landing_page/helpers.js b/js/views/02_baseline_landing_page/helpers.js
new file mode 100644
index 0000000..191af54
--- /dev/null
+++ b/js/views/02_baseline_landing_page/helpers.js
@@ -0,0 +1,66 @@
+
+import Subtitle from '../../components/header/header.js'
+import Prompt from '../../components/prompt/prompt.js'
+import NavButtons from '../../components/nav_buttons/nav_buttons.js'
+import Table from "../../components/table/table.js";
+import { DATA_ROOT } from "../../init.js";
+import Body from "../../components/body/body.js";
+
+const fundCols = [
+ { title: 'ID', className: 'fund-id' },
+ { title: 'Name', className: 'fund-name' },
+];
+
+export function preparePageView(){
+
+ localStorage.setItem("fund", '');
+
+ // prepare page view
+ Body.reset();
+ NavButtons.show();
+
+ // update page text
+ Subtitle.update('Baseline Budget Request');
+ // TODO: update to make dynamic
+ Prompt.Text.update(`We will now ask you a series of questions about your BASELINE budget request.
+ At the end, we will ask you about any new initiatives (ie. supplemental requests).
+ Select one of your funds to begin.`);
+}
+
+function allowRowSelection(){
+ var tableRows = document.querySelectorAll("tbody tr");
+ tableRows.forEach(function(row) {
+ row.addEventListener('mouseover', function() {
+ this.classList.add('hover-effect');
+ });
+ row.addEventListener('mouseout', function() {
+ this.classList.remove('hover-effect');
+ });
+ row.addEventListener('click', function() {
+ selectFund(tableRows, this);
+ });
+ });
+}
+
+export async function initializeFundTable(){
+ await Table.Data.loadFromJSON(DATA_ROOT + 'funds.json')
+ Table.adjustWidth('100%');
+ Table.show();
+ Table.Columns.assignClasses(fundCols);
+ allowRowSelection();
+}
+
+function selectFund(tableRows, selected_row){
+ // remove selected class from any other rows
+ tableRows.forEach(function(tableRow) {
+ tableRow.classList = '';
+ });
+ // add selected class to clicked row
+ selected_row.classList.add('selected');
+ // get fund and save selected fund
+ var fund = selected_row.querySelector('.fund-name').textContent;
+ localStorage.setItem("fund", fund);
+
+ // enable next step
+ NavButtons.Next.enable();
+}
\ No newline at end of file
diff --git a/js/views/02_baseline_landing_page/main.js b/js/views/02_baseline_landing_page/main.js
new file mode 100644
index 0000000..6061c56
--- /dev/null
+++ b/js/views/02_baseline_landing_page/main.js
@@ -0,0 +1,10 @@
+import { updatePageState } from "../../utils/data_utils/local_storage_handlers.js";
+import { preparePageView, initializeFundTable } from "../02_baseline_landing_page/helpers.js";
+
+
+export function loadBaselineLandingPage(){
+ //update page state
+ updatePageState('baseline-landing');
+ preparePageView();
+ initializeFundTable();
+}
diff --git a/js/views/03_revenue/helpers.js b/js/views/03_revenue/helpers.js
new file mode 100644
index 0000000..e69de29
diff --git a/js/views/03_revenue/main.js b/js/views/03_revenue/main.js
new file mode 100644
index 0000000..7dc8c50
--- /dev/null
+++ b/js/views/03_revenue/main.js
@@ -0,0 +1,40 @@
+import { updatePageState } from '../../utils/data_utils/local_storage_handlers.js'
+import Prompt from '../../components/prompt/prompt.js'
+import { formatCurrency } from '../../utils/common_utils.js'
+import { REVENUE } from '../../init.js'
+import Body from '../../components/body/body.js'
+import NavButtons from '../../components/nav_buttons/nav_buttons.js'
+import { pauseAndContinue } from '../view_logic.js'
+import Subtitle from '../../components/header/header.js'
+
+export function loadRevenuePage() {
+
+ //update page state
+ updatePageState('revenue');
+
+ // prepare page view
+ Body.reset();
+ NavButtons.show();
+
+ // update page text
+ Subtitle.update('Revenue Projections');
+ // TODO: update to make dynamic
+ Prompt.Text.update(`Your revenue projection for FY26 is ${formatCurrency(REVENUE, true)}`);
+ Prompt.Buttons.Left.updateText('Confirm');
+ Prompt.Buttons.Right.updateText("This doesn't look right");
+
+ // clicking 'confirm' will also take us to the next page
+ Prompt.Buttons.Left.addAction(pauseAndContinue);
+ // TODO: allow user to edit revenue here
+ Prompt.Buttons.Right.addAction(handleRevenueEdit);
+}
+
+function handleRevenueEdit() {
+ NavButtons.Next.enable();
+}
+
+export function cleanupRevenuePage() {
+ // remove event listeners on prompt buttons
+ Prompt.Buttons.Left.removeAction(pauseAndContinue);
+ Prompt.Buttons.Right.removeAction();
+};
\ No newline at end of file
diff --git a/js/views/04.5_OT/main.js b/js/views/04.5_OT/main.js
new file mode 100644
index 0000000..e84def6
--- /dev/null
+++ b/js/views/04.5_OT/main.js
@@ -0,0 +1,25 @@
+import Prompt from '../../components/prompt/prompt.js'
+import { updatePageState } from "../../utils/data_utils/local_storage_handlers.js";
+import Body from '../../components/body/body.js';
+import NavButtons from '../../components/nav_buttons/nav_buttons.js';
+import Subtitle from '../../components/header/header.js';
+import Sidebar from '../../components/sidebar/sidebar.js';
+
+export function loadOTPage(){
+ //update page state
+ updatePageState('overtime');
+
+ // prepare page view
+ Body.reset();
+ NavButtons.show();
+ Sidebar.show();
+
+ // just enable next for now
+ // TODO: only enable when all info is entered
+ NavButtons.Next.enable();
+
+ // update page text
+ Subtitle.update('Overtime Estimates');
+ // TODO: update to make dynamic
+ Prompt.Text.update(`This is a placeholder for the OT estimates.`);
+}
\ No newline at end of file
diff --git a/js/views/04_personnel/helpers.js b/js/views/04_personnel/helpers.js
new file mode 100644
index 0000000..91669d7
--- /dev/null
+++ b/js/views/04_personnel/helpers.js
@@ -0,0 +1,137 @@
+
+import { DATA_ROOT, FISCAL_YEAR, fringe, cola, merit } from "../../init.js"
+import Body from "../../components/body/body.js";
+import NavButtons from "../../components/nav_buttons/nav_buttons.js";
+import Subtitle from "../../components/header/header.js";
+import Form from "../../components/form/form.js";
+import Modal from "../../components/modal/modal.js";
+import Prompt from "../../components/prompt/prompt.js";
+import Table from '../../components/table/table.js'
+import Sidebar from "../../components/sidebar/sidebar.js";
+
+
+export function preparePageView(){
+ // prepare page view
+ Body.reset();
+ NavButtons.show();
+ Sidebar.show();
+ Table.adjustWidth('90%');
+ // just enable next for now
+ // TODO only enable when all info is entered
+ NavButtons.Next.enable();
+
+ // update page text
+ Subtitle.update('Personnel');
+ Prompt.Text.update(`
+ This table displays the number of FTEs in each job code for in your department's
+ current (amended) FY25 budget. To make edits to the number of positions, click the
+ "Edit" button on the row you would like to edit. The "Total Cost" column and the
+ summary sidebar will also update to reflect any edits.
+ `);
+}
+
+function assignClasses() {
+ // record columns and their classes
+ const personnelColumns = [
+ { title: 'Job Name (Type)', className: 'job-name' },
+ { title: `FY${FISCAL_YEAR} FTEs`, className: 'baseline-ftes' },
+ { title: 'Service', className: 'service' },
+ { title: 'Total Cost', className: 'total-baseline', isCost: true },
+ { title: 'Average Projected Salary', className: 'avg-salary', isCost: true }
+ ];
+
+ // assign cost classes
+ Table.Columns.assignClasses(personnelColumns)
+}
+
+function personnelRowOnEdit(){
+ Table.Cell.createTextbox('baseline-ftes');
+ Table.Cell.createDropdown('service', DATA_ROOT + 'services.json');
+}
+
+export async function initializePersonnelTable(){
+ // load table data from json
+ await Table.Data.loadFromJSON(DATA_ROOT + 'personnel_data.json');
+ //after table is loaded, fill it
+ Table.show();
+ Table.Columns.addAtEnd( '0', 'Total Cost');
+ Table.Columns.addAtEnd(Table.Buttons.edit_confirm_btns, ' ');;
+ assignClasses();
+ // add up the baseline costs and update sidebar
+ updateDisplayandTotals();
+ // activate edit buttons
+ Table.Buttons.Edit.init(personnelRowOnEdit, updateDisplayandTotals);
+ initializeRowAddition();
+}
+
+function initializeRowAddition(){
+ Table.Buttons.AddRow.updateText("Add new job");
+ Table.Buttons.AddRow.show();
+}
+
+function calculateTotalCost(ftes, avg_salary, fringe, cola, merit){
+ return ftes * avg_salary * (1 + fringe) * (1 + cola) * (1 + merit);
+}
+
+// update sidebar and also cost totals when the FTEs are edited
+function updateDisplayandTotals(){
+ // initialize
+ Sidebar.updateStat('baseline-personnel', 0);
+ Sidebar.updateStat('supp-personnel', 0);
+ // calculate for each row
+ let rows = document.getElementsByTagName('tr');
+ for (let i = 1; i < rows.length; i++){
+ // fetch values for calculations
+ let avg_salary = Table.Cell.getValue(rows[i], 'avg-salary');
+ let baseline_ftes = Table.Cell.getText(rows[i], 'baseline-ftes');
+
+ // calcuate #FTEs x average salary + COLA adjustments + merit adjustments + fringe
+ let total_baseline_cost = calculateTotalCost(baseline_ftes, avg_salary, fringe, cola, merit);
+
+ // update counter and total
+ Sidebar.incrementStat('baseline-personnel', total_baseline_cost);
+ Table.Cell.updateValue(rows[i], 'total-baseline', total_baseline_cost);
+ }
+}
+
+
+export function setUpModal() {
+ // Initialize modal
+ Modal.clear();
+ Modal.Link.add('add-btn');
+ Modal.Title.update('New job');
+}
+
+export function setUpForm() {
+ // Set up form
+ Form.new('modal-body');
+ Form.NewField.shortText('Job Name:', 'job-name', true);
+ Form.NewField.shortText('Account String:', 'account-string', true);
+ Form.SubmitButton.add();
+ // Initialize form submission to table data
+ handleFormSubmissions();
+}
+
+function handleFormSubmissions(event){
+ // initialize form submission
+
+ const modal = document.getElementById('main-modal');
+ modal.addEventListener('submit', function(event) {
+ event.preventDefault();
+ // get answers from form, hide form, show answers in table
+ const responses = Form.fetchAllResponses(event);
+ // make sure it's not an empty response
+ if (Object.values(responses)[0] != ''){
+ // change page view
+ Modal.hide();
+
+ // add data to table
+ Table.Rows.add(responses);
+ Table.show();
+ Table.Buttons.AddRow.show();
+ // TODO: save table data
+ // TODO: edit cost to show currency correctly
+ }
+
+ })
+}
diff --git a/js/views/04_personnel/main.js b/js/views/04_personnel/main.js
new file mode 100644
index 0000000..ef2b481
--- /dev/null
+++ b/js/views/04_personnel/main.js
@@ -0,0 +1,13 @@
+import { updatePageState } from "../../utils/data_utils/local_storage_handlers.js";
+import { preparePageView, initializePersonnelTable, setUpModal, setUpForm } from "./helpers.js";
+
+export function loadPersonnelPage(){
+
+ updatePageState('personnel');
+ preparePageView();
+ initializePersonnelTable();
+
+ setUpModal();
+ setUpForm();
+}
+
diff --git a/js/views/05_nonpersonnel/helpers.js b/js/views/05_nonpersonnel/helpers.js
new file mode 100644
index 0000000..5f7df25
--- /dev/null
+++ b/js/views/05_nonpersonnel/helpers.js
@@ -0,0 +1,65 @@
+import Prompt from "../../components/prompt/prompt.js";
+import Sidebar from "../../components/sidebar/sidebar.js";
+import Table from "../../components/table/table.js";
+import { DATA_ROOT } from "../../init.js";
+import Body from "../../components/body/body.js";
+import NavButtons from "../../components/nav_buttons/nav_buttons.js";
+import Subtitle from "../../components/header/header.js";
+import { displayWithCommas, unformatCurrency } from "../../utils/common_utils.js";
+
+const nonPersonnelColumns = [
+ { title: 'FY26 Request', className: 'request', isCost: true },
+ { title: 'Amount Remaining', className: 'remaining', isCost: true },
+];
+
+export function preparePageView(){
+ // prepare page view
+ Body.reset();
+ NavButtons.show();
+ Sidebar.show();
+ Table.adjustWidth('100%');
+ // update page text
+ Subtitle.update('Non-Personnel');
+ Prompt.Text.update('Select an action item for each non-personnel line item from last year.');
+
+ // just enable next for now
+ // TODO: only enable when all info is entered
+ NavButtons.Next.enable();
+}
+
+export async function initializeNonpersonnelTable(){
+ // load table data from json
+ await Table.Data.loadFromJSON(DATA_ROOT + 'nonpersonnel_data.json', 'main-table');
+ //after table is loaded, fill it
+ Table.show();
+ Table.Columns.addAtEnd(Table.Buttons.edit_confirm_btns, " ");
+ // assign cost classes
+ Table.Columns.assignClasses(nonPersonnelColumns);
+ // update sidebar
+ updateDisplayandTotals();
+ // enable editing
+ Table.Buttons.Edit.init(nonPersonnelRowOnEdit, updateDisplayandTotals);
+}
+
+function nonPersonnelRowOnEdit(){
+ // convert request to numeric from formatted currency
+ const request = document.querySelector('.active-editing > td.request');
+ request.textContent = request.textContent.replace('$', '');
+ // make it editable
+ Table.Cell.createTextbox('request');
+}
+
+// update sidebar and also cost totals when the FTEs are edited
+function updateDisplayandTotals(){
+ // initialize
+ Sidebar.updateStat('baseline-nonpersonnel', 0);
+ // calculate for each row
+ let rows = document.getElementsByTagName('tr');
+ for (let i = 1; i < rows.length; i++){
+ // fetch values for calculations
+ let request = Table.Cell.getValue(rows[i], 'request');
+
+ // update counters
+ Sidebar.incrementStat('baseline-nonpersonnel', request);
+ }
+}
diff --git a/js/views/05_nonpersonnel/main.js b/js/views/05_nonpersonnel/main.js
new file mode 100644
index 0000000..566cf77
--- /dev/null
+++ b/js/views/05_nonpersonnel/main.js
@@ -0,0 +1,9 @@
+import { updatePageState } from "../../utils/data_utils/local_storage_handlers.js";
+import { preparePageView, initializeNonpersonnelTable } from "../05_nonpersonnel/helpers.js";
+
+export function loadNonpersonnelPage(){
+
+ updatePageState('nonpersonnel');
+ preparePageView();
+ initializeNonpersonnelTable()
+}
diff --git a/js/views/06_new_initiatives/helpers.js b/js/views/06_new_initiatives/helpers.js
new file mode 100644
index 0000000..5187d84
--- /dev/null
+++ b/js/views/06_new_initiatives/helpers.js
@@ -0,0 +1,86 @@
+
+import Prompt from '../../components/prompt/prompt.js'
+import Modal from '../../components/modal/modal.js'
+import Form from '../../components/form/form.js'
+import Table from '../../components/table/table.js'
+import Body from '../../components/body/body.js'
+import NavButtons from '../../components/nav_buttons/nav_buttons.js'
+import { pauseAndContinue } from '../view_logic.js'
+import Subtitle from '../../components/header/header.js'
+
+export function initializePageView() {
+ // Prepare page view
+ Body.reset();
+ NavButtons.show();
+
+ // Load text
+ Subtitle.update('New Initiatives');
+ Prompt.Text.update('Do you have any new initiatives for FY26?');
+ Prompt.Buttons.Left.updateText('Yes');
+ Prompt.Buttons.Right.updateText('No');
+ // clicking 'no new initialitives' will also take us to the next page
+ Prompt.Buttons.Right.addAction(pauseAndContinue);
+ Prompt.Buttons.Left.addAction(NavButtons.Next.enable);
+}
+
+export function setUpModal() {
+ // Initialize modal
+ Modal.clear();
+ Modal.Link.add('option1');
+ Modal.Title.update('New initiative');
+ Modal.Link.add('add-btn');
+}
+
+export function setUpForm() {
+ // Set up form
+ Form.new('modal-body');
+ Form.NewField.shortText('Initiative Name:', 'Initiative Name', true);
+ Form.NewField.longText('Explain why this initiative is necessary and describe its potential impact.', 'Explanation', true);
+ Form.NewField.numericInput('Estimate of ADDITONAL revenue associated with this initiative?', 'Revenue', true);
+ Form.NewField.numericInput('Estimate of ADDITONAL personnel cost?', 'Personnel Cost', true);
+ Form.NewField.numericInput('Estimate of ADDITONAL nonpersonnel cost?', 'Non-personnel Cost', true);
+ Form.SubmitButton.add();
+ // Initialize form submission to table data
+ handleFormSubmissions();
+}
+
+export function setUpTable() {
+ // Set up table
+ Table.clear();
+ Table.adjustWidth('70%');
+ Table.Buttons.AddRow.updateText('Add another new initiative');
+}
+
+export function handleFormSubmissions(event){
+ // initialize form submission
+ const modal = document.getElementById('main-modal');
+ modal.addEventListener('submit', function(event) {
+ event.preventDefault();
+ // get answers from form, hide form, show answers in table
+ const responses = Form.fetchAllResponses(event);
+ // make sure it's not an empty response
+ if (Object.values(responses)[0] != ''){
+ // change page view
+ Modal.hide();
+ Prompt.hide();
+
+ // add data to table
+ Table.Rows.add(responses);
+ Table.show();
+ Table.Buttons.AddRow.show();
+ // TODO: save table data
+ // TODO: edit cost to show currency correctly
+ }
+
+ })
+}
+
+export function removeModalLinks(){
+ Modal.Link.remove('option1');
+ Modal.Link.remove('add-btn');
+}
+
+export function removePromptButtonListeners(){
+ Prompt.Buttons.Right.removeAction(pauseAndContinue);
+ Prompt.Buttons.Left.removeAction(NavButtons.Next.enable);
+}
\ No newline at end of file
diff --git a/js/views/06_new_initiatives/main.js b/js/views/06_new_initiatives/main.js
new file mode 100644
index 0000000..78039fb
--- /dev/null
+++ b/js/views/06_new_initiatives/main.js
@@ -0,0 +1,19 @@
+
+import { initializePageView, setUpModal, setUpForm, setUpTable, removeModalLinks, removePromptButtonListeners } from './helpers.js'
+import { updatePageState } from '../../utils/data_utils/local_storage_handlers.js'
+
+
+// set up page and initialize all buttons
+export function loadNewInitiatives() {
+ updatePageState('new-inits');
+ initializePageView();
+ setUpModal();
+ setUpForm();
+ setUpTable();
+}
+
+export function cleanUpInitiativesPage() {
+ removeModalLinks();
+ // remove event listeners on prompt buttons
+ removePromptButtonListeners();
+}
\ No newline at end of file
diff --git a/js/views/07_summary/main.js b/js/views/07_summary/main.js
new file mode 100644
index 0000000..0e4fbc4
--- /dev/null
+++ b/js/views/07_summary/main.js
@@ -0,0 +1,28 @@
+import { updatePageState } from "../../utils/data_utils/local_storage_handlers.js";
+import Prompt from '../../components/prompt/prompt.js'
+import { initializeWelcomePage } from "../00_welcome/main.js";
+import Body from "../../components/body/body.js";
+import Subtitle from "../../components/header/header.js";
+import { visitPage } from "../view_logic.js";
+
+export function loadSummaryPage(){
+ //update page state
+ updatePageState('summary');
+
+ // prepare page view
+ Body.reset();
+ Prompt.Buttons.Left.updateText('Download Excel');
+ Prompt.Buttons.Right.updateText('Go back and edit');
+
+ // update page text
+ Subtitle.update('Summary');
+ // TODO: update to make dynamic
+ Prompt.Text.update(`Placeholder for summary and any issues.`);
+ Prompt.Buttons.Right.addAction(returnToWelcome);
+}
+
+export function cleanUpSummaryPage(){
+ Prompt.Buttons.Right.removeAction(returnToWelcome);
+}
+
+const returnToWelcome = () => {visitPage('welcome')}
\ No newline at end of file
diff --git a/js/views/view_logic.js b/js/views/view_logic.js
new file mode 100644
index 0000000..15975be
--- /dev/null
+++ b/js/views/view_logic.js
@@ -0,0 +1,88 @@
+import { initializeWelcomePage } from './00_welcome/main.js';
+import { cleanUpInitiativesPage, loadNewInitiatives } from './06_new_initiatives/main.js'
+import { loadRevenuePage, cleanupRevenuePage } from './03_revenue/main.js'
+import { loadPersonnelPage } from './04_personnel/main.js';
+import { loadOTPage } from './04.5_OT/main.js';
+import { loadNonpersonnelPage } from './05_nonpersonnel/main.js';
+import { loadBaselineLandingPage } from './02_baseline_landing_page/main.js';
+import { cleanUpSummaryPage, loadSummaryPage } from './07_summary/main.js';
+import { loadUploadPage } from './01_upload/main.js';
+import { pauseExecution } from '../utils/common_utils.js';
+
+import { loadPageState } from '../utils/data_utils/local_storage_handlers.js';
+
+export let PAGES = {
+ 'welcome' : initializeWelcomePage,
+ 'upload' : loadUploadPage,
+ 'baseline-landing' : loadBaselineLandingPage,
+ 'revenue' : loadRevenuePage,
+ 'personnel' : loadPersonnelPage,
+ 'overtime' : loadOTPage,
+ 'nonpersonnel' : loadNonpersonnelPage,
+ 'new-inits' : loadNewInitiatives,
+ 'summary' : loadSummaryPage
+}
+
+export let CLEANUP = {
+ 'new-inits' : cleanUpInitiativesPage,
+ 'revenue' : cleanupRevenuePage,
+ 'summary' : cleanUpSummaryPage
+}
+
+export function visitPage(new_page_key){
+ // clean up from current page
+ var page_state = loadPageState();
+ if (CLEANUP[page_state]) { CLEANUP[page_state]() };
+ // Use the page_state to access and call the corresponding function from PAGES
+ if (PAGES[new_page_key]) {
+ PAGES[new_page_key](); // Invokes the function if it exists in the PAGES map
+ } else {
+ console.error(`No page initializer found for state: ${new_page_key}`);
+ }
+ PAGES[new_page_key]();
+}
+
+export function nextPage(){
+
+ var page_state = loadPageState();
+ const keys = Object.keys(PAGES);
+
+ // Find the index of the current key
+ const currentIndex = keys.indexOf(page_state);
+
+ // clean up current page
+ if (CLEANUP[page_state]) { CLEANUP[page_state]() };
+
+ // Check if there is a next key
+ if (currentIndex >= 0 && currentIndex < keys.length - 1) {
+ // Get the next key
+ const nextKey = keys[currentIndex + 1];
+ // go to that page
+ visitPage(nextKey);
+ }
+}
+
+export function lastPage(){
+
+ var page_state = loadPageState();
+ const keys = Object.keys(PAGES);
+
+ // Find the index of the current key
+ const currentIndex = keys.indexOf(page_state);
+
+ // clean up current page
+ if (CLEANUP[page_state]) { CLEANUP[page_state]() };
+
+ // Check if there is a next key
+ if (currentIndex >= 1) {
+ // Get the next key
+ const lastKey = keys[currentIndex - 1];
+ // go to that page
+ visitPage(lastKey);
+ }
+}
+
+export async function pauseAndContinue(){
+ await pauseExecution(0.5);
+ nextPage();
+}
\ No newline at end of file