diff --git a/src/middleware/tool.js b/src/middleware/tool.js index 7de66561..56fd3507 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,38 @@ const createTool = async (req, res, next) => { throw new Error("Missing required fields"); } - const existing = await Tool.findOne({ - $or: [{ serialNumber }, { barcode }, { toolID }], - tenant, - }); + // Build query conditions for duplicate checking + const queryConditions = []; + if (serialNumber) { + queryConditions.push({ serialNumber: { $eq: serialNumber }, tenant: { $eq: tenant } }); + } + if (barcode) { + queryConditions.push({ barcode: { $eq: barcode }, tenant: { $eq: tenant } }); + } + if (toolID) { + queryConditions.push({ toolID: { $eq: toolID }, tenant: { $eq: tenant } }); + } + + const existingTools = await Tool.find({ $or: queryConditions }); - if (existing) { - res.locals.tools = mutateToArray(existing); - throw new Error("Tool already exists"); + 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) => ({ + cause: err.field, + duplicateValue: err.value, + existingTool: err.tool._id, + })); + throw error; } const newTool = await Tool.create({ @@ -290,7 +314,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 +641,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 384bbf87..9168abb3 100644 --- a/src/public/js/toolkeeper.js +++ b/src/public/js/toolkeeper.js @@ -1,20 +1,32 @@ -function btnToSpinner() { - const submitBtn = document.querySelector("#testButton"); +function openInNewTab() { + // 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' + ); - submitBtn.addEventListener("click", () => { - submitBtn.outerHTML = - ''; - }); -} + 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]); + } -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); + printWindow.document.body.appendChild(sanitizedContent); + } + + printWindow.document.write(''); + printWindow.document.close(); + } } if (document.getElementsByClassName("fa-print").length > 0) { console.log("Print button found."); @@ -34,28 +46,58 @@ function populateDashboard(cachedData) { // biome-ignore lint/complexity/noForEach: cachedData.serviceAssignments.forEach((assignment) => { 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"); + 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); } }); } } -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/dashboard.routes.js b/src/routes/dashboard.routes.js index b4aeb016..615292ff 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/routes/settings/users.routes.js b/src/routes/settings/users.routes.js index 62f7e3b5..b83c1a36 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 diff --git a/src/routes/tool.routes.js b/src/routes/tool.routes.js index b0f40820..94544993 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); diff --git a/src/views/dashboard.hbs b/src/views/dashboard.hbs index cddf707b..827e6bdd 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 68bac94f..15b373d5 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 diff --git a/src/views/partials/_printerFriendly.hbs b/src/views/partials/_printerFriendly.hbs index 04520c03..3e13d847 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 b1d33f35..bc710ec3 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}}