/** * Initialises the facility data table with the provided data * @param {Array} data - Array of facility objects to display * @param {boolean} force - Whether to force reinitialization */ function initialiseFacilityData(data, force = false) { // Only prevent multiple initializations if not forcing if (!force && isInitialized) { console.debug('Facility data already initialized, skipping...'); return; } try { // Validate data format if (!Array.isArray(data)) { throw new Error('Invalid data format: expected array'); } // Store the data in sessionStorage for persistence sessionStorage.setItem('facilityData', JSON.stringify(data)); // Ensure table exists const table = document.querySelector('#facilityTable'); if (!table) { console.error('Table not found in DOM. Available elements:', Array.from(document.querySelectorAll('table')).map(t => t.id || 'no-id')); throw new Error('Facility table not found in DOM'); } // Clear existing table content const tbody = table.querySelector('tbody'); if (tbody) { tbody.innerHTML = ''; } else { console.warn('No tbody found in table, creating one'); const newTbody = document.createElement('tbody'); table.appendChild(newTbody); } // Initialize filteredData with all data filteredData = data; // Calculate total pages totalPages = Math.ceil(filteredData.length / itemsPerPage); // Set current page to 1 currentPage = 1; // Update table with paginated data updateTableWithPagination(); // Set up table controls (sorting and filtering) setupTableControls(); // Mark as initialized isInitialized = true; } catch (error) { error_log('Error initialising facility data:', error); } } /** * Renders the facility data in the table * @param {Array} data - Array of facility objects to display */ function renderFacilityTable(data) { try { const tbody = document.querySelector('#facilityTable tbody'); if (!tbody) { return; } // Clear existing table content tbody.innerHTML = ''; // Render each row data.forEach((facility, index) => { if (!facility) return; const row = document.createElement('tr'); row.className = 'facility-row'; row.style.minHeight = '60px'; // Add minimum height for consistent row sizing // Format coordinates to be more readable const coordinates = `${parseFloat(facility.lat).toFixed(4)}, ${parseFloat(facility.lng).toFixed(4)}`; // Create category badge with color based on category const categoryClass = getCategoryColorClass(facility.category); row.innerHTML = ` ${escapeHtml(facility.id)}
${escapeHtml(facility.title)}
${escapeHtml(facility.category)}
${escapeHtml(facility.description)}
${facility.description.length > 100 ? `` : ''}
${escapeHtml(formatAddress(facility))}
${escapeHtml(facility.postcode)} ${escapeHtml(coordinates)} ${escapeHtml(facility.contributor)}
${isAdmin() ? ` ` : ''}
`; tbody.appendChild(row); }); // If no data, show a message if (data.length === 0) { const emptyRow = document.createElement('tr'); emptyRow.innerHTML = `

No facilities found

`; tbody.appendChild(emptyRow); } } catch (error) { error_log('Error in renderFacilityTable:', error); } } /** * Safely escapes HTML special characters to prevent XSS attacks * @param {*} unsafe - The value to escape * @returns {string} The escaped string */ function escapeHtml(unsafe) { if (unsafe === null || unsafe === undefined) { return ''; } return unsafe .toString() .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /** * (helper function) Formats the facility address from its components * @param {Object} facility - The facility object containing address components * @returns {string} The formatted address */ function formatAddress(facility) { if (!facility) return ''; const parts = [ facility.houseNumber, facility.streetName, facility.town, facility.county, facility.postcode ].filter(Boolean); return parts.join(', '); } /** * (helper function) Checks if the current user has admin privileges * @returns {boolean} True if user is admin, false otherwise */ function isAdmin() { // Check if auth service is available and has user data if (window.auth && window.auth.getUser()) { const authUser = window.auth.getUser(); console.log('Auth service user data:', authUser); if (authUser && (authUser.accessLevel === 1 || authUser.accessLevel === 0)) { console.log('User is admin according to auth service'); return true; } } // Fallback to localStorage const user = JSON.parse(localStorage.getItem('user') || '{}'); console.log('Checking admin status from localStorage:', user); const isAdminUser = user && (user.accessLevel === 1 || user.accessLevel === 0); console.log('Is admin according to localStorage:', isAdminUser); return isAdminUser; } /** * Checks if the current user is authenticated * @returns {boolean} True if authenticated, false otherwise */ function isAuthenticated() { const token = localStorage.getItem('token'); return !!token; } // Pagination state let currentPage = 1; let itemsPerPage = 10; let totalPages = 1; let filteredData = []; // Store the pagination handler function let paginationHandler = null; // Add initialization state tracking let isInitialized = false; // Initialize modals once let updateModal, deleteModal, createModal; let formHandlersInitialized = false; document.addEventListener('DOMContentLoaded', function() { // Initialize modals once const modals = document.querySelectorAll('.modal'); modals.forEach((modal, index) => { if (modal.id === 'updateModal') { updateModal = new bootstrap.Modal(modal); // Add event listener for when update modal is shown modal.addEventListener('show.bs.modal', async function(event) { // Get the button that triggered the modal const button = event.relatedTarget; // Get the facility ID from the data attribute const facilityId = button.getAttribute('data-facility-id'); if (!facilityId) { return; } try { // Get facility data from session storage const storedData = JSON.parse(sessionStorage.getItem('facilityData') || '[]'); const facility = storedData.find(f => f.id === parseInt(facilityId)); if (!facility) { return; } // Pre-fill the form with facility data const form = this.querySelector('#updateForm'); if (!form) { return; } // Map facility data to form fields const fieldMappings = { 'idUpdate': facility.id, 'titlUpdate': facility.title, 'cateUpdate': facility.category, 'descUpdate': facility.description, 'hnumUpdate': facility.houseNumber, 'strtUpdate': facility.streetName, 'cntyUpdate': facility.county, 'townUpdate': facility.town, 'postUpdate': facility.postcode, 'lngUpdate': facility.lng, 'latUpdate': facility.lat, 'contUpdate': facility.contributor }; // Set each field value Object.entries(fieldMappings).forEach(([fieldName, value]) => { const input = form.querySelector(`[name="${fieldName}"]`); if (input) { input.value = value || ''; } else { console.warn(`Field ${fieldName} not found in form`); } }); } catch (error) { console.error('Error pre-filling update form:', error); } }); } else if (modal.id === 'deleteModal') { deleteModal = new bootstrap.Modal(modal); // Add event listener for when delete modal is shown modal.addEventListener('show.bs.modal', function(event) { // Get the button that triggered the modal const button = event.relatedTarget; // Get the facility ID from the data attribute const facilityId = button.getAttribute('data-facility-id'); // Set the facility ID in the form const idInput = this.querySelector('[name="idDelete"]'); if (idInput && facilityId) { idInput.value = facilityId; // Get facility data from session storage for confirmation text const storedData = JSON.parse(sessionStorage.getItem('facilityData') || '[]'); const facility = storedData.find(f => f.id === parseInt(facilityId)); if (facility) { const confirmationText = document.getElementById('deleteConfirmationText'); if (confirmationText) { confirmationText.textContent = `${facility.title} (${facility.category})`; } } } else { console.error('Could not find id input or facility ID in delete modal'); } }); } else if (modal.id === 'createModal') { createModal = new bootstrap.Modal(modal); } else if (modal.id === 'statusModal') { console.log('Status modal will be handled by comments.js'); } }); // Set up form handlers with a small delay to ensure DOM is fully loaded setTimeout(() => { if (!formHandlersInitialized) { console.log('Setting up form handlers...'); setupFormHandlers(); formHandlersInitialized = true; } }, 100); // Initialize facility data if not already initialized if (!isInitialized) { const storedData = sessionStorage.getItem('facilityData'); if (storedData) { try { const parsedData = JSON.parse(storedData); initialiseFacilityData(parsedData); } catch (error) { error_log('Error parsing stored facility data:', error); } } } }); // Handle create form submission function setupFormHandlers() { // Only look for create form if user is admin if (isAdmin()) { // Create form handler const createForm = document.getElementById('createForm'); if (createForm) { console.log('Found create form, attaching submit handler'); createForm.addEventListener('submit', async function(e) { e.preventDefault(); // Prevent duplicate submissions if (this.submitting) { return; } this.submitting = true; if (!isAdmin()) { alert('You must be an admin to create facilities'); this.submitting = false; return; } const formData = new FormData(this); // Set the contributor to the current user's username formData.set('contCreate', JSON.parse(localStorage.getItem('user'))?.username); try { // Use authFetch instead of regular fetch const response = await window.authFetch('/facilitycontroller.php', { method: 'POST', body: formData }); if (!response.ok) { const errorText = await response.text(); console.error('Server response:', errorText); throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success) { const storedData = JSON.parse(sessionStorage.getItem('facilityData') || '[]'); storedData.push(data.facility); sessionStorage.setItem('facilityData', JSON.stringify(storedData)); initialiseFacilityData(storedData, true); // Close modal using multiple methods to ensure it closes const modalElement = document.getElementById('createModal'); if (modalElement) { // Method 1: Using Bootstrap's modal instance if (createModal) { createModal.hide(); } // Method 2: Using Bootstrap's static method const modal = bootstrap.Modal.getInstance(modalElement); if (modal) { modal.hide(); } // Method 3: Direct DOM manipulation modalElement.classList.remove('show'); document.body.classList.remove('modal-open'); const modalBackdrop = document.querySelector('.modal-backdrop'); if (modalBackdrop) { modalBackdrop.remove(); } } // Reset the form createForm.reset(); } else { console.error('Create failed:', data.error); alert(data.error || 'Failed to create facility'); } } catch (error) { console.error('Error creating facility:', error); alert('Failed to create facility: ' + error.message); } finally { this.submitting = false; } }); } else { console.log('Create form not found in DOM - this is expected for non-admin users or if the form is not yet loaded'); } } // Update form handler const updateForm = document.getElementById('updateForm'); if (updateForm) { updateForm.addEventListener('submit', async function(e) { e.preventDefault(); // Prevent duplicate submissions if (this.submitting) { return; } this.submitting = true; if (!isAdmin()) { alert('You must be an admin to update facilities'); this.submitting = false; return; } const formData = new FormData(this); // Create a new FormData with the correct field names for the server // This is due to the contributor field being disabled in the form // disallowing it to be included in the form data const serverFormData = new FormData(); serverFormData.append('action', 'update'); // Copy all fields from the form to the server form data for (const [key, value] of formData.entries()) { serverFormData.append(key, value); } // Ensure the contributor field is included (it might be disabled in the form) const contUpdateField = document.getElementById('contUpdate'); if (contUpdateField) { serverFormData.append('contUpdate', contUpdateField.value); } try { // Use authFetch instead of regular fetch const response = await window.authFetch('/facilitycontroller.php', { method: 'POST', body: serverFormData }); if (!response.ok) { const errorText = await response.text(); console.error('Server response:', errorText); throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success) { const storedData = JSON.parse(sessionStorage.getItem('facilityData') || '[]'); const index = storedData.findIndex(f => f.id === parseInt(data.facility.id)); if (index !== -1) { storedData[index] = data.facility; sessionStorage.setItem('facilityData', JSON.stringify(storedData)); initialiseFacilityData(storedData, true); } // Close modal using multiple methods to ensure it closes const modalElement = document.getElementById('updateModal'); if (modalElement) { // Method 1: Using Bootstrap's modal instance if (updateModal) { updateModal.hide(); } // Method 2: Using Bootstrap's static method const modal = bootstrap.Modal.getInstance(modalElement); if (modal) { modal.hide(); } // Method 3: Direct DOM manipulation modalElement.classList.remove('show'); document.body.classList.remove('modal-open'); const modalBackdrop = document.querySelector('.modal-backdrop'); if (modalBackdrop) { modalBackdrop.remove(); } } } else { console.error('Update failed:', data.error); alert(data.error || 'Failed to update facility'); } } catch (error) { console.error('Error updating facility:', error); alert('Failed to update facility: ' + error.message); } finally { this.submitting = false; } }); } else { console.error('Update form not found in DOM'); } // Delete form handler const deleteForm = document.getElementById('deleteForm'); if (deleteForm) { deleteForm.addEventListener('submit', async function(e) { e.preventDefault(); // Prevent default form submission // Prevent duplicate submissions if (this.submitting) { return; } this.submitting = true; if (!isAdmin()) { alert('You must be an admin to delete facilities'); this.submitting = false; return; } const formData = new FormData(this); // Create a new FormData with the correct field names for the server const serverFormData = new FormData(); serverFormData.append('action', 'delete'); serverFormData.append('id', formData.get('idDelete')); // Map idDelete to id for the server console.log('Deleting facility with ID:', formData.get('idDelete')); try { // Check if token is valid if (!window.auth) { throw new Error('Auth service not available'); } // Validate token with server before proceeding console.log('Validating token with server...'); const isValid = await window.auth.validateToken(); if (!isValid) { throw new Error('Authentication token is invalid or expired'); } // Get token after validation to ensure it's fresh const token = window.auth.getToken(); console.log('Using token for delete request:', token); if (!token) { throw new Error('No authentication token available'); } // Decode token to check payload if (window.auth.parseJwt) { const payload = window.auth.parseJwt(token); console.log('Token payload:', payload); console.log('Access level:', payload.accessLevel); console.log('Is admin check:', payload.accessLevel === 0 || payload.accessLevel === 1); } // Use direct fetch with manual token inclusion console.log('Sending delete request to server...'); const response = await fetch('/facilitycontroller.php', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'X-Requested-With': 'XMLHttpRequest' }, body: serverFormData }); console.log('Delete response status:', response.status); if (!response.ok) { const errorText = await response.text(); console.error('Server response:', errorText); throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('Delete response data:', data); if (data.success) { console.log('Delete successful, updating UI...'); const storedData = JSON.parse(sessionStorage.getItem('facilityData') || '[]'); const filteredData = storedData.filter(f => f.id !== parseInt(data.facilityId)); sessionStorage.setItem('facilityData', JSON.stringify(filteredData)); initialiseFacilityData(filteredData, true); // Close modal using multiple methods to ensure it closes const modalElement = document.getElementById('deleteModal'); if (modalElement) { // Method 1: Using Bootstrap's modal instance if (deleteModal) { deleteModal.hide(); } // Method 2: Using Bootstrap's static method const modal = bootstrap.Modal.getInstance(modalElement); if (modal) { modal.hide(); } // Method 3: Direct DOM manipulation modalElement.classList.remove('show'); document.body.classList.remove('modal-open'); const modalBackdrop = document.querySelector('.modal-backdrop'); if (modalBackdrop) { modalBackdrop.remove(); } } // Reset the form deleteForm.reset(); } else { console.error('Delete failed:', data.error); alert(data.error || 'Failed to delete facility'); } } catch (error) { console.error('Error deleting facility:', error); console.error('Error stack:', error.stack); alert('Failed to delete facility: ' + error.message); } finally { this.submitting = false; } }); } else { console.error('Delete form not found in DOM'); } // Note: Status/comment form handlers are now in comments.js } /** * Updates the pagination controls based on current state */ function updatePaginationControls() { const paginationList = document.getElementById('paginationControls'); const firstButton = document.getElementById('firstPage'); const prevButton = document.getElementById('prevPage'); const nextButton = document.getElementById('nextPage'); const lastButton = document.getElementById('lastPage'); // Update button states firstButton.parentElement.classList.toggle('disabled', currentPage === 1); prevButton.parentElement.classList.toggle('disabled', currentPage === 1); nextButton.parentElement.classList.toggle('disabled', currentPage === totalPages); lastButton.parentElement.classList.toggle('disabled', currentPage === totalPages); // Remove existing page numbers Array.from(paginationList.children).forEach(child => { if (!child.contains(firstButton) && !child.contains(prevButton) && !child.contains(nextButton) && !child.contains(lastButton)) { child.remove(); } }); // Calculate page range const maxVisiblePages = 5; let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); // Adjust start page if we're near the end if (endPage - startPage + 1 < maxVisiblePages) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } // Insert new page numbers const insertBeforeElement = nextButton.parentElement; // First page and ellipsis if (startPage > 1) { const firstPageItem = createPageNumberElement(1, false); paginationList.insertBefore(firstPageItem, insertBeforeElement); if (startPage > 2) { const ellipsisItem = createEllipsisElement(); paginationList.insertBefore(ellipsisItem, insertBeforeElement); } } // Page numbers for (let i = startPage; i <= endPage; i++) { const pageItem = createPageNumberElement(i, i === currentPage); paginationList.insertBefore(pageItem, insertBeforeElement); } // Last page and ellipsis if (endPage < totalPages) { if (endPage < totalPages - 1) { const ellipsisItem = createEllipsisElement(); paginationList.insertBefore(ellipsisItem, insertBeforeElement); } const lastPageItem = createPageNumberElement(totalPages, false); paginationList.insertBefore(lastPageItem, insertBeforeElement); } // Update pagination info text const paginationInfo = document.getElementById('paginationInfo'); if (paginationInfo) { const startItem = (currentPage - 1) * itemsPerPage + 1; const endItem = Math.min(startItem + itemsPerPage - 1, filteredData.length); if (filteredData.length === 0) { paginationInfo.querySelector('span').textContent = 'No facilities found'; } else { paginationInfo.querySelector('span').textContent = `Showing ${startItem}-${endItem} of ${filteredData.length} facilities`; } } // Update facility count badge const facilityCount = document.getElementById('facilityCount'); if (facilityCount) { facilityCount.textContent = `${filteredData.length} facilities`; } } function createPageNumberElement(pageNum, isActive) { const li = document.createElement('li'); li.className = `page-item${isActive ? ' active' : ''}`; const a = document.createElement('a'); a.className = 'page-link border-0'; a.href = '#'; a.dataset.page = pageNum; a.textContent = pageNum; // Add aria attributes for accessibility if (isActive) { a.setAttribute('aria-current', 'page'); a.classList.add('bg-success', 'text-white'); } else { a.classList.add('text-success'); } li.appendChild(a); return li; } function createEllipsisElement() { const li = document.createElement('li'); li.className = 'page-item disabled'; const span = document.createElement('span'); span.className = 'page-link border-0'; span.innerHTML = ''; span.setAttribute('aria-hidden', 'true'); li.appendChild(span); return li; } /** * Gets the current page of data * @returns {Array} Array of items for the current page */ function getCurrentPageData() { const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = Math.min(startIndex + itemsPerPage, filteredData.length); const pageData = filteredData.slice(startIndex, endIndex); return pageData; } /** * Updates the table with current page data */ function updateTableWithPagination() { const pageData = getCurrentPageData(); renderFacilityTable(pageData); updatePaginationControls(); } /** * Sets up pagination event listeners */ function setupPaginationControls() { const paginationList = document.getElementById('paginationControls'); if (!paginationList) { console.error('Pagination controls not found'); return; } // Remove existing handler if it exists if (paginationHandler) { paginationList.removeEventListener('click', paginationHandler); } // Create new handler paginationHandler = (e) => { e.preventDefault(); const target = e.target.closest('a'); if (!target) return; let newPage = currentPage; switch(target.id) { case 'firstPage': newPage = 1; break; case 'prevPage': newPage = Math.max(1, currentPage - 1); break; case 'nextPage': newPage = Math.min(totalPages, currentPage + 1); break; case 'lastPage': newPage = totalPages; break; default: if (target.dataset.page) { newPage = parseInt(target.dataset.page); } } // Only update if the page actually changed and is valid if (newPage !== currentPage && newPage >= 1 && newPage <= totalPages) { currentPage = newPage; updateTableWithPagination(); } }; // Add the new handler paginationList.addEventListener('click', paginationHandler); } /** * Updates the table based on current filter values */ function updateTable() { try { const data = JSON.parse(sessionStorage.getItem('facilityData') || '[]'); if (!data.length) { error_log('No facility data found'); return; } // Get current filter values const sortBy = document.getElementById('sort').value; const sortDir = document.getElementById('dir').value; const filterCategory = document.getElementById('filterCat').value; const searchTerm = document.getElementById('searchInput').value.toLowerCase(); // Apply filters and sorting filteredData = filterData(data, filterCategory, searchTerm); filteredData = sortData(filteredData, sortBy, sortDir); // Update pagination totalPages = Math.ceil(filteredData.length / itemsPerPage); currentPage = 1; // Reset to first page when filters change // Update table with current page updateTableWithPagination(); } catch (error) { error_log('Error updating table:', error); } } /** * Sets up table controls for filtering and sorting */ function setupTableControls() { // Get form elements const filterForm = document.querySelector('form[role="search"]'); if (!filterForm) { error_log('Filter form not found'); return; } // Get control elements const filterControls = document.querySelectorAll('.filter-control'); const sortControls = document.querySelectorAll('.sort-control'); const searchInput = document.getElementById('searchInput'); if (!filterControls.length || !sortControls.length || !searchInput) { error_log('Missing filter or sort controls'); return; } // Prevent form submission and handle it properly filterForm.addEventListener('submit', function(e) { e.preventDefault(); e.stopPropagation(); updateTable(); }); // Add event listeners for immediate updates searchInput.addEventListener('input', updateTable); // Add change event listeners for select elements filterControls.forEach(control => { control.addEventListener('change', updateTable); }); sortControls.forEach(control => { control.addEventListener('change', updateTable); }); // Set up pagination controls setupPaginationControls(); } /** * Filters the facility data based on current filter values * @param {Array} data - Array of facility objects * @param {string} category - Filter category * @param {string} searchTerm - Search term * @returns {Array} Filtered array of facility objects */ function filterData(data, category, searchTerm) { const filtered = data.filter(facility => { if (!facility) return false; // If no category selected or no search term, show all results if (!category || !searchTerm) return true; // Get the value to search in based on the selected category let searchValue = ''; switch(category) { case 'title': searchValue = (facility.title || '').toLowerCase(); break; case 'category': searchValue = (facility.category || '').toLowerCase(); break; case 'description': searchValue = (facility.description || '').toLowerCase(); break; case 'streetName': searchValue = (facility.streetName || '').toLowerCase(); break; case 'county': searchValue = (facility.county || '').toLowerCase(); break; case 'town': searchValue = (facility.town || '').toLowerCase(); break; case 'postcode': searchValue = (facility.postcode || '').toLowerCase(); break; case 'contributor': searchValue = (facility.contributor || '').toLowerCase(); break; } return searchValue.includes(searchTerm.toLowerCase()); }); return filtered; } /** * Sorts the facility data based on current sort order * @param {Array} data - Array of facility objects * @param {string} sortBy - Sort by field name * @param {string} sortDir - Sort direction * @returns {Array} Sorted array of facility objects */ function sortData(data, sortBy, sortDir) { if (!sortBy) return data; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax return [...data].sort((a, b) => { if (!a || !b) return 0; let valueA = ''; let valueB = ''; // Get the values to compare based on the selected field switch (sortBy) { case 'title': valueA = (a.title || '').toLowerCase(); valueB = (b.title || '').toLowerCase(); break; case 'category': valueA = (a.category || '').toLowerCase(); valueB = (b.category || '').toLowerCase(); break; case 'description': valueA = (a.description || '').toLowerCase(); valueB = (b.description || '').toLowerCase(); break; case 'streetName': valueA = (a.streetName || '').toLowerCase(); valueB = (b.streetName || '').toLowerCase(); break; case 'county': valueA = (a.county || '').toLowerCase(); valueB = (b.county || '').toLowerCase(); break; case 'town': valueA = (a.town || '').toLowerCase(); valueB = (b.town || '').toLowerCase(); break; case 'postcode': valueA = (a.postcode || '').toLowerCase(); valueB = (b.postcode || '').toLowerCase(); break; case 'contributor': valueA = (a.contributor || '').toLowerCase(); valueB = (b.contributor || '').toLowerCase(); break; } const comparison = valueA.localeCompare(valueB); return sortDir === 'asc' ? comparison : -comparison; }); } /** * Logs errors to the console * @param {string} message - Error message * @param {Error} error - Error object */ function error_log(message, error) { console.error(message, error); // Add more detailed error logging if (error instanceof Error) { console.error('Error name:', error.name); console.error('Error message:', error.message); console.error('Error stack:', error.stack); } } /** * Gets an appropriate icon for a facility category * @param {string} category - The facility category * @returns {string} The Bootstrap icon class */ function getFacilityIcon(category) { const categoryLower = (category || '').toLowerCase(); if (categoryLower.includes('recycling')) return 'bi bi-recycle'; if (categoryLower.includes('green') || categoryLower.includes('roof')) return 'bi bi-tree-fill'; if (categoryLower.includes('solar') || categoryLower.includes('power')) return 'bi bi-sun-fill'; if (categoryLower.includes('water') || categoryLower.includes('rain')) return 'bi bi-droplet-fill'; if (categoryLower.includes('battery')) return 'bi bi-battery-charging'; if (categoryLower.includes('bench')) return 'bi bi-bench'; // Default icon return 'bi bi-geo-alt-fill'; } /** * Gets an appropriate color class for a facility category * @param {string} category - The facility category * @returns {string} The Bootstrap color class */ function getCategoryColorClass(category) { const categoryLower = (category || '').toLowerCase(); if (categoryLower.includes('recycling')) return 'success'; if (categoryLower.includes('green') || categoryLower.includes('roof')) return 'success'; if (categoryLower.includes('solar') || categoryLower.includes('power')) return 'warning'; if (categoryLower.includes('water') || categoryLower.includes('rain')) return 'info'; if (categoryLower.includes('battery')) return 'danger'; if (categoryLower.includes('bench')) return 'primary'; // Default color return 'secondary'; } /** * Sets up expandable content for a table row * @param {HTMLElement} row - The table row element */ function setupExpandableContent(row) { // Setup description expansion const descriptionContent = row.querySelector('.cell-content'); const toggleBtn = row.querySelector('.toggle-content-btn'); if (descriptionContent) { // Make description expandable on click descriptionContent.addEventListener('click', function() { this.classList.toggle('expanded'); // Update button text if it exists if (toggleBtn) { toggleBtn.querySelector('small').textContent = this.classList.contains('expanded') ? 'Show less' : 'Show more'; } // Ensure proper alignment when expanded const container = this.closest('.description-container'); if (container) { if (this.classList.contains('expanded')) { container.classList.remove('justify-content-center'); container.classList.add('justify-content-start'); } else { container.classList.remove('justify-content-start'); container.classList.add('justify-content-center'); } } }); // Setup toggle button if it exists if (toggleBtn) { toggleBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); const content = this.closest('.description-container').querySelector('.cell-content'); content.classList.toggle('expanded'); this.querySelector('small').textContent = content.classList.contains('expanded') ? 'Show less' : 'Show more'; // Ensure proper alignment when expanded const container = content.closest('.description-container'); if (container) { if (content.classList.contains('expanded')) { container.classList.remove('justify-content-center'); container.classList.add('justify-content-start'); } else { container.classList.remove('justify-content-start'); container.classList.add('justify-content-center'); } } }); } } // Setup address expansion const addressContent = row.querySelector('.address-content'); if (addressContent) { addressContent.addEventListener('click', function() { this.classList.toggle('expanded'); // Ensure proper alignment when expanded const container = this.closest('.d-flex'); if (container) { if (this.classList.contains('expanded')) { container.classList.remove('align-items-center'); container.classList.add('align-items-start'); } else { container.classList.remove('align-items-start'); container.classList.add('align-items-center'); } } }); } } // Export the initialization function window.initializeFacilityData = initialiseFacilityData;