From cad52c8dca38a24303212f84b368e94ac3716ed9 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Wed, 24 Jul 2024 14:42:38 -0400 Subject: [PATCH 01/24] address case where fund = null; change excel download name --- src/js/utils/data_utils/XLSX_handlers.js | 2 +- src/js/utils/data_utils/budget_data_handlers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/utils/data_utils/XLSX_handlers.js b/src/js/utils/data_utils/XLSX_handlers.js index 1bb8dae..1b16ec3 100644 --- a/src/js/utils/data_utils/XLSX_handlers.js +++ b/src/js/utils/data_utils/XLSX_handlers.js @@ -149,7 +149,7 @@ export function downloadXLSX() { // Create a link and trigger the download const link = document.createElement("a"); link.href = URL.createObjectURL(blob); - link.download = "baseline_data.xlsx"; + link.download = "Filled_Detail_Sheet.xlsx"; document.body.appendChild(link); link.click(); document.body.removeChild(link); diff --git a/src/js/utils/data_utils/budget_data_handlers.js b/src/js/utils/data_utils/budget_data_handlers.js index 0a80041..097e32b 100644 --- a/src/js/utils/data_utils/budget_data_handlers.js +++ b/src/js/utils/data_utils/budget_data_handlers.js @@ -25,7 +25,7 @@ export const FundLookupTable = { this.save({}); }, getName : function(number){ - if(number == '') { return '' }; + if(!number || !this.retrieve()) { return '' }; return this.retrieve()[number]['name']; }, listFunds : function(){ From a8919eb967d3e21ffddf25116b78f6ecd18091b9 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 10:26:36 -0400 Subject: [PATCH 02/24] add class to header cells in addition to body cells --- src/js/components/table/subcomponents/columns.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/js/components/table/subcomponents/columns.js b/src/js/components/table/subcomponents/columns.js index d6caaab..a2f2088 100644 --- a/src/js/components/table/subcomponents/columns.js +++ b/src/js/components/table/subcomponents/columns.js @@ -50,20 +50,18 @@ function assignClassToColumn(headerName, className) { // 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) { + // assign the class to the header cell + headerCells[i].classList.add(className); headerCellIndex = i; break; } } + // error check if (headerCellIndex === -1) { console.error(`No header found with name "${headerName}"`); return; From e6e2fafb1467cf1e2926e2cd9b88cca860522ba1 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 10:35:52 -0400 Subject: [PATCH 03/24] fix detail tooltip and cost class addition for columns so that it only applie to body cells --- src/js/components/table/subcomponents/columns.js | 2 +- src/js/components/tooltip/tooltip.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/js/components/table/subcomponents/columns.js b/src/js/components/table/subcomponents/columns.js index a2f2088..f641642 100644 --- a/src/js/components/table/subcomponents/columns.js +++ b/src/js/components/table/subcomponents/columns.js @@ -83,7 +83,7 @@ function addCostClass(headerName){ assignClassToColumn( headerName, 'cost'); // Get all the cells with the specified class name - const cells = document.querySelectorAll(`.cost`); + const cells = document.querySelectorAll(`td.cost`); cells.forEach(cell => { // Get the current text content of the cell and assign it to 'value' attribute diff --git a/src/js/components/tooltip/tooltip.js b/src/js/components/tooltip/tooltip.js index 2406cb3..720d5eb 100644 --- a/src/js/components/tooltip/tooltip.js +++ b/src/js/components/tooltip/tooltip.js @@ -129,28 +129,28 @@ export const Tooltip = { linkAccountStringCol : function() { // get all relevant cells - document.querySelectorAll('.account-string').forEach( (cell) => { + document.querySelectorAll('td.account-string').forEach( (cell) => { this.link(cell, showAccountString); }) }, linkSalaryCol : function() { // get all relevant cells - document.querySelectorAll('.avg-salary').forEach( (cell) => { + document.querySelectorAll('td.avg-salary').forEach( (cell) => { this.link(cell, showSalaryProjection); }) }, linkTotalPersonnelCostCol : function() { // get all relevant cells - document.querySelectorAll('.total-baseline').forEach( (cell) => { + document.querySelectorAll('td.total-baseline').forEach( (cell) => { this.link(cell, showFinalPersonnelCost); }) }, linkTotalOTCol : function() { // get all relevant cells - document.querySelectorAll('.total').forEach( (cell) => { + document.querySelectorAll('td.total').forEach( (cell) => { this.link(cell, showFICA); }) }, From a23160317463776be348a75d7c9bac5903f97b55 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 10:57:03 -0400 Subject: [PATCH 04/24] allow row addition by matching inputs to existing column classes --- src/js/components/table/subcomponents/rows.js | 17 ++++++++++++----- src/js/views/03_revenue/helpers.js | 1 + src/js/views/04_personnel/helpers.js | 11 +++++------ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/js/components/table/subcomponents/rows.js b/src/js/components/table/subcomponents/rows.js index b94861b..7a93f25 100644 --- a/src/js/components/table/subcomponents/rows.js +++ b/src/js/components/table/subcomponents/rows.js @@ -11,16 +11,23 @@ async function addNewRow(data_dictionary){ Header.add(Object.keys(data_dictionary)); } - // add row of data + // initialize new 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) { + // go through each header and add the right cell value depending on its class + let thElements = header_row.querySelectorAll('th'); + thElements.forEach( (header_cell) => { // Create new cell and add it to the row const newCell = document.createElement('td'); - newCell.textContent = cell_data; new_row.appendChild(newCell); - } + // the data has an appropriate class, add the info to the cell. Otherwise, keep empty cell + Object.keys(data_dictionary).forEach( (className) => { + if (header_cell.classList.contains(className) ){ + newCell.textContent = data_dictionary[className]; + newCell.classList.add(className); + } + }) + }); // Append the new row to the table body let tbody = table.querySelector('tbody'); diff --git a/src/js/views/03_revenue/helpers.js b/src/js/views/03_revenue/helpers.js index 4c27354..463b466 100644 --- a/src/js/views/03_revenue/helpers.js +++ b/src/js/views/03_revenue/helpers.js @@ -41,6 +41,7 @@ export function preparePageView(){ } export async function initializeRevTable(){ + console.log(document.getElementById('main-table')); // load table data from storage if(await Table.Data.load()) { //after table is loaded, fill it diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index 5dc5a59..f251385 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -114,7 +114,7 @@ export function setUpModal() { export function setUpForm() { // Set up form Form.new('modal-body'); - Form.NewField.shortText('Job Name:', 'job-name', true); + Form.NewField.shortText('Job Title:', 'job-name', true); Form.NewField.shortText('Account String:', 'account-string', true); Form.SubmitButton.add(); // Initialize form submission to table data @@ -131,10 +131,9 @@ function handleSubmitNewJob(event){ // 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 - } + Table.save(); + initializePersonnelTable(); + + } } From 95b35616243988f0e4885f3fd68befb8a003eb6f Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 11:25:39 -0400 Subject: [PATCH 05/24] #57 fix detail bug by linking and unlinking detail tooltips before/after table save --- src/js/components/table/table.js | 9 +- src/js/components/tooltip/tooltip.js | 170 ++++++++++++------------ src/js/views/03_revenue/helpers.js | 3 +- src/js/views/04_personnel/helpers.js | 2 +- src/js/views/05_overtime/helpers.js | 2 +- src/js/views/06_nonpersonnel/helpers.js | 2 +- 6 files changed, 100 insertions(+), 88 deletions(-) diff --git a/src/js/components/table/table.js b/src/js/components/table/table.js index e9f42f8..5b9fb97 100644 --- a/src/js/components/table/table.js +++ b/src/js/components/table/table.js @@ -7,6 +7,7 @@ import Header from './subcomponents/headers.js' import Rows from './subcomponents/rows.js' import Data from './subcomponents/data.js' import { saveTableData } from '../../utils/data_utils/local_storage_handlers.js' +import Tooltip from '../tooltip/tooltip.js'; function adjustTableWidth(width_pct){ const table = document.getElementById('main-table'); @@ -44,7 +45,13 @@ const Table = { clear : clearTable, hide : hideTable, show : showTable, - save : saveTableData + save : function() { + // remove the detail text + Tooltip.unlink(); + saveTableData(); + // relink, depending on page + Tooltip.linkAll(); + } } export default Table; \ No newline at end of file diff --git a/src/js/components/tooltip/tooltip.js b/src/js/components/tooltip/tooltip.js index 720d5eb..1cd0251 100644 --- a/src/js/components/tooltip/tooltip.js +++ b/src/js/components/tooltip/tooltip.js @@ -2,6 +2,7 @@ import { FISCAL_YEAR } from '../../init'; import Cell from '../table/subcomponents/cells'; import { formatCurrency } from '../../utils/common_utils'; import './tooltip.css' +import { CurrentPage } from '../../utils/data_utils/local_storage_handlers'; function hideTooltip() { document.getElementById('tooltip').style.visibility = 'hidden'; @@ -88,98 +89,103 @@ function showCPA(row){ editTooltipText(message); } -export const Tooltip = { - - hide : hideTooltip, - show : showTooltip, +function link (element, displayFn) { + + // add class to show cell with an underline, etc + element.classList.add('tooltip-cell'); + + // Create and append (detail) + const detail = document.createElement('span'); + detail.classList.add('detail'); + detail.textContent = '(detail)'; + element.appendChild(detail); + + // add event listener to show tooltip on mouseover + element.addEventListener('click', function (event) { + const row = event.target.closest('tr'); + displayFn(row); + showTooltip(); + }); + // and hide when mouse moves off + element.addEventListener('mouseout', function () { + hideTooltip(); + }); + // Update tooltip position on mouse move + element.addEventListener('mousemove', function (event) { + const tooltip = document.getElementById('tooltip'); + tooltip.style.top = (event.clientY + 10) + 'px'; + tooltip.style.left = (event.clientX + 10) + 'px'; + }); +} - link : function(element, displayFn) { - - // add class to show cell with an underline, etc - element.classList.add('tooltip-cell'); - - // Create and append the Font Awesome info icon - // const infoIcon = document.createElement('i'); - // infoIcon.classList.add('fas', 'fa-info-circle', 'info-icon'); - // element.appendChild(infoIcon); - - // Create and append (detail) - const detail = document.createElement('span'); - detail.classList.add('detail'); - detail.textContent = '(detail)'; - element.appendChild(detail); - - // add event listener to show tooltip on mouseover - element.addEventListener('click', function (event) { - const row = event.target.closest('tr'); - displayFn(row); - showTooltip(); - }); - // and hide when mouse moves off - element.addEventListener('mouseout', function () { - hideTooltip(); - }); - // Update tooltip position on mouse move - element.addEventListener('mousemove', function (event) { - const tooltip = document.getElementById('tooltip'); - tooltip.style.top = (event.clientY + 10) + 'px'; - tooltip.style.left = (event.clientX + 10) + 'px'; - }); - }, - - linkAccountStringCol : function() { - // get all relevant cells - document.querySelectorAll('td.account-string').forEach( (cell) => { - this.link(cell, showAccountString); - }) - }, +function linkAccountStringCol() { + // get all relevant cells + document.querySelectorAll('td.account-string').forEach( (cell) => { + link(cell, showAccountString); + }) +} - linkSalaryCol : function() { - // get all relevant cells - document.querySelectorAll('td.avg-salary').forEach( (cell) => { - this.link(cell, showSalaryProjection); - }) - }, +function linkSalaryCol() { + // get all relevant cells + document.querySelectorAll('td.avg-salary').forEach( (cell) => { + link(cell, showSalaryProjection); + }) +} - linkTotalPersonnelCostCol : function() { - // get all relevant cells - document.querySelectorAll('td.total-baseline').forEach( (cell) => { - this.link(cell, showFinalPersonnelCost); - }) - }, +function linkTotalPersonnelCostCol() { + // get all relevant cells + document.querySelectorAll('td.total-baseline').forEach( (cell) => { + link(cell, showFinalPersonnelCost); + }) +} - linkTotalOTCol : function() { - // get all relevant cells - document.querySelectorAll('td.total').forEach( (cell) => { - this.link(cell, showFICA); - }) - }, +function linkTotalOTCol() { + // get all relevant cells + document.querySelectorAll('td.total').forEach( (cell) => { + link(cell, showFICA); + }) +} - linkCPACol : function() { - // get all relevant cells - document.querySelectorAll('.cpa').forEach( (cell) => { - this.link(cell, showCPA); - }) - }, +function linkCPACol() { + // get all relevant cells + document.querySelectorAll('.cpa').forEach( (cell) => { + link(cell, showCPA); + }) +} - linkAllPersonnel : function() { - this.linkAccountStringCol(); - this.linkSalaryCol(); - this.linkTotalPersonnelCostCol(); - }, +export const Tooltip = { - linkAllOvertime : function() { - // this.linkAccountStringCol(); - this.linkTotalOTCol(); - }, + hide : hideTooltip, + show : showTooltip, - linkAllNP : function() { - this.linkAccountStringCol(); - this.linkCPACol(); + linkAll : () => { + switch(CurrentPage.load()){ + case 'personnel' : + linkAccountStringCol(); + linkSalaryCol(); + linkTotalPersonnelCostCol(); + break; + case 'overtime': + linkTotalOTCol(); + break; + case 'nonpersonnel': + linkAccountStringCol(); + linkCPACol(); + break; + case 'revenue': + linkAccountStringCol(); + break; + default: + break; + + } }, - linkAllRevenue : function() { - this.linkAccountStringCol(); + unlink : function() { + let details = document.querySelectorAll('.detail'); + details.forEach( (span) => { + span.textContent = ''; + }) } } diff --git a/src/js/views/03_revenue/helpers.js b/src/js/views/03_revenue/helpers.js index 463b466..a0f7d88 100644 --- a/src/js/views/03_revenue/helpers.js +++ b/src/js/views/03_revenue/helpers.js @@ -41,7 +41,6 @@ export function preparePageView(){ } export async function initializeRevTable(){ - console.log(document.getElementById('main-table')); // load table data from storage if(await Table.Data.load()) { //after table is loaded, fill it @@ -52,7 +51,7 @@ export async function initializeRevTable(){ // enable editing Table.Buttons.Edit.init(revRowOnEdit, Table.save); // show info boxes on click - Tooltip.linkAllRevenue(); + Tooltip.linkAll(); } else { Prompt.Text.update('No revenues for this fund.') } diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index f251385..e402fec 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -71,7 +71,7 @@ export async function initializePersonnelTable(){ Table.Buttons.Edit.init(personnelRowOnEdit, updateDisplayandTotals); initializeRowAddition(); // Link up tooltips to display more info on hover - Tooltip.linkAllPersonnel(); + Tooltip.linkAll(); } else { Prompt.Text.update('No personnel expenditures for this fund.') } diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index f1874d4..184482b 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -65,7 +65,7 @@ export async function initializeOTTable(){ // activate edit buttons Table.Buttons.Edit.init(OTRowOnEdit, updateDisplayandTotals); // wire up tooltips to show info on click - Tooltip.linkAllOvertime(); + Tooltip.linkAll(); } else { Prompt.Text.update('No overtime expenditures for this fund.') } diff --git a/src/js/views/06_nonpersonnel/helpers.js b/src/js/views/06_nonpersonnel/helpers.js index 000078d..ae4b822 100644 --- a/src/js/views/06_nonpersonnel/helpers.js +++ b/src/js/views/06_nonpersonnel/helpers.js @@ -49,7 +49,7 @@ export async function initializeNonpersonnelTable(){ // enable editing Table.Buttons.Edit.init(nonPersonnelRowOnEdit, Table.save); // show info boxes on click - Tooltip.linkAllNP(); + Tooltip.linkAll(); } else { Prompt.Text.update('No non-personnel expenditures for this fund.') } From 5984c727b515172a47a88832f664c323fc81bb97 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 13:44:25 -0400 Subject: [PATCH 06/24] #57 move tooltip logic (mostly) to table save function --- src/js/components/form/form.css | 3 +-- src/js/components/form/subcomponents/dropdown.js | 9 --------- src/js/components/tooltip/tooltip.js | 2 +- src/js/views/04_personnel/helpers.js | 3 +-- src/js/views/05_overtime/helpers.js | 2 -- src/js/views/06_nonpersonnel/helpers.js | 2 +- 6 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/js/components/form/form.css b/src/js/components/form/form.css index 6dd8620..3e89551 100644 --- a/src/js/components/form/form.css +++ b/src/js/components/form/form.css @@ -17,6 +17,5 @@ textarea, input { } #new-form select { - margin: auto; - width: 300px; + min-width: 300px; } \ No newline at end of file diff --git a/src/js/components/form/subcomponents/dropdown.js b/src/js/components/form/subcomponents/dropdown.js index 90ddf14..411f70f 100644 --- a/src/js/components/form/subcomponents/dropdown.js +++ b/src/js/components/form/subcomponents/dropdown.js @@ -1,11 +1,3 @@ -// async function createDropdownFromJSON(json_path) { -// // Fetch JSON data from a file asynchronously -// const response = await fetch(json_path); -// const dataArray = await response.json(); -// // create and return element -// return createDropdown(dataArray); -// } - function createDropdown(dataArray) { // Creating a select element @@ -25,7 +17,6 @@ function createDropdown(dataArray) { export const Dropdown = { - // createFromJSON : function(json_path){ return createDropdownFromJSON(json_path) }, create : function(dataArray) { return createDropdown(dataArray) }, } diff --git a/src/js/components/tooltip/tooltip.js b/src/js/components/tooltip/tooltip.js index 1cd0251..327733f 100644 --- a/src/js/components/tooltip/tooltip.js +++ b/src/js/components/tooltip/tooltip.js @@ -148,7 +148,7 @@ function linkTotalOTCol() { function linkCPACol() { // get all relevant cells - document.querySelectorAll('.cpa').forEach( (cell) => { + document.querySelectorAll('td.cpa').forEach( (cell) => { link(cell, showCPA); }) } diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index e402fec..9831bee 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -70,8 +70,6 @@ export async function initializePersonnelTable(){ // activate edit buttons Table.Buttons.Edit.init(personnelRowOnEdit, updateDisplayandTotals); initializeRowAddition(); - // Link up tooltips to display more info on hover - Tooltip.linkAll(); } else { Prompt.Text.update('No personnel expenditures for this fund.') } @@ -116,6 +114,7 @@ export function setUpForm() { Form.new('modal-body'); Form.NewField.shortText('Job Title:', 'job-name', true); Form.NewField.shortText('Account String:', 'account-string', true); + Form.NewField.dropdown('Service', 'service', Services.list(), true); Form.SubmitButton.add(); // Initialize form submission to table data Modal.Submit.init(handleSubmitNewJob); diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index 184482b..df241c2 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -64,8 +64,6 @@ export async function initializeOTTable(){ updateDisplayandTotals(); // activate edit buttons Table.Buttons.Edit.init(OTRowOnEdit, updateDisplayandTotals); - // wire up tooltips to show info on click - Tooltip.linkAll(); } else { Prompt.Text.update('No overtime expenditures for this fund.') } diff --git a/src/js/views/06_nonpersonnel/helpers.js b/src/js/views/06_nonpersonnel/helpers.js index ae4b822..a7b7421 100644 --- a/src/js/views/06_nonpersonnel/helpers.js +++ b/src/js/views/06_nonpersonnel/helpers.js @@ -48,7 +48,7 @@ export async function initializeNonpersonnelTable(){ Table.Columns.assignClasses(nonPersonnelColumns); // enable editing Table.Buttons.Edit.init(nonPersonnelRowOnEdit, Table.save); - // show info boxes on click + // show detail buttons Tooltip.linkAll(); } else { Prompt.Text.update('No non-personnel expenditures for this fund.') From b78ddb0077d32677dca15032dc30907822423268 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 14:26:31 -0400 Subject: [PATCH 07/24] Added add button for personnel --- src/js/components/form/form.css | 3 ++- src/js/components/tooltip/tooltip.js | 2 +- src/js/views/04_personnel/helpers.js | 10 +++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/js/components/form/form.css b/src/js/components/form/form.css index 3e89551..eeecba2 100644 --- a/src/js/components/form/form.css +++ b/src/js/components/form/form.css @@ -13,9 +13,10 @@ textarea, input { #new-form label { display: block; /* Ensure label is on its own line */ - margin-bottom: 0.5em; + margin-bottom: 0.25em; } #new-form select { min-width: 300px; + margin-bottom: 1.25em; } \ No newline at end of file diff --git a/src/js/components/tooltip/tooltip.js b/src/js/components/tooltip/tooltip.js index 327733f..3c7a984 100644 --- a/src/js/components/tooltip/tooltip.js +++ b/src/js/components/tooltip/tooltip.js @@ -89,7 +89,7 @@ function showCPA(row){ editTooltipText(message); } -function link (element, displayFn) { +function link(element, displayFn) { // add class to show cell with an underline, etc element.classList.add('tooltip-cell'); diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index 9831bee..01154b7 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -10,6 +10,7 @@ import Table from '../../components/table/table.js' import Sidebar from "../../components/sidebar/sidebar.js"; import { Services } from "../../utils/data_utils/budget_data_handlers.js"; import Tooltip from "../../components/tooltip/tooltip.js"; +import { unformatCurrency } from "../../utils/common_utils.js"; export function preparePageView(){ @@ -115,6 +116,9 @@ export function setUpForm() { Form.NewField.shortText('Job Title:', 'job-name', true); Form.NewField.shortText('Account String:', 'account-string', true); Form.NewField.dropdown('Service', 'service', Services.list(), true); + Form.NewField.shortText('Number of FTEs requested:', 'baseline-ftes', true); + Form.NewField.shortText(`Projected average salary IN FISCAL YEAR ${FISCAL_YEAR}:`, 'avg-salary', true); + Form.NewField.shortText(`Expected fringe rate (as a percentage)`, 'fringe', true); Form.SubmitButton.add(); // Initialize form submission to table data Modal.Submit.init(handleSubmitNewJob); @@ -123,11 +127,15 @@ export function setUpForm() { function handleSubmitNewJob(event){ // get answers from form, hide form, show answers in table const responses = Form.fetchAllResponses(event); + + // edit inputs from modal + responses['avg-salary'] = unformatCurrency(responses['avg-salary']); + responses['fringe'] = parseFloat(responses['fringe']) / 100; + console.log(responses); // 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.save(); From 327b2345ce151ac4307107ee94f52517fbe6cfe2 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 14:26:46 -0400 Subject: [PATCH 08/24] make table font smaller --- src/js/components/table/table.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/components/table/table.css b/src/js/components/table/table.css index 5f2f784..28e4cf6 100644 --- a/src/js/components/table/table.css +++ b/src/js/components/table/table.css @@ -1,5 +1,5 @@ #main-table { - font-size: calc(0.6vw + 0.5em); + font-size: calc(0.5vw + 0.5em); margin: auto; /* width: 100%; */ } From 6e8e39835574b3f3ce94df80fed17f55f2fb105d Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 14:32:16 -0400 Subject: [PATCH 09/24] make header row sticky --- src/js/components/table/table.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/components/table/table.css b/src/js/components/table/table.css index 28e4cf6..27fdae9 100644 --- a/src/js/components/table/table.css +++ b/src/js/components/table/table.css @@ -8,6 +8,9 @@ text-align: left; background-color: var(--darkGray); color: white; + position: -webkit-sticky; /* For Safari */ + position: sticky; + top: 0; } th { From b7c3527ff29a27c5236e1a474116a4373359ff9b Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 14:37:55 -0400 Subject: [PATCH 10/24] remove span instead of just removing span content --- src/js/components/tooltip/tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/components/tooltip/tooltip.js b/src/js/components/tooltip/tooltip.js index 3c7a984..edf2fbc 100644 --- a/src/js/components/tooltip/tooltip.js +++ b/src/js/components/tooltip/tooltip.js @@ -184,7 +184,7 @@ export const Tooltip = { unlink : function() { let details = document.querySelectorAll('.detail'); details.forEach( (span) => { - span.textContent = ''; + span.remove(); }) } } From 0c65950d010bbe785f7daaee4bf377805c8db217 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 15:06:22 -0400 Subject: [PATCH 11/24] create add button for overtime table --- src/js/views/04_personnel/helpers.js | 13 ++----- src/js/views/05_overtime/helpers.js | 55 +++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index 01154b7..6f4fe8c 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -9,7 +9,6 @@ import Prompt from "../../components/prompt/prompt.js"; import Table from '../../components/table/table.js' import Sidebar from "../../components/sidebar/sidebar.js"; import { Services } from "../../utils/data_utils/budget_data_handlers.js"; -import Tooltip from "../../components/tooltip/tooltip.js"; import { unformatCurrency } from "../../utils/common_utils.js"; @@ -21,6 +20,10 @@ export function preparePageView(){ Table.adjustWidth('90%'); NavButtons.Next.enable(); + // new row button + Table.Buttons.AddRow.updateText("Add new job"); + Table.Buttons.AddRow.show(); + // update page text Subtitle.update('Personnel'); Prompt.Text.update(` @@ -70,17 +73,11 @@ export async function initializePersonnelTable(){ updateDisplayandTotals(); // activate edit buttons Table.Buttons.Edit.init(personnelRowOnEdit, updateDisplayandTotals); - initializeRowAddition(); } else { Prompt.Text.update('No personnel expenditures for this fund.') } } -function initializeRowAddition(){ - Table.Buttons.AddRow.updateText("Add new job"); - Table.Buttons.AddRow.show(); -} - // update sidebar and also cost totals when the FTEs are edited function updateDisplayandTotals(){ // calculate for each row @@ -127,11 +124,9 @@ export function setUpForm() { function handleSubmitNewJob(event){ // get answers from form, hide form, show answers in table const responses = Form.fetchAllResponses(event); - // edit inputs from modal responses['avg-salary'] = unformatCurrency(responses['avg-salary']); responses['fringe'] = parseFloat(responses['fringe']) / 100; - console.log(responses); // make sure it's not an empty response if (Object.values(responses)[0] != ''){ // change page view diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index df241c2..bc5333c 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -6,7 +6,9 @@ import Subtitle from '../../components/header/header.js'; import Sidebar from '../../components/sidebar/sidebar.js'; import Table from '../../components/table/table.js'; import { Services } from '../../utils/data_utils/budget_data_handlers.js'; -import Tooltip from '../../components/tooltip/tooltip.js'; +import Modal from '../../components/modal/modal.js'; +import Form from '../../components/form/form.js'; +import { unformatCurrency } from '../../utils/common_utils.js'; export function preparePageView(){ // prepare page view @@ -24,6 +26,20 @@ export function preparePageView(){ initializeOTTable(); Prompt.Text.update(`Please see your baseline overtime / holiday pay / shift premiums in the table below. Make any edits and continue.`); + + // form for new row + setUpModal(); + setUpForm(); + + // show new row button + initializeRowAddition(); +} + +function setUpModal() { + // Initialize modal + Modal.clear(); + Modal.Link.add('add-btn'); + Modal.Title.update('New cost center for overtime'); } function assignClasses() { @@ -69,6 +85,11 @@ export async function initializeOTTable(){ } } +function initializeRowAddition(){ + Table.Buttons.AddRow.updateText("Add new cost center"); + Table.Buttons.AddRow.show(); +} + function calculateTotalCost(salary, wages, fica_rate){ fica_rate = parseFloat(fica_rate); return (wages + salary) * (1 + fica_rate) ; @@ -93,4 +114,36 @@ function updateDisplayandTotals(){ //save data Table.save(); } +} + +export function setUpForm() { + // Set up form + Form.new('modal-body'); + Form.NewField.shortText('Appropriation:', 'approp-name', true); + Form.NewField.shortText('Cost Center:', 'cc-name', true); + Form.NewField.dropdown('Service', 'service', Services.list(), true); + Form.NewField.dropdown('Recurring or One-Time', 'recurring', ['Recurring', 'One-Time'], true); + Form.NewField.shortText('Overtime amount requested:', 'OT-wages', true); + Form.SubmitButton.add(); + // Initialize form submission to table data + Modal.Submit.init(handleSubmitNewRow); +} + +function handleSubmitNewRow(event){ + // get answers from form, hide form, show answers in table + const responses = Form.fetchAllResponses(event); + + // edit inputs from modal + responses['OT-wages'] = unformatCurrency(responses['OT-wages']); + // 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.save(); + initializeOTTable(); + + } + } \ No newline at end of file From 70facc05598bae5b1e3f9c049557d45044e3d826 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 15:11:02 -0400 Subject: [PATCH 12/24] add fica to ot --- src/js/views/05_overtime/helpers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index bc5333c..5c5b1f2 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -135,6 +135,8 @@ function handleSubmitNewRow(event){ // edit inputs from modal responses['OT-wages'] = unformatCurrency(responses['OT-wages']); + responses['fica'] = 0.0765; + // make sure it's not an empty response if (Object.values(responses)[0] != ''){ // change page view From 5d727343f18613606888ee5ebe640b0cd08222aa Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 16:06:12 -0400 Subject: [PATCH 13/24] create modal for OT and NP --- .../views/02_baseline_landing_page/helpers.js | 2 +- src/js/views/04_personnel/helpers.js | 4 +- src/js/views/05_overtime/helpers.js | 8 +-- src/js/views/06_nonpersonnel/helpers.js | 53 +++++++++++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/js/views/02_baseline_landing_page/helpers.js b/src/js/views/02_baseline_landing_page/helpers.js index a1d60e4..fc90e45 100644 --- a/src/js/views/02_baseline_landing_page/helpers.js +++ b/src/js/views/02_baseline_landing_page/helpers.js @@ -23,7 +23,7 @@ export function preparePageView(){ // 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.`); + Select one of your funds then click continue.`); } function allowRowSelection(){ diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index 6f4fe8c..15fc68d 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -111,7 +111,8 @@ export function setUpForm() { // Set up form Form.new('modal-body'); Form.NewField.shortText('Job Title:', 'job-name', true); - Form.NewField.shortText('Account String:', 'account-string', true); + Form.NewField.dropdown('Appropriation:', 'approp-name', [], true); + Form.NewField.dropdown('Cost Center:', 'cc-name', [], true); Form.NewField.dropdown('Service', 'service', Services.list(), true); Form.NewField.shortText('Number of FTEs requested:', 'baseline-ftes', true); Form.NewField.shortText(`Projected average salary IN FISCAL YEAR ${FISCAL_YEAR}:`, 'avg-salary', true); @@ -127,6 +128,7 @@ function handleSubmitNewJob(event){ // edit inputs from modal responses['avg-salary'] = unformatCurrency(responses['avg-salary']); responses['fringe'] = parseFloat(responses['fringe']) / 100; + responses['account-string'] = `${responses['approp-name']}-${responses['cc-name']}` // make sure it's not an empty response if (Object.values(responses)[0] != ''){ // change page view diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index 5c5b1f2..6d3079b 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -32,7 +32,8 @@ export function preparePageView(){ setUpForm(); // show new row button - initializeRowAddition(); + Table.Buttons.AddRow.updateText("Add new cost center"); + Table.Buttons.AddRow.show(); } function setUpModal() { @@ -85,11 +86,6 @@ export async function initializeOTTable(){ } } -function initializeRowAddition(){ - Table.Buttons.AddRow.updateText("Add new cost center"); - Table.Buttons.AddRow.show(); -} - function calculateTotalCost(salary, wages, fica_rate){ fica_rate = parseFloat(fica_rate); return (wages + salary) * (1 + fica_rate) ; diff --git a/src/js/views/06_nonpersonnel/helpers.js b/src/js/views/06_nonpersonnel/helpers.js index a7b7421..c4bc416 100644 --- a/src/js/views/06_nonpersonnel/helpers.js +++ b/src/js/views/06_nonpersonnel/helpers.js @@ -5,6 +5,10 @@ import Body from "../../components/body/body.js"; import NavButtons from "../../components/nav_buttons/nav_buttons.js"; import Subtitle from "../../components/header/header.js"; import Tooltip from "../../components/tooltip/tooltip.js"; +import Modal from "../../components/modal/modal.js"; +import Form from "../../components/form/form.js"; +import { Services } from "../../utils/data_utils/budget_data_handlers.js"; +import { FISCAL_YEAR } from "../../init.js"; const nonPersonnelColumns = [ { title: 'FY26 Request', className: 'request', isCost: true }, @@ -36,6 +40,14 @@ export function preparePageView(){ Subtitle.update('Non-Personnel'); Prompt.Text.update('Review and edit non-personnel line items.'); NavButtons.Next.enable(); + + // form for new row + setUpModal(); + setUpForm(); + + // show new row button + Table.Buttons.AddRow.updateText("Add new non-personnel item"); + Table.Buttons.AddRow.show() } export async function initializeNonpersonnelTable(){ @@ -62,3 +74,44 @@ function nonPersonnelRowOnEdit(){ Table.Cell.createDropdown('recurring', ['One-Time', 'Recurring']); } +export function setUpModal() { + // Initialize modal + Modal.clear(); + Modal.Link.add('add-btn'); + Modal.Title.update('New non-personnel item'); +} + +export function setUpForm() { + // Set up form + Form.new('modal-body'); + Form.NewField.dropdown('Appropriation:', 'approp-name', [], true); + Form.NewField.dropdown('Cost Center:', 'cc-name', [], true); + Form.NewField.dropdown('Object:', 'object-name', [], true); + Form.NewField.dropdown('Service', 'service', Services.list(), true); + Form.NewField.longText('Describe your new request:', 'description', true); + Form.NewField.dropdown('Recurring or One-Time', 'recurring', ['Recurring', 'One-Time'], true); + Form.NewField.shortText('Amount requested:', 'request', true); + Form.SubmitButton.add(); + // Initialize form submission to table data + Modal.Submit.init(submitNewRow); +} + + +function submitNewRow(event){ + // get answers from form, hide form, show answers in table + const responses = Form.fetchAllResponses(event); + // edit inputs from modal + responses['avg-salary'] = unformatCurrency(responses['avg-salary']); + responses['fringe'] = parseFloat(responses['fringe']) / 100; + // 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.save(); + initializePersonnelTable(); + + } + +} From 22357e02063a3e3005b4dfdb4ff89ee8bbd1719f Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Thu, 25 Jul 2024 16:58:36 -0400 Subject: [PATCH 14/24] #54 add support for approp and cc drop downs in relevant modals --- src/js/utils/archive/archived_fns.js | 91 ------------------- .../utils/data_utils/budget_data_handlers.js | 34 ++++++- src/js/views/04_personnel/helpers.js | 6 +- src/js/views/05_overtime/helpers.js | 4 +- src/js/views/06_nonpersonnel/helpers.js | 4 +- src/js/views/07_new_initiatives/helpers.js | 3 +- 6 files changed, 42 insertions(+), 100 deletions(-) delete mode 100644 src/js/utils/archive/archived_fns.js diff --git a/src/js/utils/archive/archived_fns.js b/src/js/utils/archive/archived_fns.js deleted file mode 100644 index 2450097..0000000 --- a/src/js/utils/archive/archived_fns.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * 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 - * of the element and passed through an optional formatting function before being - * displayed in the cell. An optional callback can be triggered after the update - * to perform additional actions. - */ -function createEditableCell(cell, attribute = 'value', formatValueCallback, updateCallback, validate) { - // Add a click event to the cell to make it editable - cell.onclick = function() { - // Fetch the current attribute value of the cell or fall back to an empty string - var currentValue = cell.getAttribute(attribute) || ''; - // Create an input element to edit the value - var textbox = document.createElement('input'); - textbox.type = 'text'; - textbox.value = currentValue; - var feedback = document.createElement('p'); - feedback.style.color = "red"; - - // Function to commit the textbox value and restore static text - function commitAndRestoreText() { - // Retrieve the entered value - var enteredValue = textbox.value; - // Set the attribute to the entered value - cell.setAttribute(attribute, enteredValue); - - // validate text against validation criteria - let feedback_text = ''; - if (validate){ - feedback_text = validate(enteredValue); - } - - // if there's an error, show it - if (feedback_text){ - feedback.textContent = feedback_text; - // otherwise, proceed - } else { - // Format and set the cell's text content - cell.textContent = formatValueCallback ? formatValueCallback(enteredValue) : enteredValue; - // If there is an update callback provided, call it - if (updateCallback) { - updateCallback(); - } - }; - - // Reattach the onclick event to allow editing again in the future - cell.onclick = function() { - createEditableCell(cell, attribute, formatValueCallback, updateCallback, validate); - }; - } - - // When the textbox loses focus, commit its value - textbox.onblur = commitAndRestoreText; - // When the user presses the 'Enter' key, commit the value and blur the textbox - textbox.onkeydown = function(event) { - if (event.key === 'Enter') { - commitAndRestoreText(); - textbox.blur(); - } - }; - - // Clear the current content and append the textbox to the cell - cell.innerHTML = ''; - cell.appendChild(textbox); - cell.appendChild(feedback); - // Temporarily remove the onclick event handler to prevent re-triggering during edit - cell.onclick = null; - - // Focus on the textbox to start editing - textbox.focus(); - } -} - -// Function to apply createEditableCell to all cells matching a given selector -function applyEditableCells(selector, attribute = 'value', formatValueCallback, updateCallback, validate) { - // Select all elements that match the provided selector - var cells = document.querySelectorAll(selector); - // Iterate over each cell and make it editable - cells.forEach(function(cell) { - createEditableCell(cell, attribute, formatValueCallback, updateCallback, validate); - }); -} - -function validateNumber(input){ - var number = parseFloat(input); - if (isNaN(number)){ - return "Field only accepts numbers"; - }; - return ""; -} - diff --git a/src/js/utils/data_utils/budget_data_handlers.js b/src/js/utils/data_utils/budget_data_handlers.js index 097e32b..0ea6fc5 100644 --- a/src/js/utils/data_utils/budget_data_handlers.js +++ b/src/js/utils/data_utils/budget_data_handlers.js @@ -1,3 +1,10 @@ +import { CurrentFund } from "./local_storage_handlers"; + +function getUniqueValues(data, key) { + const values = data.map(obj => obj[key]); + return Array.from(new Set(values)); +} + export const FundLookupTable = { retrieve : function() { return JSON.parse(localStorage.getItem('fund-lookup-table')) || {}; @@ -5,9 +12,12 @@ export const FundLookupTable = { save : function(fundDict){ localStorage.setItem('fund-lookup-table', JSON.stringify(fundDict)); }, + update : function(fundData){ const table = this.retrieve(); + for (let fund of Object.keys(fundData)){ + // add to lookup table if not in there already if (!table[fund]){ // get fund name @@ -16,11 +26,33 @@ export const FundLookupTable = { table[fund] = {}; table[fund]['name'] = fundName; table[fund]['viewed'] = false; + // build lists of unique cost centers and appropriations + table[fund]['approp'] = getUniqueValues(fundData[fund], 'Appropriation Name'); + table[fund]['cc'] = getUniqueValues(fundData[fund], 'Cost Center Name'); } } // save any updates this.save(table); }, + + getCostCenters : function() { + // get current fund + const fund = CurrentFund.number() + if (this.retrieve()[fund]){ + return this.retrieve()[fund]['cc']; + } + return []; + }, + + getApprops : function() { + // get current fund + const fund = CurrentFund.number() + if (this.retrieve()[fund]){ + return this.retrieve()[fund]['approp']; + } + return []; + }, + reset : function() { this.save({}); }, @@ -64,4 +96,4 @@ export const Services = { list : function(){ return JSON.parse(localStorage.getItem('services-list')) || {}; } -} \ No newline at end of file +} diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index 15fc68d..4744d22 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -8,7 +8,7 @@ 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"; -import { Services } from "../../utils/data_utils/budget_data_handlers.js"; +import { FundLookupTable, Services } from "../../utils/data_utils/budget_data_handlers.js"; import { unformatCurrency } from "../../utils/common_utils.js"; @@ -111,8 +111,8 @@ export function setUpForm() { // Set up form Form.new('modal-body'); Form.NewField.shortText('Job Title:', 'job-name', true); - Form.NewField.dropdown('Appropriation:', 'approp-name', [], true); - Form.NewField.dropdown('Cost Center:', 'cc-name', [], true); + Form.NewField.dropdown('Appropriation:', 'approp-name', FundLookupTable.getApprops(), true); + Form.NewField.dropdown('Cost Center:', 'cc-name', FundLookupTable.getCostCenters(), true); Form.NewField.dropdown('Service', 'service', Services.list(), true); Form.NewField.shortText('Number of FTEs requested:', 'baseline-ftes', true); Form.NewField.shortText(`Projected average salary IN FISCAL YEAR ${FISCAL_YEAR}:`, 'avg-salary', true); diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index 6d3079b..014e3a4 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -115,8 +115,8 @@ function updateDisplayandTotals(){ export function setUpForm() { // Set up form Form.new('modal-body'); - Form.NewField.shortText('Appropriation:', 'approp-name', true); - Form.NewField.shortText('Cost Center:', 'cc-name', true); + Form.NewField.dropdown('Appropriation:', 'approp-name', FundLookupTable.getApprops(), true); + Form.NewField.dropdown('Cost Center:', 'cc-name', FundLookupTable.getCostCenters(), true); Form.NewField.dropdown('Service', 'service', Services.list(), true); Form.NewField.dropdown('Recurring or One-Time', 'recurring', ['Recurring', 'One-Time'], true); Form.NewField.shortText('Overtime amount requested:', 'OT-wages', true); diff --git a/src/js/views/06_nonpersonnel/helpers.js b/src/js/views/06_nonpersonnel/helpers.js index c4bc416..b82a90d 100644 --- a/src/js/views/06_nonpersonnel/helpers.js +++ b/src/js/views/06_nonpersonnel/helpers.js @@ -84,8 +84,8 @@ export function setUpModal() { export function setUpForm() { // Set up form Form.new('modal-body'); - Form.NewField.dropdown('Appropriation:', 'approp-name', [], true); - Form.NewField.dropdown('Cost Center:', 'cc-name', [], true); + Form.NewField.dropdown('Appropriation:', 'approp-name', FundLookupTable.getApprops(), true); + Form.NewField.dropdown('Cost Center:', 'cc-name', FundLookupTable.getCostCenters(), true); Form.NewField.dropdown('Object:', 'object-name', [], true); Form.NewField.dropdown('Service', 'service', Services.list(), true); Form.NewField.longText('Describe your new request:', 'description', true); diff --git a/src/js/views/07_new_initiatives/helpers.js b/src/js/views/07_new_initiatives/helpers.js index 183b83a..dfc133f 100644 --- a/src/js/views/07_new_initiatives/helpers.js +++ b/src/js/views/07_new_initiatives/helpers.js @@ -60,7 +60,8 @@ export function setUpForm() { Form.NewField.longText(`Why can’t the Initiative be funded with the Department’s baseline budget?`, 'Q3', true); // TODO: Edit to drop down - Form.NewField.shortText('Relevant account string (if known)?', 'Account String', false); + Form.NewField.dropdown('Appropriation (if known):', 'approp-name', FundLookupTable.getApprops(), true); + Form.NewField.dropdown('Cost Center (if known):', 'cc-name', FundLookupTable.getCostCenters(), true); // Numbers Form.NewField.numericInput('What is your ballpark estimate of TOTAL ADDITONAL expenses associated with this initiative?', From a5e1f977b857709cd3a1f018ed0d93f637ef2b12 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 12:03:41 -0400 Subject: [PATCH 15/24] build account string structure and logic --- .../utils/data_utils/budget_data_handlers.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/js/utils/data_utils/budget_data_handlers.js b/src/js/utils/data_utils/budget_data_handlers.js index 0ea6fc5..bc26350 100644 --- a/src/js/utils/data_utils/budget_data_handlers.js +++ b/src/js/utils/data_utils/budget_data_handlers.js @@ -97,3 +97,47 @@ export const Services = { return JSON.parse(localStorage.getItem('services-list')) || {}; } } + +export const ObjectCategories = { + list : [ + // 'Salaries & Wages', + // 'Employee Benefits', + 'Professional & Contractual Services', + 'Operating Supplies', + 'Operating Services', + 'Equipment Acquisition', + 'Capital Outlays', + 'Fixed Charges', + 'Other Expenses' + ] +} + +function getNumber(input) { + const match = input.match(/^\d+/); + return match ? match[0] : null; +} + +export const AccountString = { + build : (approp, cc, obj = null) => { + // put together account string fund-approp-costcenter[-obj] (w optional object) + const fund = CurrentFund.number(); + approp = getNumber(approp); + cc = getNumber(cc); + var string = `${fund}-${approp}-${cc}`; + string = obj ? `${string}-${getNumber(obj)}` : string; + return string; + }, + + getAccountStringSection : (account_string, section) => { + const sections = account_string.split("-"); + return sections.length > section ? sections[section] : null; + }, + + fund : (account_string) => { this.getAccountStringSection(account_string, 0) }, + + approp : (account_string) => { this.getAccountStringSection(account_string, 1) }, + + costCenter : (account_string) => { this.getAccountStringSection(account_string, 2) }, + + object : (account_string) => { this.getAccountStringSection(account_string, 3) }, +} \ No newline at end of file From c8a7b0475923e8de726b0560dd322c02388c0d74 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 12:04:01 -0400 Subject: [PATCH 16/24] fix imports; add obj category dropdown for NP --- src/js/views/04_personnel/helpers.js | 3 ++- src/js/views/05_overtime/helpers.js | 2 ++ src/js/views/06_nonpersonnel/helpers.js | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index 4744d22..e8e69b1 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -11,7 +11,6 @@ import Sidebar from "../../components/sidebar/sidebar.js"; import { FundLookupTable, Services } from "../../utils/data_utils/budget_data_handlers.js"; import { unformatCurrency } from "../../utils/common_utils.js"; - export function preparePageView(){ // prepare page view Body.reset(); @@ -48,6 +47,8 @@ function assignClasses() { { title: 'Fringe Benefits Rate', className: 'fringe', hide: true }, { title: 'Appropriation Name', className: 'approp-name', hide: true }, { title: 'Cost Center Name', className: 'cc-name', hide: true }, + { title: 'Object Name', className: 'object-name', hide: true }, + { title: 'Object', className: 'object', hide: true }, { title: 'General Increase Rate', className: 'general-increase-rate', hide: true}, { title: 'Step/Merit Increase Rate', className: 'merit-increase-rate', hide: true}, { title: `Average Salary/Wage as of 9/1/20${FISCAL_YEAR-2}`, className: 'current-salary', isCost: true, hide: true}, diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index 014e3a4..d661331 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -9,6 +9,8 @@ import { Services } from '../../utils/data_utils/budget_data_handlers.js'; import Modal from '../../components/modal/modal.js'; import Form from '../../components/form/form.js'; import { unformatCurrency } from '../../utils/common_utils.js'; +import { FundLookupTable } from "../../utils/data_utils/budget_data_handlers.js"; + export function preparePageView(){ // prepare page view diff --git a/src/js/views/06_nonpersonnel/helpers.js b/src/js/views/06_nonpersonnel/helpers.js index b82a90d..9ef5338 100644 --- a/src/js/views/06_nonpersonnel/helpers.js +++ b/src/js/views/06_nonpersonnel/helpers.js @@ -7,8 +7,8 @@ import Subtitle from "../../components/header/header.js"; import Tooltip from "../../components/tooltip/tooltip.js"; import Modal from "../../components/modal/modal.js"; import Form from "../../components/form/form.js"; -import { Services } from "../../utils/data_utils/budget_data_handlers.js"; -import { FISCAL_YEAR } from "../../init.js"; +import { ObjectCategories, Services } from "../../utils/data_utils/budget_data_handlers.js"; +import { FundLookupTable } from "../../utils/data_utils/budget_data_handlers.js"; const nonPersonnelColumns = [ { title: 'FY26 Request', className: 'request', isCost: true }, @@ -86,7 +86,8 @@ export function setUpForm() { Form.new('modal-body'); Form.NewField.dropdown('Appropriation:', 'approp-name', FundLookupTable.getApprops(), true); Form.NewField.dropdown('Cost Center:', 'cc-name', FundLookupTable.getCostCenters(), true); - Form.NewField.dropdown('Object:', 'object-name', [], true); + Form.NewField.dropdown('Object Category:', 'object-category', ObjectCategories.list, true); + Form.NewField.shortText('Object Number (if known):', 'object-number', true); Form.NewField.dropdown('Service', 'service', Services.list(), true); Form.NewField.longText('Describe your new request:', 'description', true); Form.NewField.dropdown('Recurring or One-Time', 'recurring', ['Recurring', 'One-Time'], true); From ef7ba19696c250d60b4de2b49a34939a87760d07 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 12:41:45 -0400 Subject: [PATCH 17/24] fix personnel account string construction in table for new rows --- .../utils/data_utils/budget_data_handlers.js | 38 ++++++++++++------- src/js/views/04_personnel/helpers.js | 11 ++++-- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/js/utils/data_utils/budget_data_handlers.js b/src/js/utils/data_utils/budget_data_handlers.js index bc26350..bb297f7 100644 --- a/src/js/utils/data_utils/budget_data_handlers.js +++ b/src/js/utils/data_utils/budget_data_handlers.js @@ -112,32 +112,42 @@ export const ObjectCategories = { ] } -function getNumber(input) { - const match = input.match(/^\d+/); - return match ? match[0] : null; -} - export const AccountString = { - build : (approp, cc, obj = null) => { + getNumber: function(input) { + // isolate the numerical part of a appropriation/cost center/object + const match = input.match(/^\d+/); + return match ? match[0] : null; + }, + + build : function(approp, cc, obj = null) { // put together account string fund-approp-costcenter[-obj] (w optional object) const fund = CurrentFund.number(); - approp = getNumber(approp); - cc = getNumber(cc); + // hits error here + approp = this.getNumber(approp); + cc = this.getNumber(cc); var string = `${fund}-${approp}-${cc}`; - string = obj ? `${string}-${getNumber(obj)}` : string; + string = obj ? `${string}-${this.getNumber(obj)}` : string; return string; }, - getAccountStringSection : (account_string, section) => { + getAccountStringSection : function(account_string, section) { const sections = account_string.split("-"); return sections.length > section ? sections[section] : null; }, - fund : (account_string) => { this.getAccountStringSection(account_string, 0) }, + fund : function(account_string) { + return this.getAccountStringSection(account_string, 0) + }, - approp : (account_string) => { this.getAccountStringSection(account_string, 1) }, + approp : function(account_string) { + return this.getAccountStringSection(account_string, 1) + }, - costCenter : (account_string) => { this.getAccountStringSection(account_string, 2) }, + costCenter : function(account_string) { + return this.getAccountStringSection(account_string, 2) + }, - object : (account_string) => { this.getAccountStringSection(account_string, 3) }, + object : function(account_string) { + return this.getAccountStringSection(account_string, 3) + }, } \ No newline at end of file diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index e8e69b1..a97915d 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -8,7 +8,7 @@ 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"; -import { FundLookupTable, Services } from "../../utils/data_utils/budget_data_handlers.js"; +import { AccountString, FundLookupTable, Services } from "../../utils/data_utils/budget_data_handlers.js"; import { unformatCurrency } from "../../utils/common_utils.js"; export function preparePageView(){ @@ -46,9 +46,9 @@ function assignClasses() { // hidden columns needed for calculations { title: 'Fringe Benefits Rate', className: 'fringe', hide: true }, { title: 'Appropriation Name', className: 'approp-name', hide: true }, + { title: 'Appropriation', className: 'approp', hide: true }, { title: 'Cost Center Name', className: 'cc-name', hide: true }, - { title: 'Object Name', className: 'object-name', hide: true }, - { title: 'Object', className: 'object', hide: true }, + { title: 'Cost Center', className: 'cc', hide: true }, { title: 'General Increase Rate', className: 'general-increase-rate', hide: true}, { title: 'Step/Merit Increase Rate', className: 'merit-increase-rate', hide: true}, { title: `Average Salary/Wage as of 9/1/20${FISCAL_YEAR-2}`, className: 'current-salary', isCost: true, hide: true}, @@ -129,7 +129,10 @@ function handleSubmitNewJob(event){ // edit inputs from modal responses['avg-salary'] = unformatCurrency(responses['avg-salary']); responses['fringe'] = parseFloat(responses['fringe']) / 100; - responses['account-string'] = `${responses['approp-name']}-${responses['cc-name']}` + console.log(responses['approp-name']); + responses['account-string'] = AccountString.build(responses['approp-name'], responses['cc-name']) + responses['approp'] = AccountString.getNumber(responses['approp-name']); + responses['cc'] = AccountString.getNumber(responses['cc-name']); // make sure it's not an empty response if (Object.values(responses)[0] != ''){ // change page view From 54263ee2c7dcb001720bf3f6d4e05625ee0623f6 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 12:53:20 -0400 Subject: [PATCH 18/24] edit tooltip text: add fund, change projection language, add FICA clarification --- src/js/components/tooltip/tooltip.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/js/components/tooltip/tooltip.js b/src/js/components/tooltip/tooltip.js index edf2fbc..894db29 100644 --- a/src/js/components/tooltip/tooltip.js +++ b/src/js/components/tooltip/tooltip.js @@ -2,7 +2,7 @@ import { FISCAL_YEAR } from '../../init'; import Cell from '../table/subcomponents/cells'; import { formatCurrency } from '../../utils/common_utils'; import './tooltip.css' -import { CurrentPage } from '../../utils/data_utils/local_storage_handlers'; +import { CurrentFund, CurrentPage } from '../../utils/data_utils/local_storage_handlers'; function hideTooltip() { document.getElementById('tooltip').style.visibility = 'hidden'; @@ -22,8 +22,10 @@ function showAccountString(row){ const approp = Cell.getText(row, 'approp-name'); const cc = Cell.getText(row, 'cc-name'); const obj = Cell.getText(row, 'object-name'); - var message = `Appropriation: ${approp}
- Cost Center: ${cc}`; + var message = + `Fund: ${CurrentFund.name()}
+ Appropriation: ${approp}
+ Cost Center: ${cc}`; if (obj) { message += `
Object: ${obj}`} editTooltipText(message); } @@ -35,16 +37,15 @@ function showSalaryProjection(row){ const proj_salary = Cell.getValue(row, 'avg-salary'); if (current_salary){ var message = `The average salary/wage for this position was - ${formatCurrency(current_salary)} as of September 20${FISCAL_YEAR-2}. With two general - increases of ${general_increase*100}% and a merit increase of ${merit_increase*100}%, the - Budget Office projects that the average annual - salary/wage for this position will be ${formatCurrency(proj_salary)} in FY${FISCAL_YEAR}.`; + ${formatCurrency(current_salary)} as of September 20${FISCAL_YEAR-2}. + Given a ${general_increase*100}% general increase rate and a ${merit_increase*100}% + merit increase, the FY${FISCAL_YEAR} projection for this position's average + annual salary/wage is ${formatCurrency(proj_salary)}.`; } else { var message = `The average salary/wage for this position was unknown as of September 20${FISCAL_YEAR-2}, or the position - did not exist. The Budget Office projects that - the average annual salary/wage for this position - will be ${formatCurrency(proj_salary)} in FY2026.` + did not exist. The FY${FISCAL_YEAR} projection for this position's + average annual salary/wage is ${formatCurrency(proj_salary)}.` } editTooltipText(message); @@ -65,7 +66,7 @@ function showFinalPersonnelCost(row){ function showFICA(row){ const fica = parseFloat(Cell.getText(row, 'fica')); const ficaPercentage = (fica * 100).toFixed(2); - const message = `This total is overtime wages plus overtime salary plus FICA, + const message = `This total is overtime wages plus overtime salary plus FICA (payroll tax), which is ${ficaPercentage}% for this cost center.` editTooltipText(message); } From 029cbcf2145d9239072a8f386a9f9d0a82805ba6 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 13:07:35 -0400 Subject: [PATCH 19/24] add catch in case of empty string parameter for unformatCurrency --- src/js/utils/common_utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/utils/common_utils.js b/src/js/utils/common_utils.js index eb154cf..814fadf 100644 --- a/src/js/utils/common_utils.js +++ b/src/js/utils/common_utils.js @@ -17,6 +17,7 @@ export const formatCurrency = (amount, return_zero = false) => { // function to convert formatted number to a float export const unformatCurrency = (formattedAmount) => { + if (!formattedAmount) { return 0 }; // Remove any currency symbols and commas let numericalPart = formattedAmount.replace(/[^0-9.-]+/g, ""); if (numericalPart == '-'){ From f4152205ef316eed596c77742e1eb05eeabff40d Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 13:24:29 -0400 Subject: [PATCH 20/24] fix bug with empty new inits table in storage --- src/js/utils/data_utils/local_storage_handlers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/utils/data_utils/local_storage_handlers.js b/src/js/utils/data_utils/local_storage_handlers.js index 4c887ad..04e1d90 100644 --- a/src/js/utils/data_utils/local_storage_handlers.js +++ b/src/js/utils/data_utils/local_storage_handlers.js @@ -62,9 +62,9 @@ export async function deleteAllTables(){ export function loadTableData(name){ const data = localStorage.getItem(name); - if ( data == '' ) { - return ''; - } + if ( data == '' || data == '[]' ) { + return 0; + }; return JSON.parse(data); } From 7087d717a475ca6d962dcfda5925fcbe5b798f95 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 15:23:51 -0400 Subject: [PATCH 21/24] add account string calculated cols --- src/js/views/05_overtime/helpers.js | 9 ++++++++- src/js/views/06_nonpersonnel/helpers.js | 20 ++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/js/views/05_overtime/helpers.js b/src/js/views/05_overtime/helpers.js index d661331..189878b 100644 --- a/src/js/views/05_overtime/helpers.js +++ b/src/js/views/05_overtime/helpers.js @@ -5,7 +5,7 @@ import NavButtons from '../../components/nav_buttons/nav_buttons.js'; import Subtitle from '../../components/header/header.js'; import Sidebar from '../../components/sidebar/sidebar.js'; import Table from '../../components/table/table.js'; -import { Services } from '../../utils/data_utils/budget_data_handlers.js'; +import { AccountString, Services } from '../../utils/data_utils/budget_data_handlers.js'; import Modal from '../../components/modal/modal.js'; import Form from '../../components/form/form.js'; import { unformatCurrency } from '../../utils/common_utils.js'; @@ -59,6 +59,9 @@ function assignClasses() { { title: 'Edit', className: 'edit'}, // calc columns { title: 'FICA Rate', className: 'fica', hide: true}, + { title: 'Account String', className: 'account-string', hide: true}, + { title: `Cost Center`, className: 'cc', hide: true }, + { title: 'Appropriation', className: 'approp', hide: true}, ]; // assign cost classes @@ -134,6 +137,10 @@ function handleSubmitNewRow(event){ // edit inputs from modal responses['OT-wages'] = unformatCurrency(responses['OT-wages']); responses['fica'] = 0.0765; + // create account string + responses['account-string'] = AccountString.build(responses['approp-name'], responses['cc-name']); + responses['approp'] = AccountString.getNumber(responses['approp-name']); + responses['cc'] = AccountString.getNumber(responses['cc-name']); // make sure it's not an empty response if (Object.values(responses)[0] != ''){ diff --git a/src/js/views/06_nonpersonnel/helpers.js b/src/js/views/06_nonpersonnel/helpers.js index 9ef5338..c6ea724 100644 --- a/src/js/views/06_nonpersonnel/helpers.js +++ b/src/js/views/06_nonpersonnel/helpers.js @@ -7,8 +7,9 @@ import Subtitle from "../../components/header/header.js"; import Tooltip from "../../components/tooltip/tooltip.js"; import Modal from "../../components/modal/modal.js"; import Form from "../../components/form/form.js"; -import { ObjectCategories, Services } from "../../utils/data_utils/budget_data_handlers.js"; +import { ObjectCategories, Services, AccountString } from "../../utils/data_utils/budget_data_handlers.js"; import { FundLookupTable } from "../../utils/data_utils/budget_data_handlers.js"; +import { unformatCurrency } from "../../utils/common_utils.js"; const nonPersonnelColumns = [ { title: 'FY26 Request', className: 'request', isCost: true }, @@ -22,9 +23,12 @@ const nonPersonnelColumns = [ // hidden columns used for calcs and info boxes { title: 'Appropriation Name', className: 'approp-name', hide: true }, { title: 'Cost Center Name', className: 'cc-name', hide: true }, - { title : 'Contract End Date', className : 'contract-end', hide:true}, + { title: 'Appropriation', className: 'approp', hide: true }, + { title: 'Cost Center', className: 'cc', hide: true }, + { title: 'Contract End Date', className: 'contract-end', hide:true}, { title: 'Amount Remaining on Contract', className: 'remaining', isCost: true , hide: true}, { title: 'Object Name', className: 'object-name', hide: true}, + { title: 'Object', className: 'object', hide: true}, { title: 'Vendor Name', className: 'vendor', hide: true}, { title: 'Object Category', className: 'object-category', hide: true}, { title: 'BPA/CPA Description', className: 'cpa-description', hide: true} @@ -87,7 +91,8 @@ export function setUpForm() { Form.NewField.dropdown('Appropriation:', 'approp-name', FundLookupTable.getApprops(), true); Form.NewField.dropdown('Cost Center:', 'cc-name', FundLookupTable.getCostCenters(), true); Form.NewField.dropdown('Object Category:', 'object-category', ObjectCategories.list, true); - Form.NewField.shortText('Object Number (if known):', 'object-number', true); + // TODO: maybe give dropdown based on selected obj category + Form.NewField.shortText('Object Number (if known):', 'object', false); Form.NewField.dropdown('Service', 'service', Services.list(), true); Form.NewField.longText('Describe your new request:', 'description', true); Form.NewField.dropdown('Recurring or One-Time', 'recurring', ['Recurring', 'One-Time'], true); @@ -104,6 +109,13 @@ function submitNewRow(event){ // edit inputs from modal responses['avg-salary'] = unformatCurrency(responses['avg-salary']); responses['fringe'] = parseFloat(responses['fringe']) / 100; + // create account string + responses['account-string'] = AccountString.build(responses['approp-name'], responses['cc-name'], responses['object']); + responses['approp'] = AccountString.getNumber(responses['approp-name']); + responses['cc'] = AccountString.getNumber(responses['cc-name']); + // TODO: build out lookup table from meta.obj tab from detail sheet? + responses['object-name'] = responses['object']; + // make sure it's not an empty response if (Object.values(responses)[0] != ''){ // change page view @@ -111,7 +123,7 @@ function submitNewRow(event){ // add data to table Table.Rows.add(responses); Table.save(); - initializePersonnelTable(); + initializeNonpersonnelTable(); } From 6e1130dcc198c104fd21ccaafbc2f71cc53ea278 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 15:24:27 -0400 Subject: [PATCH 22/24] fix new init modal and table functions to add header row if table was empty --- src/js/components/table/subcomponents/data.js | 6 +- .../components/table/subcomponents/headers.js | 12 ++-- src/js/components/table/subcomponents/rows.js | 17 +++-- .../utils/data_utils/budget_data_handlers.js | 34 +++++++-- src/js/views/07_new_initiatives/helpers.js | 72 +++++++++++-------- 5 files changed, 94 insertions(+), 47 deletions(-) diff --git a/src/js/components/table/subcomponents/data.js b/src/js/components/table/subcomponents/data.js index 809ac0c..67c4b23 100644 --- a/src/js/components/table/subcomponents/data.js +++ b/src/js/components/table/subcomponents/data.js @@ -24,9 +24,9 @@ function fillTable(data) { data.forEach(item => { const row = document.createElement('tr'); Object.values(item).forEach(val => { - const cell = document.createElement('td'); - cell.innerHTML = val; - row.appendChild(cell); + const cell = document.createElement('td'); + cell.innerHTML = val; + row.appendChild(cell); }); tbody.appendChild(row); }); diff --git a/src/js/components/table/subcomponents/headers.js b/src/js/components/table/subcomponents/headers.js index 1304c8f..233f0af 100644 --- a/src/js/components/table/subcomponents/headers.js +++ b/src/js/components/table/subcomponents/headers.js @@ -1,21 +1,21 @@ -function addTableHeaders(header_array){ +function addTableHeaders(cols){ // 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) { + cols.forEach(col => { // Create a header cell element const headerCell = document.createElement('th'); - headerCell.textContent = headerText; + headerCell.textContent = col['title']; + headerCell.classList.add(col['className']); // 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); diff --git a/src/js/components/table/subcomponents/rows.js b/src/js/components/table/subcomponents/rows.js index 7a93f25..67d6491 100644 --- a/src/js/components/table/subcomponents/rows.js +++ b/src/js/components/table/subcomponents/rows.js @@ -1,14 +1,18 @@ import Header from "./headers.js"; import { formatCurrency } from "../../../utils/common_utils.js"; -async function addNewRow(data_dictionary){ +async function addNewRow(data_dictionary, columns = []){ + + console.log(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)); + Header.add(columns); + header_row = table.querySelector('thead tr'); } // initialize new row of data @@ -20,13 +24,14 @@ async function addNewRow(data_dictionary){ // Create new cell and add it to the row const newCell = document.createElement('td'); new_row.appendChild(newCell); - // the data has an appropriate class, add the info to the cell. Otherwise, keep empty cell + // if the data has an appropriate class, add the info to the cell. + // Otherwise, keep empty cell Object.keys(data_dictionary).forEach( (className) => { if (header_cell.classList.contains(className) ){ newCell.textContent = data_dictionary[className]; newCell.classList.add(className); } - }) + }); }); // Append the new row to the table body @@ -60,8 +65,8 @@ function saveRowEdits(row){ } const Rows = { - add : function(data_dictionary){ - addNewRow(data_dictionary) + add : function(data_dictionary, cols){ + addNewRow(data_dictionary, cols) }, saveEdits : function(row){ saveRowEdits(row) diff --git a/src/js/utils/data_utils/budget_data_handlers.js b/src/js/utils/data_utils/budget_data_handlers.js index bb297f7..187a046 100644 --- a/src/js/utils/data_utils/budget_data_handlers.js +++ b/src/js/utils/data_utils/budget_data_handlers.js @@ -35,13 +35,28 @@ export const FundLookupTable = { this.save(table); }, + getAll: function(key) { + // function to aggregate all approps or CCs for every fund in one array + const funds = this.retrieve(); + const ret = []; + for (const fund in funds) { + if (funds.hasOwnProperty(fund)) { + for (let i in funds[fund][key]){ + ret.push(funds[fund][key][i]); + } + } + } + return ret; + }, + getCostCenters : function() { // get current fund const fund = CurrentFund.number() if (this.retrieve()[fund]){ return this.retrieve()[fund]['cc']; } - return []; + // if no fund (ie. we're on the new initiative page), return all options + return this.getAll('cc'); }, getApprops : function() { @@ -50,7 +65,8 @@ export const FundLookupTable = { if (this.retrieve()[fund]){ return this.retrieve()[fund]['approp']; } - return []; + // if no fund (ie. we're on the new initiative page), return all options + return this.getAll('approp'); }, reset : function() { @@ -63,6 +79,16 @@ export const FundLookupTable = { listFunds : function(){ return Object.keys(this.retrieve()); }, + listFundNames : function(){ + const funds = this.retrieve(); + // initialize array + var ret = []; + Object.keys(funds).forEach( (fund_number) => { + var fund_name = funds[fund_number]['name']; + ret.push(fund_name); + }); + return ret; + }, editFund : function(fund){ const table = this.retrieve(); if (table[fund]){ @@ -119,9 +145,9 @@ export const AccountString = { return match ? match[0] : null; }, - build : function(approp, cc, obj = null) { + build : function(approp, cc, obj = null, fund = null) { // put together account string fund-approp-costcenter[-obj] (w optional object) - const fund = CurrentFund.number(); + if (!fund) { fund = CurrentFund.number() }; // hits error here approp = this.getNumber(approp); cc = this.getNumber(cc); diff --git a/src/js/views/07_new_initiatives/helpers.js b/src/js/views/07_new_initiatives/helpers.js index dfc133f..8f3d6ad 100644 --- a/src/js/views/07_new_initiatives/helpers.js +++ b/src/js/views/07_new_initiatives/helpers.js @@ -8,12 +8,37 @@ import NavButtons from '../../components/nav_buttons/nav_buttons.js' import { nextPage } from '../view_logic.js' import Subtitle from '../../components/header/header.js' import Sidebar from '../../components/sidebar/sidebar.js' +import { FundLookupTable, AccountString } from '../../utils/data_utils/budget_data_handlers.js' const explanation = `New initiative submissions will count as supplemental line items and will be the starting point for a conversation with both OB and ODFS, who will help with the details.` const dropdownOptions = ['N/A', 'One-Time', 'Recurring'] +const initiativesCols = [ + { title: 'Initiative Name', className: 'init-name' }, + { title: 'Account String', className: 'account-string' }, + { title: 'Ballpark Total Expenses', className: 'total', isCost: true }, + { title: 'Revenue', className: 'revenue', isCost: true }, + { title: 'Personnel Cost', className: 'personnel', isCost: true }, + { title: 'Non-personnel Cost', className: 'nonpersonnel', isCost: true }, + { title: 'One-time v. Recurring', className: 'rev-type' }, + { title: 'Edit', className : 'edit' }, + + // hide the explanation columns + { title: 'Q1', className: 'q1', hide: true }, + { title: 'Q2', className: 'q2', hide: true }, + { title: 'Q3', className: 'q3', hide: true }, + + // hide the account string breakdown columns too + { title: 'Appropriation Name', className: 'approp-name', hide: true }, + { title: 'Cost Center Name', className: 'cc-name', hide: true }, + { title: 'Appropriation', className: 'approp', hide: true }, + { title: 'Cost Center', className: 'cc', hide: true }, + { title: 'Fund Name', className: 'fund-name', hide: true }, + { title: 'Fund', className: 'fund', hide: true } +]; + export function initializePageView() { // Prepare page view Body.reset(); @@ -53,24 +78,25 @@ export function setUpForm() { Form.new('modal-body'); // general questions - Form.NewField.shortText('Initiative Name:', 'Initiative Name', true); - Form.NewField.longText('What is the business case for the Initiative?', 'Q1', true); + Form.NewField.shortText('Initiative Name:', 'init-name', true); + Form.NewField.longText('What is the business case for the Initiative?', 'q1', true); Form.NewField.longText(`Why is the initiative needed? What is the value-add to residents? - What is the Department’s plan for implementing the Initiative?`, 'Q2', true); - Form.NewField.longText(`Why can’t the Initiative be funded with the Department’s baseline budget?`, 'Q3', true); + What is the Department’s plan for implementing the Initiative?`, 'q2', true); + Form.NewField.longText(`Why can’t the Initiative be funded with the Department’s baseline budget?`, 'q3', true); // TODO: Edit to drop down + Form.NewField.dropdown('Fund:', 'fund-name', FundLookupTable.listFundNames(), true); Form.NewField.dropdown('Appropriation (if known):', 'approp-name', FundLookupTable.getApprops(), true); Form.NewField.dropdown('Cost Center (if known):', 'cc-name', FundLookupTable.getCostCenters(), true); // Numbers Form.NewField.numericInput('What is your ballpark estimate of TOTAL ADDITONAL expenses associated with this initiative?', - 'Ballpark Total Expenses', false); - Form.NewField.numericInput('Estimate of ADDITONAL personnel cost?', 'Personnel Cost', false); - Form.NewField.numericInput('Estimate of ADDITONAL nonpersonnel cost?', 'Non-personnel Cost', false); - Form.NewField.numericInput('Estimate of ADDITONAL revenue (if applicable)?', 'Revenue', false); + 'total', false); + Form.NewField.numericInput('Estimate of ADDITONAL personnel cost?', 'personnel', false); + Form.NewField.numericInput('Estimate of ADDITONAL nonpersonnel cost?', 'nonpersonnel', false); + Form.NewField.numericInput('Estimate of ADDITONAL revenue (if applicable)?', 'revenue', false); Form.NewField.dropdown(`If there will be revenue, is it one-time or recurring?`, - 'One-time v. Recurring', dropdownOptions); + 'rev-type', dropdownOptions); Form.SubmitButton.add(); @@ -79,23 +105,6 @@ export function setUpForm() { } function assignClasses() { - // record columns and their classes - const initiativesCols = [ - { title: 'Initiative Name', className: 'init-name' }, - { title: 'Account String', className: 'account-string' }, - { title: 'Ballpark Total Expenses', className: 'total', isCost: true }, - { title: 'Revenue', className: 'revenue', isCost: true }, - { title: 'Personnel Cost', className: 'personnel', isCost: true }, - { title: 'Non-personnel Cost', className: 'nonpersonnel', isCost: true }, - { title: 'One-time v. Recurring', className: 'rev-type' }, - { title: 'Edit', className : 'edit' }, - - // hide the explanation columns - { title: 'Q1', className: 'q1', hide: true }, - { title: 'Q2', className: 'q2', hide: true }, - { title: 'Q3', className: 'q3', hide: true }, - ]; - // assign cost classes Table.Columns.assignClasses(initiativesCols) } @@ -104,7 +113,7 @@ export async function initializeInitTable(){ Table.clear(); // load table data from storage if(await Table.Data.load()) { - //after table is loaded, fill it + // after table is loaded, fill it Table.Columns.addAtEnd(Table.Buttons.edit_confirm_btns, "Edit"); assignClasses(); // enable editing @@ -127,10 +136,17 @@ function rowOnEdit(){ function handleNewInitSubmission(event){ // get answers from form, hide form, show answers in table const responses = Form.fetchAllResponses(event); + + // create account string columns + responses['approp'] = AccountString.getNumber(responses['approp-name']); + responses['cc'] = AccountString.getNumber(responses['cc-name']); + responses['fund'] = AccountString.getNumber(responses['fund-name']); + responses['account-string'] = AccountString.build(responses['approp-name'], responses['cc-name'], null, responses['fund']); + // make sure it's not an empty response if (Object.values(responses)[0] != ''){ // add data to table - Table.Rows.add(responses); + Table.Rows.add(responses, initiativesCols); // save it Table.save(); // show updated table From 2e38c3c3ae380012a811353a0bb8aa62489776a3 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 15:44:41 -0400 Subject: [PATCH 23/24] fix detail appearance for new inits --- src/js/components/tooltip/tooltip.js | 6 +++++- src/js/views/07_new_initiatives/helpers.js | 10 ++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/js/components/tooltip/tooltip.js b/src/js/components/tooltip/tooltip.js index 894db29..c86dd88 100644 --- a/src/js/components/tooltip/tooltip.js +++ b/src/js/components/tooltip/tooltip.js @@ -22,8 +22,9 @@ function showAccountString(row){ const approp = Cell.getText(row, 'approp-name'); const cc = Cell.getText(row, 'cc-name'); const obj = Cell.getText(row, 'object-name'); + const fund = Cell.getText(row, 'fund-name'); var message = - `Fund: ${CurrentFund.name()}
+ `Fund: ${fund}
Appropriation: ${approp}
Cost Center: ${cc}`; if (obj) { message += `
Object: ${obj}`} @@ -176,6 +177,9 @@ export const Tooltip = { case 'revenue': linkAccountStringCol(); break; + case 'new-inits': + linkAccountStringCol(); + break; default: break; diff --git a/src/js/views/07_new_initiatives/helpers.js b/src/js/views/07_new_initiatives/helpers.js index 8f3d6ad..7b4ebbf 100644 --- a/src/js/views/07_new_initiatives/helpers.js +++ b/src/js/views/07_new_initiatives/helpers.js @@ -101,7 +101,7 @@ export function setUpForm() { Form.SubmitButton.add(); // Initialize form submission to table data - Modal.Submit.init(handleNewInitSubmission); + Modal.Submit.init(submitNewRow); } function assignClasses() { @@ -110,7 +110,6 @@ function assignClasses() { } export async function initializeInitTable(){ - Table.clear(); // load table data from storage if(await Table.Data.load()) { // after table is loaded, fill it @@ -119,6 +118,7 @@ export async function initializeInitTable(){ // enable editing Table.Buttons.Edit.init(rowOnEdit, Table.save); // show table + Table.save(); Table.show(); } } @@ -133,7 +133,7 @@ function rowOnEdit(){ Table.Cell.createDropdown('rev-type', dropdownOptions); } -function handleNewInitSubmission(event){ +function submitNewRow(event){ // get answers from form, hide form, show answers in table const responses = Form.fetchAllResponses(event); @@ -145,13 +145,11 @@ function handleNewInitSubmission(event){ // make sure it's not an empty response if (Object.values(responses)[0] != ''){ + Modal.hide(); // add data to table Table.Rows.add(responses, initiativesCols); - // save it Table.save(); - // show updated table initializeInitTable(); - Modal.hide(); Table.Buttons.AddRow.updateText('Add another new initiative'); } } From 9268321170ea6278d37fc5377cc076492159a358 Mon Sep 17 00:00:00 2001 From: Katrina Wheelan Date: Fri, 26 Jul 2024 16:44:02 -0400 Subject: [PATCH 24/24] fix detail bug on new init page --- src/js/components/table/subcomponents/rows.js | 4 +--- src/js/components/table/table.js | 2 +- src/js/utils/data_utils/local_storage_handlers.js | 1 + src/js/views/04_personnel/helpers.js | 1 - src/js/views/07_new_initiatives/helpers.js | 15 +++++++++------ src/js/views/view_logic.js | 6 ++++++ 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/js/components/table/subcomponents/rows.js b/src/js/components/table/subcomponents/rows.js index 67d6491..512bdb6 100644 --- a/src/js/components/table/subcomponents/rows.js +++ b/src/js/components/table/subcomponents/rows.js @@ -2,9 +2,7 @@ import Header from "./headers.js"; import { formatCurrency } from "../../../utils/common_utils.js"; async function addNewRow(data_dictionary, columns = []){ - - console.log(data_dictionary); - + // Get the table element by its ID const table = document.getElementById('main-table'); diff --git a/src/js/components/table/table.js b/src/js/components/table/table.js index 5b9fb97..94f29dd 100644 --- a/src/js/components/table/table.js +++ b/src/js/components/table/table.js @@ -45,7 +45,7 @@ const Table = { clear : clearTable, hide : hideTable, show : showTable, - save : function() { + save : async function() { // remove the detail text Tooltip.unlink(); saveTableData(); diff --git a/src/js/utils/data_utils/local_storage_handlers.js b/src/js/utils/data_utils/local_storage_handlers.js index 04e1d90..dd5e9b1 100644 --- a/src/js/utils/data_utils/local_storage_handlers.js +++ b/src/js/utils/data_utils/local_storage_handlers.js @@ -42,6 +42,7 @@ export function saveTableData() { var save_as = CurrentPage.load(); } localStorage.setItem(save_as, convertToJSON(table, ['Edit'])); + console.log('saved'); Sidebar.updateTotals(); } diff --git a/src/js/views/04_personnel/helpers.js b/src/js/views/04_personnel/helpers.js index a97915d..d63fb8d 100644 --- a/src/js/views/04_personnel/helpers.js +++ b/src/js/views/04_personnel/helpers.js @@ -129,7 +129,6 @@ function handleSubmitNewJob(event){ // edit inputs from modal responses['avg-salary'] = unformatCurrency(responses['avg-salary']); responses['fringe'] = parseFloat(responses['fringe']) / 100; - console.log(responses['approp-name']); responses['account-string'] = AccountString.build(responses['approp-name'], responses['cc-name']) responses['approp'] = AccountString.getNumber(responses['approp-name']); responses['cc'] = AccountString.getNumber(responses['cc-name']); diff --git a/src/js/views/07_new_initiatives/helpers.js b/src/js/views/07_new_initiatives/helpers.js index 7b4ebbf..a3d1723 100644 --- a/src/js/views/07_new_initiatives/helpers.js +++ b/src/js/views/07_new_initiatives/helpers.js @@ -19,10 +19,10 @@ const initiativesCols = [ { title: 'Initiative Name', className: 'init-name' }, { title: 'Account String', className: 'account-string' }, { title: 'Ballpark Total Expenses', className: 'total', isCost: true }, - { title: 'Revenue', className: 'revenue', isCost: true }, { title: 'Personnel Cost', className: 'personnel', isCost: true }, { title: 'Non-personnel Cost', className: 'nonpersonnel', isCost: true }, - { title: 'One-time v. Recurring', className: 'rev-type' }, + { title: 'Revenue', className: 'revenue', isCost: true }, + { title: 'Revenue Type', className: 'rev-type' }, { title: 'Edit', className : 'edit' }, // hide the explanation columns @@ -110,16 +110,19 @@ function assignClasses() { } export async function initializeInitTable(){ + // load table data from storage if(await Table.Data.load()) { // after table is loaded, fill it Table.Columns.addAtEnd(Table.Buttons.edit_confirm_btns, "Edit"); assignClasses(); - // enable editing - Table.Buttons.Edit.init(rowOnEdit, Table.save); // show table - Table.save(); Table.show(); + // enable editing + Table.Buttons.Edit.init(rowOnEdit, Table.save); + } else { + Table.clear(); + console.log('no data'); } } @@ -128,7 +131,6 @@ function rowOnEdit(){ Table.Cell.createTextbox('revenue', true); Table.Cell.createTextbox('personnel', true); Table.Cell.createTextbox('nonpersonnel', true); - Table.Cell.createTextbox('account-string'); Table.Cell.createTextbox('init-name'); Table.Cell.createDropdown('rev-type', dropdownOptions); } @@ -136,6 +138,7 @@ function rowOnEdit(){ function submitNewRow(event){ // get answers from form, hide form, show answers in table const responses = Form.fetchAllResponses(event); + console.log(responses); // create account string columns responses['approp'] = AccountString.getNumber(responses['approp-name']); diff --git a/src/js/views/view_logic.js b/src/js/views/view_logic.js index 48febd6..4d20432 100644 --- a/src/js/views/view_logic.js +++ b/src/js/views/view_logic.js @@ -85,6 +85,12 @@ export function lastPage(){ // clean up current page if (CLEANUP[page_state]) { CLEANUP[page_state]() }; + + // if on new-inits, circle back to fund selection + if (CurrentPage.load() == 'new-inits'){ + visitPage('baseline-landing'); + return; + } // Check if there is a next key if (currentIndex >= 1) {