Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 37 additions & 10 deletions src/middleware/tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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");
}
};

Expand Down Expand Up @@ -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({
Expand Down
98 changes: 70 additions & 28 deletions src/public/js/toolkeeper.js
Original file line number Diff line number Diff line change
@@ -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 =
'<span class="loading loading-infinity loading-lg"></span>';
});
}
if (printWindow) {
// Set security headers for the new window
printWindow.document.write('<!DOCTYPE html><html><head>');
printWindow.document.write('<meta http-equiv="Content-Security-Policy" content="default-src \'self\'; script-src \'none\';">');
printWindow.document.write('<title>Printer Friendly View</title></head><body>');

// 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('</body></html>');
printWindow.document.close();
}
}
if (document.getElementsByClassName("fa-print").length > 0) {
console.log("Print button found.");
Expand All @@ -34,28 +46,58 @@ function populateDashboard(cachedData) {
// biome-ignore lint/complexity/noForEach: <explanation>
cachedData.serviceAssignments.forEach((assignment) => {
if (assignment.toolCount > 0 && assignment.active) {
const assignmentElement = document.createElement("div");
assignmentElement.innerHTML = `
<tr>
<td><a href="/tool/search?searchBy=serviceAssignment&searchTerm=${assignment._id}"> ${assignment.toolCount} | ${assignment.jobNumber} - ${assignment.jobName}</td>
</tr>
`;

// 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);
}
}
});

Expand Down
11 changes: 11 additions & 0 deletions src/routes/dashboard.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
4 changes: 2 additions & 2 deletions src/routes/settings/users.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions src/routes/tool.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import {
sanitizeReqBody,
hoistSearchParamsToBody,
initCachedContent,
} from "../middleware/util.js";
import { listAllSAs } from "../middleware/serviceAssignment.js";
import {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/views/dashboard.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</div>
<div class="flex flex-row">
<div class="flex-1" id="recentlyChanged">
{{#if recentlyUpdatedTools}}
{{#if tools}}
{{> dashboard/_recentlyUpdated}}
{{/if}}
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/views/layouts/main.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="ToolKeeper In Design">
<meta name="author" content="Dave Luhman">
<meta name="description" content="ToolKeeper by ADO Software">
<meta name="author" content="ADO Software">
<link rel="icon" href="/img/favicon.ico">
<link rel="canonical" href="https://toolkeeper.dev.ado.software">
<link rel="canonical" href="https://toolkeeper.site">
<meta name="namespace" content="user">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css"
crossorigin="anonymous" referrerpolicy="no-referrer" />
Expand Down Expand Up @@ -40,7 +40,7 @@
{{> _modals}}

<script src="/js/util.js" type="module" defer></script>
<script src="/js/toolkeeper.js" type="module" defer></script>
<script src="/js/toolkeeper.js" type="module"></script>
</body>

</html>
2 changes: 1 addition & 1 deletion src/views/partials/_printerFriendly.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<div class="data-cell">{{toolID}}</div>
<div class="col-span-2 data-cell">{{description}}</div>
<div class="data-cell"><div class="barcode">{{barcode}}</div>{{barcode}}</div>
{{/each}}
{{/each}}
</section>
{{/withGroup}}
</body>
Expand Down
6 changes: 3 additions & 3 deletions src/views/partials/dashboard/_recentlyUpdated.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<tr>
<td>
<div id="printButton" class="md:mr-3 md:items-start">
<label onclick="openInNewTab()"><i class="fa-solid fa-print"></i></label>
<i class="fa-solid fa-print"></i>
</div>
</td>
<th>Barcode (Link To Tool)</th>
Expand All @@ -13,9 +13,9 @@
<th>Updated</th>
</tr>
</thead>
{{#if recentlyUpdatedTools}}
{{#if tools}}
<tbody>
{{#each recentlyUpdatedTools}}
{{#each tools}}
<tr>
<td></td>
<td id="barcode"><a href="/tool/{{id}}">{{barcode}}</a></td>
Expand Down
Loading