`;
dashboardContent.innerHTML += card;
});
};
const renderLocationsTable = () => {
const tableBody = document.getElementById('locations-table-body');
if (!tableBody) return;
tableBody.innerHTML = '';
locations.forEach(loc => {
const ratingStars = '★'.repeat(loc.rating) + '☆'.repeat(5 - loc.rating);
const row = `
| ${loc.name} |
${loc.city} |
${loc.type} |
${ratingStars} |
|
`;
tableBody.innerHTML += row;
});
};
const renderSummaryStats = () => {
const statsContainer = document.getElementById('summary-stats');
if (!statsContainer) return;
const totalLocations = locations.length;
const averageRating = totalLocations > 0 ? (locations.reduce((sum, loc) => sum + parseInt(loc.rating, 10), 0) / totalLocations).toFixed(1) : 'N/A';
const locationTypes = new Set(locations.map(loc => loc.type)).size;
statsContainer.innerHTML = `
Total Locations
${totalLocations}
Average Rating
${averageRating}
Location Types
${locationTypes}
`;
};
// --- FORM HANDLING & CRUD LOGIC ---
const resetForm = () => {
if (!form) return;
form.reset();
locationIdInput.value = '';
formTitle.textContent = 'Add New Location';
submitBtn.textContent = 'Add Location';
cancelBtn.classList.add('hidden');
};
form.addEventListener('submit', (e) => {
e.preventDefault();
const id = locationIdInput.value;
const locationData = {
name: document.getElementById('location-name').value,
address: document.getElementById('location-address').value,
city: document.getElementById('location-city').value,
state: document.getElementById('location-state').value,
zip: document.getElementById('location-zip').value,
image: document.getElementById('location-image').value,
type: document.getElementById('location-type').value,
rating: parseInt(document.getElementById('location-rating').value, 10),
contactPerson: document.getElementById('contact-person').value,
contactPhone: document.getElementById('contact-phone').value,
notes: document.getElementById('location-notes').value,
};
if (id) { // Editing existing location
const index = locations.findIndex(loc => loc.id == id);
if (index !== -1) {
locations[index] = { ...locations[index], ...locationData };
}
} else { // Adding new location
if (!locationData.name) {
alert("Location Name is required.");
return;
}
locationData.id = Date.now(); // Simple unique ID
locations.push(locationData);
}
resetForm();
updateAllViews();
});
cancelBtn.addEventListener('click', () => {
resetForm();
});
// Make handlers globally accessible for inline onclick
window.handleEdit = (id) => {
const location = locations.find(loc => loc.id === id);
if (!location) return;
// Populate form
locationIdInput.value = location.id;
document.getElementById('location-name').value = location.name;
document.getElementById('location-address').value = location.address;
document.getElementById('location-city').value = location.city;
document.getElementById('location-state').value = location.state;
document.getElementById('location-zip').value = location.zip;
document.getElementById('location-image').value = location.image;
document.getElementById('location-type').value = location.type;
document.getElementById('location-rating').value = location.rating;
document.getElementById('contact-person').value = location.contactPerson;
document.getElementById('contact-phone').value = location.contactPhone;
document.getElementById('location-notes').value = location.notes;
// Update UI
formTitle.textContent = 'Edit Location';
submitBtn.textContent = 'Save Changes';
cancelBtn.classList.remove('hidden');
window.scrollTo(0, 0); // Scroll to top of form
};
window.handleDelete = (id) => {
if (confirm('Are you sure you want to delete this location?')) {
locations = locations.filter(loc => loc.id !== id);
updateAllViews();
}
};
// --- PDF GENERATION ---
downloadPdfBtn.addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const pdfContainer = document.getElementById('pdf-content-container');
if (!pdfContainer) return;
// 1. Generate clean HTML for the PDF
let pdfHtml = `
Location Scouting Dashboard
`;
pdfHtml += `
| Name |
Address |
Type |
Rating |
Notes |
`;
locations.forEach(loc => {
const ratingStars = '★'.repeat(loc.rating) + '☆'.repeat(5 - loc.rating);
pdfHtml += `
| ${loc.name} |
${loc.city}, ${loc.state} |
${loc.type} |
${ratingStars} |
${loc.notes || ''} |
`;
});
pdfHtml += `
`;
pdfContainer.innerHTML = pdfHtml;
// 2. Use html2canvas to capture the generated content
html2canvas(pdfContainer, {
scale: 2, // Higher scale for better quality
useCORS: true,
width: pdfContainer.scrollWidth,
height: pdfContainer.scrollHeight,
windowWidth: pdfContainer.scrollWidth,
windowHeight: pdfContainer.scrollHeight
}).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'pt',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const ratio = canvasWidth / canvasHeight;
const newCanvasWidth = pdfWidth;
const newCanvasHeight = newCanvasWidth / ratio;
// Check if content exceeds one page
if (newCanvasHeight > pdfHeight) {
// This simple implementation adds the image and lets it be scrollable in PDF viewers.
// For true multi-page, a more complex slicing logic would be needed.
console.warn("PDF content may exceed one page.");
}
pdf.addImage(imgData, 'PNG', 0, 0, newCanvasWidth, newCanvasHeight);
pdf.save('Location_Scouting_Dashboard.pdf');
// Clean up
pdfContainer.innerHTML = '';
}).catch(err => {
console.error("PDF generation failed:", err);
alert("Sorry, there was an error creating the PDF.");
});
});
// --- INITIALIZATION ---
const updateAllViews = () => {
renderDashboard();
renderLocationsTable();
renderSummaryStats();
};
// Initial load
showTab(0); // Show Dashboard tab first
updateAllViews();
});