final round of updates. Still need to stabalize first load for the map, having issues for sure; longer load time
This commit is contained in:
@@ -66,7 +66,8 @@
|
||||
<button class="btn btn-secondary btn-sm" id="close-edit-footer-btn">✕ Close</button>
|
||||
</div>
|
||||
<form id="edit-location-form">
|
||||
<input type="hidden" id="edit-location-id" name="id">
|
||||
<!-- Hidden ID field - don't include in form data -->
|
||||
<input type="hidden" id="edit-location-id" data-exclude="true">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="edit-first-name">First Name</label>
|
||||
@@ -110,7 +111,7 @@
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<input type="text" id="edit-location-address" name="Address" style="flex: 1;">
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="lookup-address-edit-btn">
|
||||
🔍 Lookup
|
||||
📍 Lookup Address
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,7 +222,7 @@
|
||||
<input type="text" id="location-address" name="Address"
|
||||
placeholder="Enter address" style="flex: 1;">
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="lookup-address-add-btn">
|
||||
🔍 Lookup
|
||||
📍 Lookup Address
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -228,9 +228,13 @@ function createLocationMarker(location) {
|
||||
const popupContent = createPopupContent(location);
|
||||
marker.bindPopup(popupContent);
|
||||
|
||||
// Add click handler for editing
|
||||
// Add click handler for editing - Store location data on marker
|
||||
marker._locationData = location;
|
||||
marker.on('click', () => {
|
||||
if (currentUser) {
|
||||
// Debug: Log the location object to see its structure
|
||||
console.log('Location clicked:', location);
|
||||
console.log('Available fields:', Object.keys(location));
|
||||
setTimeout(() => openEditForm(location), 100);
|
||||
}
|
||||
});
|
||||
@@ -240,6 +244,9 @@ function createLocationMarker(location) {
|
||||
|
||||
// Create popup content
|
||||
function createPopupContent(location) {
|
||||
// Try to find the ID field
|
||||
const locationId = location.Id || location.id || location.ID || location._id;
|
||||
|
||||
const name = [location['First Name'], location['Last Name']]
|
||||
.filter(Boolean).join(' ') || 'Unknown';
|
||||
const address = location.Address || 'No address';
|
||||
@@ -254,7 +261,7 @@ function createPopupContent(location) {
|
||||
${location.Sign ? '<p>🏁 Has campaign sign</p>' : ''}
|
||||
${location.Notes ? `<p><strong>Notes:</strong> ${escapeHtml(location.Notes)}</p>` : ''}
|
||||
<div class="popup-meta">
|
||||
<p>ID: ${location.Id}</p>
|
||||
<p>ID: ${locationId || 'Unknown'}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -508,7 +515,11 @@ async function handleAddLocation(e) {
|
||||
|
||||
// Convert form data to object
|
||||
for (let [key, value] of formData.entries()) {
|
||||
if (value.trim() !== '') {
|
||||
// Map form field names to NocoDB column names
|
||||
if (key === 'latitude') data.latitude = value.trim();
|
||||
else if (key === 'longitude') data.longitude = value.trim();
|
||||
else if (key === 'Geo-Location') data['Geo-Location'] = value.trim();
|
||||
else if (value.trim() !== '') {
|
||||
data[key] = value.trim();
|
||||
}
|
||||
}
|
||||
@@ -549,8 +560,29 @@ async function handleAddLocation(e) {
|
||||
function openEditForm(location) {
|
||||
currentEditingLocation = location;
|
||||
|
||||
// Debug: Log all possible ID fields
|
||||
console.log('Opening edit form for location:', {
|
||||
'Id': location.Id,
|
||||
'id': location.id,
|
||||
'ID': location.ID,
|
||||
'_id': location._id,
|
||||
'all_keys': Object.keys(location)
|
||||
});
|
||||
|
||||
// Extract ID - check multiple possible field names
|
||||
const locationId = location.Id || location.id || location.ID || location._id;
|
||||
|
||||
if (!locationId) {
|
||||
console.error('No ID found in location object. Available fields:', Object.keys(location));
|
||||
showStatus('Error: Location ID not found. Check console for details.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the ID in a data attribute for later use
|
||||
document.getElementById('edit-location-id').value = locationId;
|
||||
document.getElementById('edit-location-id').setAttribute('data-location-id', locationId);
|
||||
|
||||
// Populate form fields
|
||||
document.getElementById('edit-location-id').value = location.Id || '';
|
||||
document.getElementById('edit-first-name').value = location['First Name'] || '';
|
||||
document.getElementById('edit-last-name').value = location['Last Name'] || '';
|
||||
document.getElementById('edit-location-email').value = location.Email || '';
|
||||
@@ -558,7 +590,7 @@ function openEditForm(location) {
|
||||
document.getElementById('edit-location-unit').value = location['Unit Number'] || '';
|
||||
document.getElementById('edit-support-level').value = location['Support Level'] || '';
|
||||
document.getElementById('edit-location-address').value = location.Address || '';
|
||||
document.getElementById('edit-sign').checked = location.Sign === true || location.Sign === 'true';
|
||||
document.getElementById('edit-sign').checked = location.Sign === true || location.Sign === 'true' || location.Sign === 1;
|
||||
document.getElementById('edit-sign-size').value = location['Sign Size'] || '';
|
||||
document.getElementById('edit-location-notes').value = location.Notes || '';
|
||||
document.getElementById('edit-location-lat').value = location.latitude || '';
|
||||
@@ -581,12 +613,25 @@ async function handleEditLocation(e) {
|
||||
|
||||
if (!currentEditingLocation) return;
|
||||
|
||||
// Get the stored location ID
|
||||
const locationIdElement = document.getElementById('edit-location-id');
|
||||
const locationId = locationIdElement.getAttribute('data-location-id') || locationIdElement.value;
|
||||
|
||||
if (!locationId || locationId === 'undefined') {
|
||||
showStatus('Error: Location ID not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const data = { Id: currentEditingLocation.Id };
|
||||
const data = {};
|
||||
|
||||
// Convert form data to object
|
||||
for (let [key, value] of formData.entries()) {
|
||||
if (value.trim() !== '') {
|
||||
// Skip the ID field
|
||||
if (key === 'id' || key === 'Id' || key === 'ID') continue;
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
// Don't skip empty strings - they may be intentional field clearing
|
||||
data[key] = value.trim();
|
||||
}
|
||||
}
|
||||
@@ -599,8 +644,12 @@ async function handleEditLocation(e) {
|
||||
// Handle checkbox
|
||||
data.Sign = document.getElementById('edit-sign').checked;
|
||||
|
||||
// Add debugging
|
||||
console.log('Sending update data for ID:', locationId);
|
||||
console.log('Update data:', data);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/locations/${currentEditingLocation.Id}`, {
|
||||
const response = await fetch(`/api/locations/${locationId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -608,7 +657,15 @@ async function handleEditLocation(e) {
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
const responseText = await response.text();
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse response:', responseText);
|
||||
throw new Error(`Server response error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
showStatus('Location updated successfully!', 'success');
|
||||
@@ -619,7 +676,7 @@ async function handleEditLocation(e) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating location:', error);
|
||||
showStatus(error.message || 'Failed to update location', 'error');
|
||||
showStatus(`Update failed: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,12 +684,21 @@ async function handleEditLocation(e) {
|
||||
async function handleDeleteLocation() {
|
||||
if (!currentEditingLocation) return;
|
||||
|
||||
// Get the stored location ID
|
||||
const locationIdElement = document.getElementById('edit-location-id');
|
||||
const locationId = locationIdElement.getAttribute('data-location-id') || locationIdElement.value;
|
||||
|
||||
if (!locationId || locationId === 'undefined') {
|
||||
showStatus('Error: Location ID not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to delete this location?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/locations/${currentEditingLocation.Id}`, {
|
||||
const response = await fetch(`/api/locations/${locationId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
@@ -651,51 +717,80 @@ async function handleDeleteLocation() {
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup address
|
||||
// Lookup address based on current coordinates
|
||||
async function lookupAddress(mode) {
|
||||
const addressInput = mode === 'add' ?
|
||||
document.getElementById('location-address') :
|
||||
document.getElementById('edit-location-address');
|
||||
let latInput, lngInput, addressInput;
|
||||
|
||||
const address = addressInput.value.trim();
|
||||
|
||||
if (!address) {
|
||||
showStatus('Please enter an address to lookup', 'warning');
|
||||
if (mode === 'add') {
|
||||
latInput = document.getElementById('location-lat');
|
||||
lngInput = document.getElementById('location-lng');
|
||||
addressInput = document.getElementById('location-address');
|
||||
} else if (mode === 'edit') {
|
||||
latInput = document.getElementById('edit-location-lat');
|
||||
lngInput = document.getElementById('edit-location-lng');
|
||||
addressInput = document.getElementById('edit-location-address');
|
||||
} else {
|
||||
console.error('Invalid lookup mode:', mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!latInput || !lngInput || !addressInput) {
|
||||
showStatus('Form elements not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const lat = parseFloat(latInput.value);
|
||||
const lng = parseFloat(lngInput.value);
|
||||
|
||||
if (isNaN(lat) || isNaN(lng)) {
|
||||
showStatus('Please enter valid coordinates first', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
const button = mode === 'add' ?
|
||||
document.getElementById('lookup-address-add-btn') :
|
||||
document.getElementById('lookup-address-edit-btn');
|
||||
|
||||
const originalText = button ? button.textContent : '';
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.textContent = 'Looking up...';
|
||||
}
|
||||
|
||||
try {
|
||||
showStatus('Looking up address...', 'info');
|
||||
console.log(`Looking up address for: ${lat}, ${lng}`);
|
||||
const response = await fetch(`/api/geocode/reverse?lat=${lat}&lng=${lng}`);
|
||||
|
||||
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}&limit=1`);
|
||||
const results = await response.json();
|
||||
|
||||
if (results.length > 0) {
|
||||
const result = results[0];
|
||||
const lat = parseFloat(result.lat);
|
||||
const lng = parseFloat(result.lon);
|
||||
|
||||
// Update form fields
|
||||
if (mode === 'add') {
|
||||
document.getElementById('location-lat').value = lat.toFixed(8);
|
||||
document.getElementById('location-lng').value = lng.toFixed(8);
|
||||
document.getElementById('geo-location').value = `${lat.toFixed(8)};${lng.toFixed(8)}`;
|
||||
} else {
|
||||
document.getElementById('edit-location-lat').value = lat.toFixed(8);
|
||||
document.getElementById('edit-location-lng').value = lng.toFixed(8);
|
||||
document.getElementById('edit-geo-location').value = `${lat.toFixed(8)};${lng.toFixed(8)}`;
|
||||
}
|
||||
|
||||
// Center map on location
|
||||
map.setView([lat, lng], 16);
|
||||
|
||||
showStatus('Address found!', 'success');
|
||||
} else {
|
||||
showStatus('Address not found. Please try a different format.', 'warning');
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Geocoding failed: ${response.status} ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data) {
|
||||
// Use the formatted address or full address
|
||||
const address = data.data.formattedAddress || data.data.fullAddress;
|
||||
if (address) {
|
||||
addressInput.value = address;
|
||||
showStatus('Address found!', 'success');
|
||||
} else {
|
||||
showStatus('No address found for these coordinates', 'warning');
|
||||
}
|
||||
} else {
|
||||
showStatus('Address lookup failed', 'warning');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Address lookup error:', error);
|
||||
showStatus('Failed to lookup address', 'error');
|
||||
showStatus(`Address lookup failed: ${error.message}`, 'error');
|
||||
} finally {
|
||||
// Restore button state
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.textContent = originalText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -952,6 +952,20 @@ app.get('/api/locations', async (req, res) => {
|
||||
// Process locations to ensure they have required fields
|
||||
const locations = response.data.list || [];
|
||||
|
||||
// Log the structure of the first location to debug ID field name
|
||||
if (locations.length > 0) {
|
||||
const sampleLocation = locations[0];
|
||||
logger.info('Sample location structure:', {
|
||||
keys: Object.keys(sampleLocation),
|
||||
idFields: {
|
||||
'Id': sampleLocation.Id,
|
||||
'id': sampleLocation.id,
|
||||
'ID': sampleLocation.ID,
|
||||
'_id': sampleLocation._id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const validLocations = locations.filter(loc => {
|
||||
// Apply geo field synchronization to each location
|
||||
loc = syncGeoFields(loc);
|
||||
@@ -1148,15 +1162,34 @@ app.post('/api/locations', strictLimiter, async (req, res) => {
|
||||
// Update location
|
||||
app.put('/api/locations/:id', strictLimiter, async (req, res) => {
|
||||
try {
|
||||
const locationId = req.params.id;
|
||||
|
||||
// Validate ID
|
||||
if (!locationId || locationId === 'undefined' || locationId === 'null') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid location ID'
|
||||
});
|
||||
}
|
||||
|
||||
let updateData = { ...req.body };
|
||||
|
||||
// Remove ID from update data to avoid conflicts - use the correct field name
|
||||
delete updateData.ID;
|
||||
delete updateData.Id;
|
||||
delete updateData.id;
|
||||
delete updateData._id;
|
||||
|
||||
// Sync geo fields
|
||||
updateData = syncGeoFields(updateData);
|
||||
|
||||
updateData.last_updated_at = new Date().toISOString();
|
||||
updateData.last_updated_by = req.session.userEmail; // Track who updated
|
||||
|
||||
const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}/${req.params.id}`;
|
||||
const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}/${locationId}`;
|
||||
|
||||
logger.info(`Updating location ${locationId} by ${req.session.userEmail}`);
|
||||
logger.debug('Update data:', updateData);
|
||||
|
||||
const response = await axios.patch(url, updateData, {
|
||||
headers: {
|
||||
@@ -1172,9 +1205,14 @@ app.put('/api/locations/:id', strictLimiter, async (req, res) => {
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error updating location ${req.params.id}:`, error.message);
|
||||
if (error.response) {
|
||||
logger.error('Error response:', error.response.data);
|
||||
}
|
||||
|
||||
res.status(error.response?.status || 500).json({
|
||||
success: false,
|
||||
error: 'Failed to update location'
|
||||
error: 'Failed to update location',
|
||||
details: error.response?.data?.message || error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1182,7 +1220,17 @@ app.put('/api/locations/:id', strictLimiter, async (req, res) => {
|
||||
// Delete location
|
||||
app.delete('/api/locations/:id', strictLimiter, async (req, res) => {
|
||||
try {
|
||||
const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}/${req.params.id}`;
|
||||
const locationId = req.params.id;
|
||||
|
||||
// Validate ID
|
||||
if (!locationId || locationId === 'undefined' || locationId === 'null') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid location ID'
|
||||
});
|
||||
}
|
||||
|
||||
const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}/${locationId}`;
|
||||
|
||||
await axios.delete(url, {
|
||||
headers: {
|
||||
@@ -1190,7 +1238,7 @@ app.delete('/api/locations/:id', strictLimiter, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Location ${req.params.id} deleted by ${req.session.userEmail}`);
|
||||
logger.info(`Location ${locationId} deleted by ${req.session.userEmail}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -1206,67 +1254,34 @@ app.delete('/api/locations/:id', strictLimiter, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Debug endpoint to check settings table structure
|
||||
app.get('/api/debug/settings-table', requireAdmin, async (req, res) => {
|
||||
// Add a debug endpoint to check table structure
|
||||
app.get('/api/debug/table-structure', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
logger.info('Debug: SETTINGS_SHEET_ID =', SETTINGS_SHEET_ID);
|
||||
logger.info('Debug: NOCODB_API_URL =', process.env.NOCODB_API_URL);
|
||||
logger.info('Debug: NOCODB_PROJECT_ID =', process.env.NOCODB_PROJECT_ID);
|
||||
const url = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${process.env.NOCODB_TABLE_ID}`;
|
||||
|
||||
if (!SETTINGS_SHEET_ID) {
|
||||
return res.json({
|
||||
success: false,
|
||||
error: 'SETTINGS_SHEET_ID not configured',
|
||||
settingsSheetId: SETTINGS_SHEET_ID,
|
||||
originalSetting: process.env.NOCODB_SETTINGS_SHEET
|
||||
});
|
||||
}
|
||||
const response = await axios.get(url, {
|
||||
headers: {
|
||||
'xc-token': process.env.NOCODB_API_TOKEN
|
||||
},
|
||||
params: {
|
||||
limit: 1
|
||||
}
|
||||
});
|
||||
|
||||
// Try the working endpoint
|
||||
const workingEndpoint = `/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`;
|
||||
const sample = response.data.list?.[0] || {};
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${process.env.NOCODB_API_URL}${workingEndpoint}`,
|
||||
{
|
||||
headers: {
|
||||
'xc-token': process.env.NOCODB_API_TOKEN
|
||||
},
|
||||
params: {
|
||||
limit: 5
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const records = response.data.list || [];
|
||||
const sampleRecord = records.length > 0 ? records[0] : null;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
settingsSheetId: SETTINGS_SHEET_ID,
|
||||
workingEndpoint: workingEndpoint,
|
||||
recordCount: response.data.pageInfo?.totalRows || 0,
|
||||
sampleRecord: sampleRecord,
|
||||
availableFields: sampleRecord ? Object.keys(sampleRecord) : [],
|
||||
allRecords: records
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
responseData: error.response?.data,
|
||||
status: error.response?.status,
|
||||
settingsSheetId: SETTINGS_SHEET_ID
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
fields: Object.keys(sample),
|
||||
sampleRecord: sample,
|
||||
idField: sample.ID ? 'ID' : (sample.Id ? 'Id' : (sample.id ? 'id' : 'unknown'))
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Debug settings table error:', error);
|
||||
logger.error('Error checking table structure:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
settingsSheetId: SETTINGS_SHEET_ID
|
||||
error: 'Failed to check table structure'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user