Semi working map cuts view; need to refactor and fix some stuff however stable enough to commit
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
const nocodbService = require('../services/nocodb');
|
||||
const logger = require('../utils/logger');
|
||||
const config = require('../config');
|
||||
const spatialUtils = require('../utils/spatial');
|
||||
|
||||
class CutsController {
|
||||
/**
|
||||
@@ -358,6 +359,272 @@ class CutsController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all locations within a cut boundary - admin only
|
||||
*/
|
||||
async getLocationsInCut(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { isAdmin } = req.user || {};
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
|
||||
// Get the cut
|
||||
const cut = await nocodbService.getById(config.CUTS_TABLE_ID, id);
|
||||
if (!cut) {
|
||||
return res.status(404).json({ error: 'Cut not found' });
|
||||
}
|
||||
|
||||
// Get all locations
|
||||
const locationsResponse = await nocodbService.getAll(config.LOCATIONS_TABLE_ID);
|
||||
if (!locationsResponse || !locationsResponse.list) {
|
||||
return res.json({ locations: [], statistics: { total_locations: 0 } });
|
||||
}
|
||||
|
||||
// Apply filters from query params
|
||||
const filters = {
|
||||
support_level: req.query.support_level,
|
||||
has_sign: req.query.has_sign === 'true' ? true : req.query.has_sign === 'false' ? false : undefined,
|
||||
sign_size: req.query.sign_size,
|
||||
has_email: req.query.has_email === 'true' ? true : req.query.has_email === 'false' ? false : undefined,
|
||||
has_phone: req.query.has_phone === 'true' ? true : req.query.has_phone === 'false' ? false : undefined
|
||||
};
|
||||
|
||||
// Filter locations within cut boundaries
|
||||
const filteredLocations = spatialUtils.filterLocationsInCut(
|
||||
locationsResponse.list,
|
||||
cut,
|
||||
filters
|
||||
);
|
||||
|
||||
// Calculate statistics
|
||||
const statistics = spatialUtils.calculateCutStatistics(filteredLocations);
|
||||
|
||||
const cutName = cut.name || cut.Name || cut.title || cut.Title || 'Unknown';
|
||||
logger.info(`Found ${filteredLocations.length} locations in cut: ${cutName}`);
|
||||
res.json({
|
||||
locations: filteredLocations,
|
||||
statistics,
|
||||
cut: { id: cut.id || cut.Id || cut.ID, name: cutName }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error getting locations in cut:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to get locations in cut',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export locations within a cut as CSV - admin only
|
||||
*/
|
||||
async exportCutLocations(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { isAdmin } = req.user || {};
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
|
||||
// Get the cut
|
||||
const cut = await nocodbService.getById(config.CUTS_TABLE_ID, id);
|
||||
if (!cut) {
|
||||
return res.status(404).json({ error: 'Cut not found' });
|
||||
}
|
||||
|
||||
// Check if export is enabled for this cut
|
||||
if (cut.export_enabled === false) {
|
||||
return res.status(403).json({ error: 'Export is disabled for this cut' });
|
||||
}
|
||||
|
||||
// Get all locations
|
||||
const locationsResponse = await nocodbService.getAll(config.LOCATIONS_TABLE_ID);
|
||||
if (!locationsResponse || !locationsResponse.list) {
|
||||
return res.json({ locations: [] });
|
||||
}
|
||||
|
||||
// Apply filters from query params
|
||||
const filters = {
|
||||
support_level: req.query.support_level,
|
||||
has_sign: req.query.has_sign === 'true' ? true : req.query.has_sign === 'false' ? false : undefined,
|
||||
sign_size: req.query.sign_size,
|
||||
has_email: req.query.has_email === 'true' ? true : req.query.has_email === 'false' ? false : undefined,
|
||||
has_phone: req.query.has_phone === 'true' ? true : req.query.has_phone === 'false' ? false : undefined
|
||||
};
|
||||
|
||||
// Filter locations within cut boundaries
|
||||
const filteredLocations = spatialUtils.filterLocationsInCut(
|
||||
locationsResponse.list,
|
||||
cut,
|
||||
filters
|
||||
);
|
||||
|
||||
// Generate CSV content
|
||||
const csvHeaders = [
|
||||
'ID', 'First Name', 'Last Name', 'Email', 'Phone', 'Address',
|
||||
'Unit Number', 'Support Level', 'Has Sign', 'Sign Size',
|
||||
'Latitude', 'Longitude', 'Notes'
|
||||
];
|
||||
|
||||
const csvRows = filteredLocations.map(location => [
|
||||
location.id || '',
|
||||
location.first_name || '',
|
||||
location.last_name || '',
|
||||
location.email || '',
|
||||
location.phone || '',
|
||||
location.address || '',
|
||||
location.unit_number || '',
|
||||
location.support_level || '',
|
||||
location.sign ? 'Yes' : 'No',
|
||||
location.sign_size || '',
|
||||
location.latitude || '',
|
||||
location.longitude || '',
|
||||
(location.notes || '').replace(/"/g, '""') // Escape quotes in notes
|
||||
]);
|
||||
|
||||
const csvContent = [
|
||||
csvHeaders.join(','),
|
||||
...csvRows.map(row => row.map(field => `"${field}"`).join(','))
|
||||
].join('\n');
|
||||
|
||||
const cutName = (cut.name || 'cut').replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const timestamp = new Date().toISOString().split('T')[0];
|
||||
const filename = `${cutName}_locations_${timestamp}.csv`;
|
||||
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
res.send(csvContent);
|
||||
|
||||
logger.info(`Exported ${filteredLocations.length} locations from cut: ${cut.name}`);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error exporting cut locations:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to export cut locations',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cut settings (visibility, filters, etc.) - admin only
|
||||
*/
|
||||
async updateCutSettings(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { isAdmin } = req.user || {};
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
|
||||
const {
|
||||
show_locations,
|
||||
export_enabled,
|
||||
assigned_to,
|
||||
filter_settings,
|
||||
last_canvassed,
|
||||
completion_percentage
|
||||
} = req.body;
|
||||
|
||||
const updateData = {
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Only include fields that are provided
|
||||
if (show_locations !== undefined) updateData.show_locations = show_locations;
|
||||
if (export_enabled !== undefined) updateData.export_enabled = export_enabled;
|
||||
if (assigned_to !== undefined) updateData.assigned_to = assigned_to;
|
||||
if (filter_settings !== undefined) updateData.filter_settings = JSON.stringify(filter_settings);
|
||||
if (last_canvassed !== undefined) updateData.last_canvassed = last_canvassed;
|
||||
if (completion_percentage !== undefined) updateData.completion_percentage = completion_percentage;
|
||||
|
||||
const response = await nocodbService.update(
|
||||
config.CUTS_TABLE_ID,
|
||||
id,
|
||||
updateData
|
||||
);
|
||||
|
||||
logger.info(`Updated cut settings for cut ID: ${id}`);
|
||||
res.json(response);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error updating cut settings:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to update cut settings',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cut statistics - admin only
|
||||
*/
|
||||
async getCutStatistics(req, res) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { isAdmin } = req.user || {};
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
|
||||
// Get the cut
|
||||
const cut = await nocodbService.getById(config.CUTS_TABLE_ID, id);
|
||||
if (!cut) {
|
||||
return res.status(404).json({ error: 'Cut not found' });
|
||||
}
|
||||
|
||||
// Get all locations
|
||||
const locationsResponse = await nocodbService.getAll(config.LOCATIONS_TABLE_ID);
|
||||
if (!locationsResponse || !locationsResponse.list) {
|
||||
return res.json({
|
||||
statistics: { total_locations: 0 },
|
||||
cut: { id: cut.id, name: cut.name }
|
||||
});
|
||||
}
|
||||
|
||||
// Get locations within cut boundaries (no additional filters)
|
||||
const locationsInCut = spatialUtils.filterLocationsInCut(
|
||||
locationsResponse.list,
|
||||
cut,
|
||||
{} // No additional filters for statistics
|
||||
);
|
||||
|
||||
// Calculate statistics
|
||||
const statistics = spatialUtils.calculateCutStatistics(locationsInCut);
|
||||
|
||||
// Add cut metadata
|
||||
const cutStats = {
|
||||
...statistics,
|
||||
cut_metadata: {
|
||||
id: cut.id,
|
||||
name: cut.name,
|
||||
category: cut.category,
|
||||
assigned_to: cut.assigned_to,
|
||||
completion_percentage: cut.completion_percentage || 0,
|
||||
last_canvassed: cut.last_canvassed,
|
||||
created_at: cut.created_at
|
||||
}
|
||||
};
|
||||
|
||||
logger.info(`Generated statistics for cut: ${cut.name}`);
|
||||
res.json({ statistics: cutStats });
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error getting cut statistics:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to get cut statistics',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CutsController();
|
||||
|
||||
Reference in New Issue
Block a user