{"id":2953,"date":"2025-09-05T16:45:57","date_gmt":"2025-09-05T16:45:57","guid":{"rendered":"https:\/\/garyhengeveld.com\/wordpress\/?p=2953"},"modified":"2025-09-05T16:57:21","modified_gmt":"2025-09-05T16:57:21","slug":"tool-maint-track","status":"publish","type":"post","link":"https:\/\/garyhengeveld.com\/wordpress\/tool-maint-track\/","title":{"rendered":"Tool Maintenance Tracker"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" \/>\n  <title>Tool Maintenance Tracker<\/title>\n  <style>\n    body { font-family: Arial, sans-serif; padding: 20px; }\n    input, button, textarea { margin: 5px; padding: 5px; }\n    table { border-collapse: collapse; width: 100%; margin-top: 20px; }\n    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }\n    th { background-color: #f4f4f4; }\n    td input { width: 100%; }\n    .partRow { display: flex; gap: 10px; margin-bottom: 5px; }\n    .partRow input { flex: 1; }\n  <\/style>\n<\/head>\n<body>\n \n\n  <h3>Add Tool<\/h3>\n  <label>Unit Number:<\/label><br>\n  <input type=\"text\" id=\"unitNumber\" \/><br>\n  <label>Description:<\/label><br>\n  <input type=\"text\" id=\"description\" \/><br>\n  <label>Last Service Date:<\/label><br>\n  <input type=\"date\" id=\"lastServiceDate\" \/><br>\n  <label>Interval (days):<\/label><br>\n  <input type=\"number\" id=\"intervalDays\" \/><br>\n\n  <h4>Parts Needed<\/h4>\n  <div id=\"partsContainer\">\n    <div class=\"partRow\">\n      <input type=\"text\" placeholder=\"Part Name\" class=\"partName\" \/>\n      <input type=\"number\" placeholder=\"Quantity\" class=\"partQty\" \/>\n      <input type=\"text\" placeholder=\"Part Number\" class=\"partNum\" \/>\n    <\/div>\n  <\/div>\n  <button onclick=\"addPartRow()\"> Add Another Part<\/button><br>\n  <button onclick=\"addTool()\"> Add Tool<\/button>\n\n  <h3>Tools List<\/h3>\n  <table id=\"toolsTable\">\n    <thead>\n      <tr>\n        <th>Unit #<\/th>\n        <th>Description<\/th>\n        <th>Last Service<\/th>\n        <th>Interval<\/th>\n        <th>Next Service<\/th>\n        <th>Parts Needed<\/th>\n        <th>Actions<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody><\/tbody>\n  <\/table>\n\n  <h3> Generate Shopping List<\/h3>\n  <label>Select date:<\/label>\n  <input type=\"date\" id=\"targetDate\" \/>\n  <button onclick=\"generateShoppingList()\">Generate<\/button>\n\n  <h4>Shopping List:<\/h4>\n  <table id=\"shoppingListTable\">\n    <thead>\n      <tr>\n        <th>Qnty<\/th>\n        <th>Part<\/th>\n        <th>Part Number<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody><\/tbody>\n  <\/table>\n\n  <h3> Save \/ Load<\/h3>\n  <button onclick=\"saveToXML()\"> Save to XML<\/button>\n  <input type=\"file\" accept=\".xml\" onchange=\"loadFromXML(event)\" \/>\n\n <script>\n  let tools = [];\n\n  function addPartRow() {\n    const container = document.getElementById('partsContainer');\n    const row = document.createElement('div');\n    row.className = 'partRow';\n    row.innerHTML = `\n      <input type=\"text\" placeholder=\"Part Name\" class=\"partName\" \/>\n      <input type=\"number\" placeholder=\"Quantity\" class=\"partQty\" \/>\n      <input type=\"text\" placeholder=\"Part Number\" class=\"partNum\" \/>\n    `;\n    container.appendChild(row);\n  }\n\n  function collectParts() {\n    const partRows = document.querySelectorAll('.partRow');\n    const parts = [];\n    partRows.forEach(row => {\n      const name = row.querySelector('.partName').value.trim();\n      const qty = parseInt(row.querySelector('.partQty').value);\n      const partNum = row.querySelector('.partNum').value.trim();\n      if (name && !isNaN(qty) && partNum) {\n        parts.push({ name, qty, partNum });\n      }\n    });\n    return parts;\n  }\n\n  function addTool() {\n    const unit = document.getElementById('unitNumber').value.trim();\n    const desc = document.getElementById('description').value.trim();\n    const lastDate = new Date(document.getElementById('lastServiceDate').value);\n    const interval = parseInt(document.getElementById('intervalDays').value);\n    const parts = collectParts();\n\n    if (!unit || !desc || isNaN(lastDate) || isNaN(interval) || parts.length === 0) {\n      alert(\"Please fill in all fields correctly.\");\n      return;\n    }\n\n    const nextDate = new Date(lastDate);\n    nextDate.setDate(nextDate.getDate() + interval);\n\n    const existing = tools.find(t => t.unit === unit);\n    if (existing) {\n      existing.desc = desc;\n      existing.lastDate = lastDate;\n      existing.interval = interval;\n      existing.nextDate = nextDate;\n      parts.forEach(p => {\n        const match = existing.parts.find(ep => ep.name === p.name && ep.partNum === p.partNum);\n        if (match) match.qty += p.qty;\n        else existing.parts.push(p);\n      });\n    } else {\n      tools.push({ unit, desc, lastDate, interval, nextDate, parts });\n    }\n\n    updateTable();\n    document.getElementById('unitNumber').value = '';\n    document.getElementById('description').value = '';\n    document.getElementById('lastServiceDate').value = '';\n    document.getElementById('intervalDays').value = '';\n    document.getElementById('partsContainer').innerHTML = '';\n    addPartRow();\n  }\n\n  function formatParts(parts) {\n    return parts.map(p => `${p.name}:${p.qty}:${p.partNum}`).join(', ');\n  }\n\n  function parseParts(input) {\n    const parts = [];\n    input.split(',').forEach(item => {\n      const [name, qty, partNum] = item.split(':').map(s => s.trim());\n      if (name && qty && partNum && !isNaN(qty)) {\n        parts.push({ name, qty: parseInt(qty), partNum });\n      }\n    });\n    return parts;\n  }\n\n  function updateTable() {\n    const tbody = document.querySelector('#toolsTable tbody');\n    tbody.innerHTML = '';\n    tools.forEach((tool, index) => {\n      const row = document.createElement('tr');\n      row.innerHTML = `\n        <td><input value=\"${tool.unit}\" onchange=\"editTool(${index}, 'unit', this.value)\" \/><\/td>\n        <td><input value=\"${tool.desc}\" onchange=\"editTool(${index}, 'desc', this.value)\" \/><\/td>\n        <td><input type=\"date\" value=\"${tool.lastDate.toISOString().split('T')[0]}\" onchange=\"editTool(${index}, 'lastDate', this.value)\" \/><\/td>\n        <td><input type=\"number\" value=\"${tool.interval}\" onchange=\"editTool(${index}, 'interval', this.value)\" \/><\/td>\n        <td>${tool.nextDate.toISOString().split('T')[0]}<\/td>\n        <td><input value=\"${formatParts(tool.parts)}\" onchange=\"editTool(${index}, 'parts', this.value)\" \/><\/td>\n        <td><button onclick=\"deleteTool(${index})\">Delete<\/button><\/td>\n      `;\n      tbody.appendChild(row);\n    });\n  }\n\n  function editTool(index, field, value) {\n    const tool = tools[index];\n    if (field === 'unit') tool.unit = value;\n    if (field === 'desc') tool.desc = value;\n    if (field === 'lastDate') {\n      tool.lastDate = new Date(value);\n      tool.nextDate = new Date(tool.lastDate);\n      tool.nextDate.setDate(tool.lastDate.getDate() + tool.interval);\n    }\n    if (field === 'interval') {\n      tool.interval = parseInt(value);\n      tool.nextDate = new Date(tool.lastDate);\n      tool.nextDate.setDate(tool.lastDate.getDate() + tool.interval);\n    }\n    if (field === 'parts') {\n      tool.parts = parseParts(value);\n    }\n    updateTable();\n  }\n\n  function deleteTool(index) {\n    tools.splice(index, 1);\n    updateTable();\n  }\n\n  function generateShoppingList() {\n    const target = new Date(document.getElementById('targetDate').value);\n    if (isNaN(target)) {\n      alert(\"Please select a valid date.\");\n      return;\n    }\n\n    const partsMap = {};\n    tools.forEach(tool => {\n      if (tool.nextDate <= target) {\n        tool.parts.forEach(({ name, qty, partNum }) => {\n          const key = `${name}|${partNum}`;\n          partsMap[key] = (partsMap[key] || 0) + qty;\n        });\n      }\n    });\n\n    const tbody = document.querySelector('#shoppingListTable tbody');\n    tbody.innerHTML = '';\n\n    if (Object.keys(partsMap).length === 0) {\n      const row = document.createElement('tr');\n      row.innerHTML = `<td colspan=\"3\">No parts needed by selected date.<\/td>`;\n      tbody.appendChild(row);\n      return;\n    }\n\n    for (const key in partsMap) {\n      const [name, partNum] = key.split('|');\n      const qty = partsMap[key];\n      const row = document.createElement('tr');\n      row.innerHTML = `<td>${qty}<\/td><td>${name}<\/td><td>${partNum}<\/td>`;\n      tbody.appendChild(row);\n    }\n  }\n\n  function saveToXML() {\n    const xmlDoc = document.implementation.createDocument('', '', null);\n    const root = xmlDoc.createElement('ToolList');\n\n    tools.forEach(tool => {\n      const toolEl = xmlDoc.createElement('Tool');\n      toolEl.setAttribute('unit', tool.unit);\n      toolEl.setAttribute('description', tool.desc);\n      toolEl.setAttribute('lastServiceDate', tool.lastDate.toISOString().split('T')[0]);\n      toolEl.setAttribute('interval', tool.interval);\n      toolEl.setAttribute('nextServiceDate', tool.nextDate.toISOString().split('T')[0]);\n\n      tool.parts.forEach(part => {\n        const partEl = xmlDoc.createElement('Part');\n        partEl.setAttribute('name', part.name);\n        partEl.setAttribute('qty', part.qty);\n        partEl.setAttribute('partNum', part.partNum);\n        toolEl.appendChild(partEl);\n      });\n\n      root.appendChild(toolEl);\n    });\n\n    xmlDoc.appendChild(root);\n    const serializer = new XMLSerializer();\n    const xmlString = serializer.serializeToString(xmlDoc);\n    const blob = new Blob([xmlString], { type: 'application\/xml' });\n    const link = document.createElement('a');\n    link.href = URL.createObjectURL(blob);\n    link.download = 'tool_list.xml';\n    link.click();\n  }\n\n  function loadFromXML(event) {\n    const file = event.target.files[0];\n    if (!file) return;\n\n    const reader = new FileReader();\n    reader.onload = function(e) {\n      const parser = new DOMParser();\n      const xml = parser.parseFromString(e.target.result, 'application\/xml');\n      const toolNodes = xml.getElementsByTagName('Tool');\n      tools = [];\n\n      for (let toolNode of toolNodes) {\n        const unit = toolNode.getAttribute('unit');\n        const desc = toolNode.getAttribute('description');\n        const lastDate = new Date(toolNode.getAttribute('lastServiceDate'));\n        const interval = parseInt(toolNode.getAttribute('interval'));\n        const nextDate = new Date(toolNode.getAttribute('nextServiceDate'));\n\n        const parts = [];\n        const partNodes = toolNode.getElementsByTagName('Part');\n        for (let partNode of partNodes) {\n          const name = partNode.getAttribute('name');\n          const qty = parseInt(partNode.getAttribute('qty'));\n          const partNum = partNode.getAttribute('partNum');\n          parts.push({ name, qty, partNum });\n        }\n\n        tools.push({ unit, desc, lastDate, interval, nextDate, parts });\n      }\n\n      updateTable();\n    };\n    reader.readAsText(file);\n\n    }\n  <\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>Tool Maintenance Tracker Add Tool Unit Number: Description: Last Service Date: Interval (days): Parts Needed Add Another Part Add Tool Tools List Unit # Description Last Service Interval Next Service Parts Needed Actions Generate Shopping List Select date: Generate Shopping List: Qnty Part Part Number Save \/ Load Save to XML<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2953","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/posts\/2953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/comments?post=2953"}],"version-history":[{"count":7,"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/posts\/2953\/revisions"}],"predecessor-version":[{"id":2960,"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/posts\/2953\/revisions\/2960"}],"wp:attachment":[{"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/media?parent=2953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/categories?post=2953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/garyhengeveld.com\/wordpress\/wp-json\/wp\/v2\/tags?post=2953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}