From 103dcc0f0ae7d1c9f666026064baf6aaff965a4e Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Sat, 18 Jan 2025 07:21:07 -0600 Subject: [PATCH 1/7] eliminated legacy code debt --- src/public/js/toolkeeper.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/public/js/toolkeeper.js b/src/public/js/toolkeeper.js index 384bbf8..cbc22a4 100644 --- a/src/public/js/toolkeeper.js +++ b/src/public/js/toolkeeper.js @@ -1,12 +1,3 @@ -function btnToSpinner() { - const submitBtn = document.querySelector("#testButton"); - - submitBtn.addEventListener("click", () => { - submitBtn.outerHTML = - ''; - }); -} - function openInNewTab() { const x = globalThis.open(); From 26bf583adc757ac62cd3b3b439c0d9cdb0a1964b Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Sat, 18 Jan 2025 09:00:27 -0600 Subject: [PATCH 2/7] Refactor Handlebars templates for improved structure and consistency - Removed unnecessary whitespace in the `_printerFriendly.hbs` file for cleaner code. - Updated the `_recentlyUpdated.hbs` file to replace legacy variable names and streamline the rendering logic. These changes enhance the maintainability and readability of the Handlebars templates. --- src/views/partials/_printerFriendly.hbs | 2 +- src/views/partials/dashboard/_recentlyUpdated.hbs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/partials/_printerFriendly.hbs b/src/views/partials/_printerFriendly.hbs index 04520c0..3e13d84 100644 --- a/src/views/partials/_printerFriendly.hbs +++ b/src/views/partials/_printerFriendly.hbs @@ -54,7 +54,7 @@
{{toolID}}
{{description}}
{{barcode}}
{{barcode}}
- {{/each}} + {{/each}} {{/withGroup}} diff --git a/src/views/partials/dashboard/_recentlyUpdated.hbs b/src/views/partials/dashboard/_recentlyUpdated.hbs index b1d33f3..bc710ec 100644 --- a/src/views/partials/dashboard/_recentlyUpdated.hbs +++ b/src/views/partials/dashboard/_recentlyUpdated.hbs @@ -4,7 +4,7 @@
- +
Barcode (Link To Tool) @@ -13,9 +13,9 @@ Updated - {{#if recentlyUpdatedTools}} + {{#if tools}} - {{#each recentlyUpdatedTools}} + {{#each tools}} {{barcode}} From 46c2b9d3b40bf8bbd46776b489ddaf533eacf05c Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Sat, 18 Jan 2025 09:00:43 -0600 Subject: [PATCH 3/7] Enhance dashboard functionality and update layout - Added a new route to fetch cached data in `dashboard.routes.js`, improving data retrieval efficiency. - Updated the Handlebars template to use a more generic variable name (`tools` instead of `recentlyUpdatedTools`) for better clarity and consistency. - Modified meta tags in `main.hbs` to reflect updated branding and improve SEO, including changes to the description and canonical link. These changes enhance the user experience and maintainability of the dashboard component. --- src/routes/dashboard.routes.js | 11 +++++++++++ src/views/dashboard.hbs | 2 +- src/views/layouts/main.hbs | 8 ++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/routes/dashboard.routes.js b/src/routes/dashboard.routes.js index b4aeb01..615292f 100644 --- a/src/routes/dashboard.routes.js +++ b/src/routes/dashboard.routes.js @@ -3,7 +3,11 @@ import { generatePrinterFriendlyToolList, getRecentlyUpdatedTools } from '../mid import {initCachedContent} from '../middleware/util.js' import { hoistOnboarding, dashboardOnboardingComplete, skipStep } from '../middleware/onboarding.js' import { Router } from 'express' + export const dashboardRouter = Router() + +// @desc Show dashboard +// @route GET /dashboard dashboardRouter.get( '/', initCachedContent, @@ -12,6 +16,13 @@ dashboardRouter.get( hoistOnboarding, renderDashboard ) + +// @desc Get cached data +// @route GET /dashboard/cache +dashboardRouter.get("/cache", initCachedContent, (req, res) => { + res.json(res.locals.cachedContent); +}); + dashboardRouter.get('/skip-step/:step', skipStep, renderDashboard) dashboardRouter.post('/onboarding-complete', dashboardOnboardingComplete) // src\routes\dashboard.routes.js diff --git a/src/views/dashboard.hbs b/src/views/dashboard.hbs index cddf707..827e6bd 100644 --- a/src/views/dashboard.hbs +++ b/src/views/dashboard.hbs @@ -3,7 +3,7 @@
- {{#if recentlyUpdatedTools}} + {{#if tools}} {{> dashboard/_recentlyUpdated}} {{/if}}
diff --git a/src/views/layouts/main.hbs b/src/views/layouts/main.hbs index 68bac94..15b373d 100644 --- a/src/views/layouts/main.hbs +++ b/src/views/layouts/main.hbs @@ -5,10 +5,10 @@ - - + + - + @@ -40,7 +40,7 @@ {{> _modals}} - + \ No newline at end of file From 3b47079d1148148987cd946adf7393a53bd15d88 Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Sat, 18 Jan 2025 09:00:54 -0600 Subject: [PATCH 4/7] Refactor tool management logic and enhance error handling - Updated the `getCheckedInTools` function to use a consistent casing for "Stockroom". - Improved the `createTool` function to check for duplicates individually, providing detailed error messages for each duplicate found. - Enhanced error handling in the `createTool` function to return a structured error response with a list of duplicates. - Updated the `getRecentlyUpdatedTools` function to use a more generic variable name for better clarity. - Integrated `initCachedContent` middleware into several routes to ensure cached data is utilized effectively. These changes improve the maintainability, clarity, and user experience of the tool management features. --- src/middleware/tool.js | 58 ++++++++++++++++++++++----- src/public/js/toolkeeper.js | 78 ++++++++++++++++++++++++++++++------- src/routes/tool.routes.js | 13 ++++--- 3 files changed, 118 insertions(+), 31 deletions(-) diff --git a/src/middleware/tool.js b/src/middleware/tool.js index 7de6656..2d99413 100644 --- a/src/middleware/tool.js +++ b/src/middleware/tool.js @@ -107,7 +107,7 @@ const getCheckedInTools = async (tenant) => { try { return await Tool.find() .where("serviceAssignment.type") - .equals("stockroom") + .equals("Stockroom") .where("tenant") .equals(tenant) .where("archived") @@ -229,14 +229,49 @@ const createTool = async (req, res, next) => { throw new Error("Missing required fields"); } - const existing = await Tool.findOne({ - $or: [{ serialNumber }, { barcode }, { toolID }], - tenant, - }); + // Check for duplicates individually, putting errors in an + // error list of objects with the tool, field, and value + + const errorList = []; + if (serialNumber) { + const existingSerial = await Tool.findOne({ serialNumber, tenant }); + if (existingSerial) { + errorList.push({ + field: "Serial Number", + value: serialNumber, + tool: existingSerial, + }); + } + } + if (barcode) { + const existingBarcode = await Tool.findOne({ barcode, tenant }); + if (existingBarcode) { + errorList.push({ + field: "Barcode", + value: barcode, + tool: existingBarcode, + }); + } + } + if (toolID) { + const existingToolID = await Tool.findOne({ toolID, tenant }); + if (existingToolID) { + errorList.push({ + field: "Tool ID", + value: toolID, + tool: existingToolID, + }); + } + } - if (existing) { - res.locals.tools = mutateToArray(existing); - throw new Error("Tool already exists"); + if (errorList.length > 0) { + const error = new Error("Duplicate Tool(s) Found"); + error.errorList = errorList.map(err => ({ + cause: err.field, + duplicateValue: err.value, + existingTool: err.tool._id + })); + throw error; } const newTool = await Tool.create({ @@ -290,7 +325,10 @@ const createTool = async (req, res, next) => { }); res.locals.message = error.message; - res.status(500).redirect("back"); + if (error.errorList) { + res.locals.errorList = error.errorList; + } + return res.render("results"); } }; @@ -614,7 +652,7 @@ export const getRecentlyUpdatedTools = async (req, res, next) => { metadata: { tenant: req.user.tenant, toolCount: tools.length }, }); - res.locals.recentlyUpdatedTools = tools; + res.locals.tools = tools; return next(); } catch (error) { req.logger.error({ diff --git a/src/public/js/toolkeeper.js b/src/public/js/toolkeeper.js index cbc22a4..0f879f9 100644 --- a/src/public/js/toolkeeper.js +++ b/src/public/js/toolkeeper.js @@ -1,4 +1,3 @@ - function openInNewTab() { const x = globalThis.open(); const newPage = x.document.createElement("div"); @@ -21,32 +20,81 @@ function populateDashboard(cachedData) { // Clear any existing content serviceAssignmentsContainer.innerHTML = ""; + console.log( + "All cached service assignments:", + cachedData.serviceAssignments, + ); + // Loop through the cached service assignments and render them // biome-ignore lint/complexity/noForEach: cachedData.serviceAssignments.forEach((assignment) => { + console.log("Processing assignment:", assignment); + console.log( + "toolCount:", + assignment.toolCount, + "active:", + assignment.active, + ); + if (assignment.toolCount > 0 && assignment.active) { - const assignmentElement = document.createElement("div"); - assignmentElement.innerHTML = ` - - ${assignment.toolCount} | ${assignment.jobNumber} - ${assignment.jobName} - - `; - - // Append each active assignment to the container - serviceAssignmentsContainer.appendChild(assignmentElement); + const tr = document.createElement("tr"); + const td = document.createElement("td"); + td.innerHTML = ` ${assignment.toolCount} | ${assignment.jobNumber} - ${assignment.jobName}`; + tr.appendChild(td); + serviceAssignmentsContainer.appendChild(tr); + } else { + console.log( + "Assignment filtered out:", + assignment.jobNumber, + "toolCount:", + assignment.toolCount, + "active:", + assignment.active, + ); } }); } } -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("DOMContentLoaded", async () => { const cachedData = JSON.parse(localStorage.getItem("serviceData")); + const cachedHash = localStorage.getItem("serviceDataHash"); + + // sourcery skip: avoid-function-declarations-in-blocks + async function fetchAndUpdateCache() { + console.log("Fetching fresh data from server..."); + try { + const response = await fetch("/dashboard/cache"); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + // If the hash is different, update the cache + if (data.hash !== cachedHash) { + console.log("Cache is stale, updating with new data..."); + localStorage.setItem("serviceData", JSON.stringify(data.data)); + localStorage.setItem("serviceDataHash", data.hash); + return data.data; + } + console.log("Cache is up to date"); + return cachedData; + } catch (error) { + console.error("Error fetching cached data:", error); + return cachedData; // Fall back to cached data if fetch fails + } + } - if (cachedData) { - // Pass the cached data to a function to populate the dashboard - populateDashboard(cachedData); + if (cachedData && cachedHash) { + console.log("Found cached data, checking if it's still valid..."); + const data = await fetchAndUpdateCache(); + populateDashboard(data); } else { - console.log("No cached data found."); + console.log("No cached data found, fetching from server..."); + const data = await fetchAndUpdateCache(); + if (data) { + populateDashboard(data); + } } }); diff --git a/src/routes/tool.routes.js b/src/routes/tool.routes.js index b0f4082..9454499 100644 --- a/src/routes/tool.routes.js +++ b/src/routes/tool.routes.js @@ -14,6 +14,7 @@ import { import { sanitizeReqBody, hoistSearchParamsToBody, + initCachedContent, } from "../middleware/util.js"; import { listAllSAs } from "../middleware/serviceAssignment.js"; import { @@ -46,21 +47,21 @@ toolRouter.post( ); // save the tool's new service assignment to the database. -toolRouter.post("/submitCheckInOut", submitCheckInOut, renderResults); +toolRouter.post("/submitCheckInOut", submitCheckInOut, initCachedContent, renderResults); // create new tool -toolRouter.post("/submit", sanitizeReqBody, createTool, redirectToDashboard); +toolRouter.post("/submit", sanitizeReqBody, createTool, initCachedContent, redirectToDashboard); // render batch creation page toolRouter.get("/batchCreate", renderBatchCreationPage); // validate and create a batch of submitted tools -toolRouter.post("/batchCreate", sanitizeReqBody, batchCreateTools); +toolRouter.post("/batchCreate", sanitizeReqBody, batchCreateTools, initCachedContent, redirectToDashboard); // update tool -toolRouter.post("/update", sanitizeReqBody, updateTool, renderResults); +toolRouter.post("/update", sanitizeReqBody, updateTool, initCachedContent, renderResults); // archive tool -toolRouter.get("/archive/:id", archiveTool, getAllTools, renderResults); +toolRouter.get("/archive/:id", archiveTool, initCachedContent, getAllTools, renderResults); // archive tool -toolRouter.get("/unarchive/:id", unarchiveTool, getAllTools, renderResults); +toolRouter.get("/unarchive/:id", unarchiveTool, initCachedContent, getAllTools, renderResults); // get tool by id toolRouter.get("/:id", getToolByID, listAllSAs, renderEditTool); From d64d21c1ffde8e2336b37286ff7c57c9edcef022 Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Sat, 18 Jan 2025 09:42:46 -0600 Subject: [PATCH 5/7] Refactor user routes to enhance functionality and maintainability - Updated the `/disableUser/:id` route to include `getUsers` middleware, ensuring user data is refreshed after disabling a user. - Modified the `/:id/delete` route to also include `getUsers`, improving the user list consistency after a deletion. These changes improve the user management experience and maintain the clarity of route functionalities. --- src/routes/settings/users.routes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/settings/users.routes.js b/src/routes/settings/users.routes.js index 62f7e3b..b83c1a3 100644 --- a/src/routes/settings/users.routes.js +++ b/src/routes/settings/users.routes.js @@ -23,11 +23,11 @@ userSettingsRouter.post('/resetPW/:id', resetPassword, renderSettingsUsers) // @desc disable user // @endpoint POST /settings/users/disableUser/:id -userSettingsRouter.post('/disableUser/:id', disableUser, renderSettingsUsers) +userSettingsRouter.post('/disableUser/:id', disableUser, getUsers, renderSettingsUsers) // @desc delete user // @endpoint GET /settings/users/:id/delete -userSettingsRouter.get("/:id/delete", deleteUser, renderSettingsUsers); +userSettingsRouter.get("/:id/delete", deleteUser, getUsers, renderSettingsUsers); // @desc create new user // @endpoint POST /settings/users/create From 42a1bfd929d246abc1bcd1266796180ee44b7666 Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Sat, 18 Jan 2025 09:42:58 -0600 Subject: [PATCH 6/7] Refactor duplicate tool checking logic in createTool function - Simplified the duplicate checking process by consolidating queries into a single database call, improving performance and readability. - Enhanced error handling to provide a structured response with detailed information about duplicate tools found, including field names and values. - Removed individual duplicate checks for serial number, barcode, and tool ID, streamlining the code. These changes improve the maintainability and efficiency of the tool creation process. --- src/middleware/tool.js | 51 +++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/src/middleware/tool.js b/src/middleware/tool.js index 2d99413..56fd350 100644 --- a/src/middleware/tool.js +++ b/src/middleware/tool.js @@ -229,47 +229,36 @@ const createTool = async (req, res, next) => { throw new Error("Missing required fields"); } - // Check for duplicates individually, putting errors in an - // error list of objects with the tool, field, and value - - const errorList = []; + // Build query conditions for duplicate checking + const queryConditions = []; if (serialNumber) { - const existingSerial = await Tool.findOne({ serialNumber, tenant }); - if (existingSerial) { - errorList.push({ - field: "Serial Number", - value: serialNumber, - tool: existingSerial, - }); - } + queryConditions.push({ serialNumber: { $eq: serialNumber }, tenant: { $eq: tenant } }); } if (barcode) { - const existingBarcode = await Tool.findOne({ barcode, tenant }); - if (existingBarcode) { - errorList.push({ - field: "Barcode", - value: barcode, - tool: existingBarcode, - }); - } + queryConditions.push({ barcode: { $eq: barcode }, tenant: { $eq: tenant } }); } if (toolID) { - const existingToolID = await Tool.findOne({ toolID, tenant }); - if (existingToolID) { - errorList.push({ - field: "Tool ID", - value: toolID, - tool: existingToolID, - }); - } + queryConditions.push({ toolID: { $eq: toolID }, tenant: { $eq: tenant } }); } - if (errorList.length > 0) { + const existingTools = await Tool.find({ $or: queryConditions }); + + if (existingTools.length > 0) { + const errorList = existingTools.map((tool) => { + if (tool.serialNumber === serialNumber) { + return { field: "Serial Number", value: serialNumber, tool }; + } + if (tool.barcode === barcode) { + return { field: "Barcode", value: barcode, tool }; + } + return { field: "Tool ID", value: toolID, tool }; + }); + const error = new Error("Duplicate Tool(s) Found"); - error.errorList = errorList.map(err => ({ + error.errorList = errorList.map((err) => ({ cause: err.field, duplicateValue: err.value, - existingTool: err.tool._id + existingTool: err.tool._id, })); throw error; } From 643ef0eee71bbdb4d1adaa26a4eee87d2548bbe5 Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Sat, 18 Jan 2025 09:52:13 -0600 Subject: [PATCH 7/7] Enhance print functionality and improve dashboard rendering - Updated the `openInNewTab` function to open a new window with enhanced security features, including a Content Security Policy and removal of script tags from the printed content. - Refactored the dashboard rendering logic to create links for service assignments, improving clarity and maintainability by using `textContent` instead of `innerHTML`. These changes improve the user experience and security of the printing feature while enhancing the readability of the dashboard code. --- src/public/js/toolkeeper.js | 61 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/public/js/toolkeeper.js b/src/public/js/toolkeeper.js index 0f879f9..9168abb 100644 --- a/src/public/js/toolkeeper.js +++ b/src/public/js/toolkeeper.js @@ -1,10 +1,32 @@ function openInNewTab() { - const x = globalThis.open(); - const newPage = x.document.createElement("div"); - newPage.width = "100%"; - newPage.height = "100%"; - newPage.innerHTML = document.getElementById("printerFriendlyTools").innerHTML; - x.document.body.appendChild(newPage); + // Open new window with specific security features + const printWindow = window.open('', '_blank', + 'width=800,height=600,menubar=no,toolbar=no,location=no,status=no,noopener,noreferrer' + ); + + if (printWindow) { + // Set security headers for the new window + printWindow.document.write(''); + printWindow.document.write(''); + printWindow.document.write('Printer Friendly View'); + + // Get the content and create a sanitized copy + const content = document.getElementById("printerFriendlyTools"); + if (content) { + // Create a deep clone to avoid manipulating the original DOM + const sanitizedContent = content.cloneNode(true); + // Remove any script tags from the cloned content + const scripts = sanitizedContent.getElementsByTagName('script'); + while (scripts[0]) { + scripts[0].parentNode.removeChild(scripts[0]); + } + + printWindow.document.body.appendChild(sanitizedContent); + } + + printWindow.document.write(''); + printWindow.document.close(); + } } if (document.getElementsByClassName("fa-print").length > 0) { console.log("Print button found."); @@ -20,37 +42,18 @@ function populateDashboard(cachedData) { // Clear any existing content serviceAssignmentsContainer.innerHTML = ""; - console.log( - "All cached service assignments:", - cachedData.serviceAssignments, - ); - // Loop through the cached service assignments and render them // biome-ignore lint/complexity/noForEach: cachedData.serviceAssignments.forEach((assignment) => { - console.log("Processing assignment:", assignment); - console.log( - "toolCount:", - assignment.toolCount, - "active:", - assignment.active, - ); - if (assignment.toolCount > 0 && assignment.active) { const tr = document.createElement("tr"); const td = document.createElement("td"); - td.innerHTML = ` ${assignment.toolCount} | ${assignment.jobNumber} - ${assignment.jobName}`; + const link = document.createElement("a"); + link.href = `/tool/search?searchBy=serviceAssignment&searchTerm=${assignment._id}`; + link.textContent = `${assignment.toolCount} | ${assignment.jobNumber} - ${assignment.jobName}`; + td.appendChild(link); tr.appendChild(td); serviceAssignmentsContainer.appendChild(tr); - } else { - console.log( - "Assignment filtered out:", - assignment.jobNumber, - "toolCount:", - assignment.toolCount, - "active:", - assignment.active, - ); } }); }