Major architectural improvements: - Created models/services layer for better separation of concerns - Location model with async methods for database operations - ProfanityWord model for content moderation - DatabaseService for centralized database management - ProfanityFilterService refactored to use models - Refactored frontend map implementations to share common code - MapBase class extracts 60-70% of duplicate functionality - Refactored implementations extend MapBase for specific features - Maintained unique geocoding capabilities per implementation - Updated server.js to use new service architecture - All routes now use async/await with models instead of raw queries - Enhanced error handling and maintainability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
266 lines
No EOL
9.7 KiB
HTML
266 lines
No EOL
9.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Test Refactored Maps</title>
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<link rel="stylesheet" href="style.css">
|
|
<style>
|
|
.test-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
.test-section {
|
|
margin-bottom: 40px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
}
|
|
.test-section h2 {
|
|
margin-top: 0;
|
|
color: #333;
|
|
}
|
|
#map {
|
|
height: 400px;
|
|
width: 100%;
|
|
border-radius: 8px;
|
|
}
|
|
.map-controls {
|
|
margin-bottom: 20px;
|
|
}
|
|
.btn {
|
|
padding: 10px 20px;
|
|
margin: 5px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
background: #007bff;
|
|
color: white;
|
|
}
|
|
.btn.active {
|
|
background: #0056b3;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
}
|
|
.form-group input, .form-group textarea {
|
|
width: 100%;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
.search-container {
|
|
position: relative;
|
|
}
|
|
#autocomplete {
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-top: none;
|
|
border-radius: 0 0 4px 4px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
z-index: 1000;
|
|
display: none;
|
|
}
|
|
#autocomplete.show {
|
|
display: block;
|
|
}
|
|
.autocomplete-item {
|
|
padding: 10px;
|
|
cursor: pointer;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
.autocomplete-item:hover,
|
|
.autocomplete-item.selected {
|
|
background: #f0f0f0;
|
|
}
|
|
.message {
|
|
padding: 10px;
|
|
margin: 10px 0;
|
|
border-radius: 4px;
|
|
display: none;
|
|
}
|
|
.message.show {
|
|
display: block;
|
|
}
|
|
.message.success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
.message.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
.location-stats {
|
|
margin: 10px 0;
|
|
padding: 10px;
|
|
background: #f8f9fa;
|
|
border-radius: 4px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="test-container">
|
|
<h1>Refactored Map Components Test</h1>
|
|
|
|
<div class="test-section">
|
|
<h2>Map Display</h2>
|
|
<div class="map-controls">
|
|
<button id="map-view-btn" class="btn active">Map View</button>
|
|
<button id="table-view-btn" class="btn">Table View</button>
|
|
<button id="theme-toggle" class="btn">Toggle Theme</button>
|
|
<button id="refresh-button" class="btn">Refresh</button>
|
|
</div>
|
|
|
|
<div id="map-view">
|
|
<div id="map"></div>
|
|
</div>
|
|
|
|
<div id="table-view" style="display: none;">
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
<thead>
|
|
<tr style="background: #f8f9fa;">
|
|
<th style="padding: 10px; border: 1px solid #ddd;">Location</th>
|
|
<th style="padding: 10px; border: 1px solid #ddd;">Details</th>
|
|
<th style="padding: 10px; border: 1px solid #ddd;">Reported</th>
|
|
<th style="padding: 10px; border: 1px solid #ddd;">Expires</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="reports-tbody">
|
|
<tr><td colspan="4" style="padding: 20px; text-align: center;">Loading...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="location-stats">
|
|
<span id="location-count">Loading...</span> |
|
|
<span id="last-update">Initializing...</span> |
|
|
<span id="table-location-count">0 reports</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>Location Submission Form</h2>
|
|
<form id="location-form">
|
|
<div class="form-group">
|
|
<label for="address">Address:</label>
|
|
<div class="search-container">
|
|
<input type="text" id="address" placeholder="Enter address or intersection..." required>
|
|
<div id="autocomplete"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="description">Description (optional):</label>
|
|
<textarea id="description" placeholder="Describe road conditions..." rows="3"></textarea>
|
|
</div>
|
|
|
|
<button type="submit" class="submit-btn btn">
|
|
<i class="fas fa-flag"></i> Report Ice
|
|
</button>
|
|
</form>
|
|
|
|
<div id="message" class="message"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script src="utils.js"></script>
|
|
<script src="map-base.js"></script>
|
|
<script>
|
|
// Test both implementations
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Create a simple test implementation
|
|
class TestMapApp extends MapBase {
|
|
constructor() {
|
|
super();
|
|
this.initializeMap();
|
|
this.initializeTestFeatures();
|
|
}
|
|
|
|
initializeMap() {
|
|
this.map = L.map('map').setView([42.9634, -85.6681], 10);
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
maxZoom: 18,
|
|
}).addTo(this.map);
|
|
}
|
|
|
|
initializeTestFeatures() {
|
|
// View toggle
|
|
const mapViewBtn = document.getElementById('map-view-btn');
|
|
const tableViewBtn = document.getElementById('table-view-btn');
|
|
const themeToggle = document.getElementById('theme-toggle');
|
|
|
|
if (mapViewBtn) mapViewBtn.addEventListener('click', () => this.switchView('map'));
|
|
if (tableViewBtn) tableViewBtn.addEventListener('click', () => this.switchView('table'));
|
|
if (themeToggle) themeToggle.addEventListener('click', () => this.toggleTheme());
|
|
}
|
|
|
|
switchView(viewType) {
|
|
const mapView = document.getElementById('map-view');
|
|
const tableView = document.getElementById('table-view');
|
|
const mapViewBtn = document.getElementById('map-view-btn');
|
|
const tableViewBtn = document.getElementById('table-view-btn');
|
|
|
|
if (viewType === 'map') {
|
|
mapView.style.display = 'block';
|
|
tableView.style.display = 'none';
|
|
mapViewBtn.classList.add('active');
|
|
tableViewBtn.classList.remove('active');
|
|
setTimeout(() => this.map.invalidateSize(), 100);
|
|
} else {
|
|
mapView.style.display = 'none';
|
|
tableView.style.display = 'block';
|
|
mapViewBtn.classList.remove('active');
|
|
tableViewBtn.classList.add('active');
|
|
}
|
|
}
|
|
|
|
toggleTheme() {
|
|
const currentTheme = document.body.dataset.theme || 'light';
|
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
document.body.dataset.theme = newTheme;
|
|
}
|
|
|
|
createMarker(location) {
|
|
const isPersistent = !!location.persistent;
|
|
const timeAgo = window.getTimeAgo ? window.getTimeAgo(location.created_at) : '';
|
|
|
|
const marker = L.marker([location.latitude, location.longitude])
|
|
.addTo(this.map)
|
|
.bindPopup(`<strong>${location.address}</strong><br>${location.description || 'No details'}<br><small>${timeAgo}</small>`);
|
|
|
|
return marker;
|
|
}
|
|
|
|
async searchAddress(query) {
|
|
try {
|
|
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&limit=5&q=${encodeURIComponent(query + ', Michigan')}`);
|
|
const data = await response.json();
|
|
this.showAutocomplete(data.slice(0, 5));
|
|
} catch (error) {
|
|
console.error('Search failed:', error);
|
|
this.showAutocomplete([]);
|
|
}
|
|
}
|
|
}
|
|
|
|
new TestMapApp();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |