Renewable Resource Inventory Generator

Renewable Resource Inventory Generator

What does each row in the data log represent? (e.g., Location, Quarter, Facility)

Define Resource Types

Define the categories you will track. The "Unit" should represent the metric (Capacity or Generation) you will log for this resource.

Current Catalog

Log the data (quantities in the units you defined) for each tracked location/period. Edit cells directly (values should be numeric).

Location/Period Actions

Overall Resource Totals

This sums the total quantity logged for each defined resource.

Generate the summary using the "Next" button.

${escapeHTML(r.name)} (${escapeHTML(r.unit)})

`; resourceInputs.list.appendChild(div); }); }; const addResource = () => { const name = resourceInputs.name.value.trim(); const unit = resourceInputs.unit.value.trim(); if (!name || !unit) { alert('Both Resource Name and Metric Unit are required.'); return; } const newKey = name.toLowerCase().replace(/[^a-z0-9]/g, ''); if (resources.find(c => c.key === newKey)) { alert('A resource with a similar name already exists.'); return; } resources.push({ id: resourceIdCounter++, name: name, unit: unit, key: newKey }); resourceInputs.name.value = ''; resourceInputs.unit.value = ''; renderResources(); // Automatically switch to the log tab to update it if (currentTab === 3) renderLogDashboard(); }; const removeResource = (keyToRemove) => { const index = resources.findIndex(c => c.key === keyToRemove); if (index !== -1) { resources.splice(index, 1); // Remove the associated data column from all log entries logData.forEach(logEntry => { delete logEntry[keyToRemove]; }); renderResources(); } }; // --- Data Logging Management (Tab 3) --- const renderLogDashboard = () => { const analysisLabel = infoInputs.unitAnalysis.value || 'Location/Period'; infoInputs.unitAnalysisLabel.textContent = analysisLabel; if (resources.length === 0) { logElements.theadRow.innerHTML = `${analysisLabel}Actions`; logElements.tbody.innerHTML = 'Please define resources in the Catalog tab first.'; logElements.msg.style.display = 'block'; return; } logElements.msg.style.display = 'none'; // Render Header logElements.theadRow.innerHTML = `${analysisLabel}`; resources.forEach(r => { logElements.theadRow.innerHTML += `${escapeHTML(r.name)} (${escapeHTML(r.unit)})`; }); logElements.theadRow.innerHTML += `Actions`; // Render Body logElements.tbody.innerHTML = ''; if (logData.length === 0) { logElements.tbody.innerHTML = `Click "+ Add New Log Entry" to start logging data.`; return; } logData.forEach(period => { const tr = document.createElement('tr'); let rowHTML = `${escapeHTML(period.location)}`; resources.forEach(r => { // Use zero if the value is missing (to handle older logs after adding a new resource) const value = period[r.key] === undefined ? 0 : period[r.key]; rowHTML += `${value}`; }); rowHTML += ``; tr.innerHTML = rowHTML; logElements.tbody.appendChild(tr); }); }; const addLogEntry = () => { if (resources.length === 0) { alert('Please define at least one resource in the Catalog tab before adding a log entry.'); return; } const newLog = { id: logIdCounter++, location: 'New Facility / Period' }; // Initialize new log with zero for all current resources resources.forEach(r => { newLog[r.key] = 0; }); logData.push(newLog); renderLogDashboard(); }; const removeLogEntry = (id) => { logData = logData.filter(p => p.id !== id); renderLogDashboard(); }; const updateLogData = (id, field, value) => { const logEntry = logData.find(p => p.id === id); if (logEntry) { if (field === 'location') { logEntry.location = value; } else { const num = parseFloat(value); logEntry[field] = isNaN(num) || num < 0 ? 0 : num; } } }; // --- Summary & Export (Tab 4) --- const calculateTotals = () => { const totals = {}; let grandTotal = 0; resources.forEach(r => { totals[r.key] = { name: r.name, unit: r.unit, total: 0 }; }); logData.forEach(period => { resources.forEach(r => { const value = parseFloat(period[r.key]) || 0; totals[r.key].total += value; grandTotal += value; }); }); return { totals: Object.values(totals), grandTotal: grandTotal }; }; const generateSummary = () => { const { totals, grandTotal } = calculateTotals(); summaryOutput.innerHTML = ''; if (grandTotal === 0) { summaryOutput.innerHTML = '

No valid numerical data has been logged yet.

'; return; } let summaryHTML = ''; totals.forEach(t => { summaryHTML += `
${escapeHTML(t.name)} (${escapeHTML(t.unit)}) ${t.total.toFixed(2)}
`; }); summaryOutput.innerHTML = ` ${summaryHTML}
Overall Portfolio Total: ${grandTotal.toFixed(2)}
`; }; const downloadTxt = () => { if (resources.length === 0) return; const { totals, grandTotal } = calculateTotals(); const info = { projectName: infoInputs.projectName.value || 'N/A', scope: infoInputs.scope.value || 'N/A', period: infoInputs.period.value || 'N/A', unitAnalysis: infoInputs.unitAnalysis.value || 'Location/Period' }; let content = `RENEWABLE RESOURCE INVENTORY REPORT\n`; content += "========================================================\n\n"; // 1. Metadata content += "1. INVENTORY SETUP\n"; content += `Project: ${info.projectName}\n`; content += `Scope: ${info.scope}\n`; content += `Period: ${info.period}\n\n`; // 2. Resource Catalog content += "2. RESOURCE CATALOG\n"; resources.forEach(r => { content += `- ${r.name} (Unit: ${r.unit})\n`; }); content += "\n"; // 3. Raw Data content += "3. RAW DATA LOG\n"; content += "--------------------------------------------------------\n"; const header = [info.unitAnalysis, ...resources.map(r => `${r.name} (${r.unit})`)]; content += header.map(h => h.padEnd(20).substring(0, 20)).join('| ') + '\n'; content += "--------------------------------------------------------\n"; logData.forEach(period => { let line = (period.location || 'N/A').padEnd(20).substring(0, 20); resources.forEach(r => { const value = (period[r.key] || 0).toFixed(2); line += `| ${value.padEnd(20).substring(0, 20)}`; }); content += line + '\n'; }); content += "--------------------------------------------------------\n\n"; // 4. Summary content += "4. SUMMARY TOTALS\n"; totals.forEach(t => { content += `- Total ${t.name} (${t.unit}):\t${t.total.toFixed(2)}\n`; }); content += `\nOVERALL GRAND TOTAL: ${grandTotal.toFixed(2)}\n`; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `inventory_${info.projectName.replace(/ /g, '_')}_Report.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } if (resources.length === 0 || logData.length === 0) { alert('Cannot generate PDF: Please define resources and log data first.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('l', 'mm', 'a4'); // Landscape for tables const data = getProtocolData(); const { totals, grandTotal } = calculateTotals(); const margin = 10; let yPos = 15; const pageWidth = doc.internal.pageSize.getWidth(); const usableWidth = pageWidth - (margin * 2); // --- Title and Metadata --- doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(`Renewable Resource Inventory: ${data.setup.projectName}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 10; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(108, 117, 125); doc.text(`Scope: ${data.setup.scope} | Period: ${data.setup.period}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 10; // --- 1. Summary Totals Table --- doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(52, 73, 94); doc.text("1. Overall Portfolio Totals", margin, yPos); yPos += 3; const summaryHead = [['Resource', 'Unit', 'Total Quantity']]; const summaryBody = totals.map(t => [t.name, t.unit, t.total.toFixed(2)]); summaryBody.push(['OVERALL GRAND TOTAL', 'N/A', grandTotal.toFixed(2)]); doc.autoTable({ startY: yPos, head: [summaryHead], body: summaryBody, theme: 'striped', headStyles: { fillColor: [52, 73, 94], textColor: [255, 255, 255], fontSize: 9 }, styles: { fontSize: 9, cellPadding: 2, textColor: [52, 73, 94] }, columnStyles: { 0: { fontStyle: 'bold' }, 2: { halign: 'right' } }, margin: { left: margin, right: margin } }); yPos = doc.autoTable.previous ? (doc.autoTable.previous.finalY + 10) : (yPos + 30); // --- 2. Raw Data Log Table --- doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(52, 73, 94); doc.text("2. Raw Data Log", margin, yPos); yPos += 3; const logHead = [[data.setup.unitAnalysis, ...resources.map(r => `${r.name} (${r.unit})`)]]; const logBodyData = logData.map(p => [ p.location, ...resources.map(r => (p[r.key] || 0).toFixed(2)) ]); doc.autoTable({ startY: yPos, head: logHead, body: logBodyData, theme: 'grid', headStyles: { fillColor: [213, 230, 246], textColor: [44, 62, 80], fontSize: 9 }, styles: { fontSize: 9, cellPadding: 2, textColor: [52, 73, 94] }, columnStyles: { 0: { fontStyle: 'bold' } }, margin: { left: margin, right: margin } }); doc.save('renewable_inventory_report.pdf'); }; // --- Event Listeners --- // Tab Buttons tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); // Next/Prev Navigation nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 2 Actions (Resource Catalog) resourceInputs.addBtn.addEventListener('click', addResource); resourceInputs.list.addEventListener('click', (e) => { if (e.target.dataset.removeKey) { removeResource(e.target.dataset.removeKey); if (currentTab === 2) renderResources(); } }); // Tab 3 Actions (Data Logging) logElements.addBtn.addEventListener('click', addLogEntry); logElements.tbody.addEventListener('click', (e) => { if (e.target.dataset.removeLogId) { removeLogEntry(parseInt(e.target.dataset.removeLogId)); } }); logElements.tbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updateLogData(id, field, e.target.textContent); } }, true); // Update analysis label whenever input changes infoInputs.unitAnalysis.addEventListener('input', () => { if (currentTab === 3) renderLogDashboard(); }); // Tab 4 Actions downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- renderResources(); showTab(1); });
Scroll to Top