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
No resources defined yet.
Log the data (quantities in the units you defined) for each tracked location/period. Edit cells directly (values should be numeric).
| Location/Period | Actions |
|---|
Please define categories in the Resource Catalog tab first.
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 = `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);
});
