pre-clean

Signed-off-by: boris <boris@borishub.co.uk>
This commit is contained in:
boris
2025-04-21 21:24:46 +01:00
parent 78508a7cbd
commit 8877faa631
15 changed files with 1302 additions and 407 deletions

View File

@@ -5,8 +5,7 @@
*/
function initialiseFacilityData(data, force = false) {
// Only prevent multiple initializations if not forcing
if (!force && isInitialized) {
console.debug('Facility data already initialized, skipping...');
if (!force && isinitialised) {
return;
}
@@ -21,11 +20,9 @@ function initialiseFacilityData(data, force = false) {
// Check if we're on the map page
const isMapPage = window.location.pathname.includes('map.php');
if (!isMapPage) {
// Only try to initialize table if we're not on the map page
// Only try to initialise table if we're not on the map page
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
@@ -33,27 +30,30 @@ function initialiseFacilityData(data, force = false) {
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
// initialise 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
updateTable();
// Reset sorting state
currentSortField = null;
currentSortOrder = null;
// Set up table controls (sorting and filtering)
setupTableControls();
// Update table with paginated data
updateTableWithPagination();
}
// Mark as initialized
isInitialized = true;
// Mark as initialised
isinitialised = true;
} catch (error) {
console.error('Error initialising facility data:', error);
// Don't throw error if we're on map page, as table errors are expected
@@ -79,7 +79,52 @@ function renderFacilityTable(data) {
// Check if user is admin
const userIsAdmin = isAdmin();
console.log('renderFacilityTable - userIsAdmin:', userIsAdmin);
// Set up table headers first
const tableHeaderRow = document.getElementById('tableHeaderRow');
if (tableHeaderRow) {
// Define header configuration
const headers = [
{ field: 'title', label: 'Title', width: '17%' },
{ field: 'category', label: 'Category', width: '11%', center: true },
{ field: 'description', label: 'Description', width: '27%' },
{ field: 'address', label: 'Address', width: '20%' },
{ field: 'coordinates', label: 'Coordinates', width: '10%', center: true },
{ field: 'contributor', label: 'Contributor', width: '7%', center: true },
{ field: 'actions', label: 'Actions', width: '8%', center: true, sortable: false }
];
// Clear existing headers
tableHeaderRow.innerHTML = '';
// Create header cells
headers.forEach(header => {
const th = document.createElement('th');
th.className = 'fw-semibold' + (header.center ? ' text-center' : '');
th.style.width = header.width;
if (header.sortable !== false) {
th.classList.add('sortable');
th.style.cursor = 'pointer';
th.dataset.field = header.field;
// Create header content with sort indicator
th.innerHTML = `
<div class="d-flex align-items-center gap-1 ${header.center ? 'justify-content-center' : ''}">
<span>${header.label}</span>
<i class="bi bi-arrow-down-up sort-icon"></i>
</div>
`;
// Add click handler
th.addEventListener('click', () => handleHeaderClick(header.field));
} else {
th.textContent = header.label;
}
tableHeaderRow.appendChild(th);
});
}
// Render each row
data.forEach((facility, index) => {
@@ -98,19 +143,9 @@ function renderFacilityTable(data) {
// Start building the row HTML
let rowHtml = '';
// Only show ID column for admins
if (userIsAdmin) {
console.log('Adding ID column for facility:', facility.id);
rowHtml += `
<td class="fw-medium align-middle text-center" style="width: 40px;">
${escapeHtml(facility.id)}
</td>
`;
}
// Add the rest of the columns
rowHtml += `
<td class="fw-medium align-middle" style="${userIsAdmin ? 'width: 15%;' : 'width: 17%;'}">
<td class="fw-medium align-middle" style="width: 17%;">
<div class="d-flex align-items-center h-100">
<div class="facility-icon me-2 rounded-circle bg-light d-flex align-items-center justify-content-center" style="width: 28px; height: 28px; min-width: 28px;">
<i class="${getFacilityIcon(facility.category)} text-${categoryClass}"></i>
@@ -118,14 +153,14 @@ function renderFacilityTable(data) {
<span class="text-truncate" style="max-width: calc(100% - 35px);">${escapeHtml(facility.title)}</span>
</div>
</td>
<td class="text-center align-middle" style="${userIsAdmin ? 'width: 10%;' : 'width: 11%;'}">
<td class="text-center align-middle" style="width: 11%;">
<div class="d-flex align-items-center justify-content-center h-100">
<span class="badge bg-${categoryClass} bg-opacity-10 text-${categoryClass} px-2 py-1 rounded-pill">
${escapeHtml(facility.category)}
</span>
</div>
</td>
<td class="align-middle" style="${userIsAdmin ? 'width: 25%;' : 'width: 27%;'}">
<td class="align-middle" style="width: 27%;">
<div class="description-container d-flex flex-column justify-content-center">
<div class="cell-content" data-full-text="${escapeHtml(facility.description)}">
${escapeHtml(facility.description)}
@@ -144,13 +179,13 @@ function renderFacilityTable(data) {
</div>
</div>
</td>
<td class="small text-nowrap text-center align-middle" style="${userIsAdmin ? 'width: 12%;' : 'width: 12%;'}">
<td class="small text-nowrap text-center align-middle" style="width: 10%;">
<span class="badge bg-light text-dark border">
${escapeHtml(coordinates)}
</span>
</td>
<td class="small text-center align-middle" style="width: 8%;">${escapeHtml(facility.contributor)}</td>
<td class="text-center align-middle" style="${userIsAdmin ? 'width: 10%;' : 'width: 5%;'}">
<td class="small text-center align-middle" style="width: 5%;">${escapeHtml(facility.contributor)}</td>
<td class="text-center align-middle" style="width: 8%;">
<div class="d-flex justify-content-center gap-1">
${userIsAdmin ? `
<button type="button" class="btn btn-sm btn-outline-primary update-btn rounded-circle d-flex align-items-center justify-content-center" style="width: 30px; height: 30px;" data-bs-toggle="modal" data-bs-target="#updateModal" data-facility-id="${facility.id}" title="Edit">
@@ -185,6 +220,9 @@ function renderFacilityTable(data) {
`;
tbody.appendChild(emptyRow);
}
// Update sort indicators
updateSortIndicators();
} catch (error) {
error_log('Error in renderFacilityTable:', error);
}
@@ -283,15 +321,19 @@ let filteredData = [];
let paginationHandler = null;
// Add initialization state tracking
let isInitialized = false;
let isinitialised = false;
// Initialize modals once
// initialise modals once
let updateModal, deleteModal, createModal;
let formHandlersInitialized = false;
let formHandlersinitialised = false;
// Add sorting state variables
let currentSortField = null;
let currentSortOrder = null; // null = unsorted, 'asc' = ascending, 'desc' = descending
document.addEventListener('DOMContentLoaded', function() {
// Initialize modals once
// initialise modals once
const modals = document.querySelectorAll('.modal');
modals.forEach((modal, index) => {
if (modal.id === 'updateModal') {
@@ -388,15 +430,15 @@ document.addEventListener('DOMContentLoaded', function() {
// Set up form handlers with a small delay to ensure DOM is fully loaded
setTimeout(() => {
if (!formHandlersInitialized) {
if (!formHandlersinitialised) {
console.log('Setting up form handlers...');
setupFormHandlers();
formHandlersInitialized = true;
formHandlersinitialised = true;
}
}, 100);
// Initialize facility data if not already initialized
if (!isInitialized) {
// initialise facility data if not already initialised
if (!isinitialised) {
const storedData = sessionStorage.getItem('facilityData');
if (storedData) {
try {
@@ -407,6 +449,23 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
}
// Add CSS styles for sort indicators
const style = document.createElement('style');
style.textContent = `
.sortable:hover {
background-color: rgba(25, 135, 84, 0.1);
}
.sort-icon {
opacity: 0.5;
font-size: 0.8em;
}
.sortable:hover .sort-icon,
.text-success .sort-icon {
opacity: 1;
}
`;
document.head.appendChild(style);
});
// Handle create form submission
@@ -435,6 +494,8 @@ function setupFormHandlers() {
const formData = new FormData(this);
// Set the contributor to the current user's username
formData.set('contCreate', JSON.parse(localStorage.getItem('user'))?.username);
// Set the action to 'create'
formData.set('action', 'create');
try {
// Use simpleAuth.fetchAuth for authenticated requests
const response = await simpleAuth.fetchAuth('/facilitycontroller.php', {
@@ -517,20 +578,30 @@ function setupFormHandlers() {
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);
}
// Map form fields to server field names
const fieldMappings = {
'idUpdate': 'id',
'titlUpdate': 'title',
'cateUpdate': 'category',
'descUpdate': 'description',
'hnumUpdate': 'houseNumber',
'strtUpdate': 'streetName',
'cntyUpdate': 'county',
'townUpdate': 'town',
'postUpdate': 'postcode',
'lngUpdate': 'lng',
'latUpdate': 'lat',
'contUpdate': 'contributor'
};
// 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);
// Copy and transform fields from the form to the server form data
for (const [key, value] of formData.entries()) {
if (fieldMappings[key]) {
serverFormData.append(fieldMappings[key], value);
}
}
try {
@@ -801,12 +872,6 @@ function updatePaginationControls() {
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) {
@@ -862,6 +927,8 @@ function updateTableWithPagination() {
const pageData = getCurrentPageData();
renderFacilityTable(pageData);
updatePaginationControls();
// Update sort indicators after rendering table
updateSortIndicators();
}
/**
@@ -930,14 +997,13 @@ function updateTable() {
}
// 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();
const searchTerm = document.getElementById('searchInput').value;
// Apply filters and sorting
filteredData = filterData(data, filterCategory, searchTerm);
filteredData = sortData(filteredData, sortBy, sortDir);
filteredData = filterData(data, searchTerm);
if (currentSortField && currentSortOrder) {
filteredData = sortData(filteredData, currentSortField, currentSortOrder);
}
// Update pagination
totalPages = Math.ceil(filteredData.length / itemsPerPage);
@@ -962,12 +1028,9 @@ function setupTableControls() {
}
// 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');
if (!searchInput) {
error_log('Missing search input');
return;
}
@@ -978,66 +1041,142 @@ function setupTableControls() {
updateTable();
});
// Add event listeners for immediate updates
// Add event listener for search input
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 table headers for sorting
setupSortableHeaders();
// Set up pagination controls
setupPaginationControls();
}
/**
* Filters the facility data based on current filter values
* Sets up sortable table headers
*/
function setupSortableHeaders() {
const tableHeaderRow = document.getElementById('tableHeaderRow');
if (!tableHeaderRow) return;
// Define header configuration
const headers = [
{ field: 'title', label: 'Title', width: '17%' },
{ field: 'category', label: 'Category', width: '11%', center: true },
{ field: 'description', label: 'Description', width: '27%' },
{ field: 'address', label: 'Address', width: '20%' },
{ field: 'coordinates', label: 'Coordinates', width: '12%', center: true },
{ field: 'contributor', label: 'Contributor', width: '8%', center: true },
{ field: 'actions', label: 'Actions', width: '5%', center: true, sortable: false }
];
// Clear existing headers
tableHeaderRow.innerHTML = '';
// Create header cells
headers.forEach(header => {
const th = document.createElement('th');
th.className = 'fw-semibold' + (header.center ? ' text-center' : '');
th.style.width = header.width;
if (header.sortable !== false) {
th.classList.add('sortable');
th.style.cursor = 'pointer';
th.dataset.field = header.field;
// Create header content with sort indicator
th.innerHTML = `
<div class="d-flex align-items-center gap-1 ${header.center ? 'justify-content-center' : ''}">
<span>${header.label}</span>
<i class="bi bi-arrow-down-up sort-icon"></i>
</div>
`;
// Add click handler
th.addEventListener('click', () => handleHeaderClick(header.field));
} else {
th.textContent = header.label;
}
tableHeaderRow.appendChild(th);
});
// initialise sort indicators
updateSortIndicators();
}
/**
* Handles click on sortable header
* @param {string} field - The field to sort by
*/
function handleHeaderClick(field) {
console.log('Header clicked:', field); // Debug log
// Rotate through sort orders: none -> asc -> desc -> none
if (currentSortField === field) {
if (currentSortOrder === 'asc') {
currentSortOrder = 'desc';
} else if (currentSortOrder === 'desc') {
currentSortField = null;
currentSortOrder = null;
}
} else {
currentSortField = field;
currentSortOrder = 'asc';
}
console.log('New sort state:', { field: currentSortField, order: currentSortOrder }); // Debug log
// Update table
updateTable();
}
/**
* Updates sort indicators in table headers
*/
function updateSortIndicators() {
const headers = document.querySelectorAll('#tableHeaderRow th.sortable');
headers.forEach(header => {
const icon = header.querySelector('.sort-icon');
if (header.dataset.field === currentSortField) {
icon.classList.remove('bi-arrow-down-up');
icon.classList.add(currentSortOrder === 'asc' ? 'bi-arrow-up' : 'bi-arrow-down');
header.classList.add('text-success');
} else {
icon.classList.remove('bi-arrow-up', 'bi-arrow-down');
icon.classList.add('bi-arrow-down-up');
header.classList.remove('text-success');
}
});
}
/**
* Filters the facility data based on search term
* @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) {
function filterData(data, 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;
// If no search term, show all results
if (!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;
}
// Convert search term to lowercase for case-insensitive search
searchTerm = searchTerm.toLowerCase();
return searchValue.includes(searchTerm.toLowerCase());
// Search across all relevant fields
return (
(facility.title || '').toLowerCase().includes(searchTerm) ||
(facility.category || '').toLowerCase().includes(searchTerm) ||
(facility.description || '').toLowerCase().includes(searchTerm) ||
(facility.streetName || '').toLowerCase().includes(searchTerm) ||
(facility.county || '').toLowerCase().includes(searchTerm) ||
(facility.town || '').toLowerCase().includes(searchTerm) ||
(facility.postcode || '').toLowerCase().includes(searchTerm) ||
(facility.contributor || '').toLowerCase().includes(searchTerm) ||
(facility.houseNumber || '').toLowerCase().includes(searchTerm)
);
});
return filtered;
}
@@ -1051,51 +1190,50 @@ function filterData(data, category, searchTerm) {
*/
function sortData(data, sortBy, sortDir) {
if (!sortBy) return data;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
console.log('Sorting by:', sortBy, 'Direction:', sortDir); // Debug log
return [...data].sort((a, b) => {
if (!a || !b) return 0;
let valueA = '';
let valueB = '';
let valueA, 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;
// Special handling for address field which is composed of multiple fields
if (sortBy === 'address') {
valueA = [
a.houseNumber || '',
a.streetName || '',
a.town || '',
a.county || '',
a.postcode || ''
].filter(Boolean).join(', ').toLowerCase();
valueB = [
b.houseNumber || '',
b.streetName || '',
b.town || '',
b.county || '',
b.postcode || ''
].filter(Boolean).join(', ').toLowerCase();
}
// Special handling for coordinates field
else if (sortBy === 'coordinates') {
// Sort by latitude first, then longitude
valueA = `${parseFloat(a.lat || 0)},${parseFloat(a.lng || 0)}`;
valueB = `${parseFloat(b.lat || 0)},${parseFloat(b.lng || 0)}`;
}
// Default handling for other fields
else {
valueA = (a[sortBy] || '').toString().toLowerCase();
valueB = (b[sortBy] || '').toString().toLowerCase();
}
const comparison = valueA.localeCompare(valueB);
return sortDir === 'asc' ? comparison : -comparison;
console.log('Comparing:', valueA, valueB); // Debug log
// Compare the values
if (valueA < valueB) return sortDir === 'asc' ? -1 : 1;
if (valueA > valueB) return sortDir === 'asc' ? 1 : -1;
return 0;
});
}
@@ -1154,4 +1292,4 @@ function getCategoryColorClass(category) {
}
// Export the initialization function
window.initializeFacilityData = initialiseFacilityData;
window.initialiseFacilityData = initialiseFacilityData;