package pages import ( "tankstopp/internal/currency" "tankstopp/internal/models" "tankstopp/internal/views/components" ) templ AddFuelStopPage(user *models.User, username string, vehicles []models.Vehicle, currencies []currency.Currency) { @components.BaseLayout("Add Fuel Stop", user, username) { @components.PageHeader("Add Fuel Stop", "Record a new fuel stop")

Fuel Stop Details

@components.FormGroup("Date", "") { @components.DateInput("date", "", true) }
@components.FormGroup("Vehicle", "") { @components.VehicleSelect("vehicle_id", 0, vehicles, true) }
@components.FormGroup("Station Name", "") {
@components.Input("station_name", "text", "Enter station name", "", false)
}
@components.FormGroup("Location", "") { @components.Input("location", "text", "Enter location", "", false) }
@components.FormGroup("Fuel Type", "") { @components.FuelTypeSelect("fuel_type", "", true) }
@components.FormGroup("Amount (Liters)", "") { @components.NumberInput("amount", "0.00", 0.0, "0.01", 0.0, true) }
@components.FormGroup("Price per Liter", "") {
@components.NumberInput("price_per_liter", "0.000", 0.0, "0.001", 0.0, true) { user.BaseCurrency }
}
@components.FormGroup("Total Cost", "") {
@components.NumberInput("total_cost", "0.00", 0.0, "0.01", 0.0, true) { user.BaseCurrency }
}
@components.FormGroup("Currency", "") { @components.CurrencySelect("currency", user.BaseCurrency, currencies) }
@components.FormGroup("Odometer Reading (km)", "") { @components.NumberInput("odometer", "0", 0.0, "1", 0.0, false) }
@components.FormGroup("Trip Length (km)", "") { @components.NumberInput("trip_length", "0.0", 0.0, "0.1", 0.0, false) }
@components.FormGroup("Notes", "") { @components.TextArea("notes", "Optional notes about this fuel stop", "", 3) }
@FuelStopScript(vehicles) } } templ EditFuelStopPage(user *models.User, username string, stop *models.FuelStop, vehicles []models.Vehicle, currencies []currency.Currency) { @components.BaseLayout("Edit Fuel Stop", user, username) { @components.PageHeader("Edit Fuel Stop", "Update fuel stop details")

Fuel Stop Details

@components.FormGroup("Date", "") { @components.DateInput("date", stop.Date.Format("2006-01-02"), true) }
@components.FormGroup("Vehicle", "") { @components.VehicleSelect("vehicle_id", stop.VehicleID, vehicles, true) }
@components.FormGroup("Station Name", "") {
@components.Input("station_name", "text", "Enter station name", stop.StationName, false)
}
@components.FormGroup("Location", "") { @components.Input("location", "text", "Enter location", stop.Location, false) }
@components.FormGroup("Fuel Type", "") { @components.FuelTypeSelect("fuel_type", stop.FuelType, true) }
@components.FormGroup("Amount (Liters)", "") { @components.NumberInput("amount", "0.00", stop.Liters, "0.01", 0.0, true) }
@components.FormGroup("Price per Liter", "") {
@components.NumberInput("price_per_liter", "0.000", stop.PricePerL, "0.001", 0.0, true) { stop.Currency }
}
@components.FormGroup("Total Cost", "") {
@components.NumberInput("total_cost", "0.00", stop.TotalPrice, "0.01", 0.0, true) { stop.Currency }
}
@components.FormGroup("Currency", "") { @components.CurrencySelect("currency", stop.Currency, currencies) }
@components.FormGroup("Odometer Reading (km)", "") { @components.NumberInput("odometer", "0", float64(stop.Odometer), "1", 0.0, false) }
@components.FormGroup("Trip Length (km)", "") { @components.NumberInput("trip_length", "0.0", stop.TripLength, "0.1", 0.0, false) }
@components.FormGroup("Notes", "") { @components.TextArea("notes", "Optional notes about this fuel stop", stop.Notes, 3) }
@FuelStopScript(vehicles) } } script FuelStopScript(vehicles []models.Vehicle) { // Update currency display when currency dropdown changes document.addEventListener('DOMContentLoaded', function() { const currencySelect = document.querySelector('select[name="currency"]'); const priceCurrency = document.getElementById('price-currency'); const totalCurrency = document.getElementById('total-currency'); if (currencySelect) { currencySelect.addEventListener('change', function() { const selectedCurrency = this.value; if (priceCurrency) priceCurrency.textContent = selectedCurrency; if (totalCurrency) totalCurrency.textContent = selectedCurrency; }); } // Update fuel type when vehicle is selected const vehicleSelect = document.querySelector('select[name="vehicle_id"]'); const fuelTypeSelect = document.querySelector('select[name="fuel_type"]'); if (vehicleSelect && fuelTypeSelect) { vehicleSelect.addEventListener('change', async function() { const selectedVehicleId = this.value; if (selectedVehicleId) { try { // Fetch vehicle information from API const response = await fetch(`/api/vehicles/${selectedVehicleId}`); if (response.ok) { const vehicle = await response.json(); if (vehicle.fuel_type) { fuelTypeSelect.value = vehicle.fuel_type; } } else { console.warn('Failed to fetch vehicle information'); } } catch (error) { console.error('Error fetching vehicle information:', error); } } else { // Reset fuel type when no vehicle is selected fuelTypeSelect.value = ''; } }); } // Auto-calculate total cost when amount or price per liter changes const amountInput = document.querySelector('input[name="amount"]'); const priceInput = document.querySelector('input[name="price_per_liter"]'); const totalInput = document.querySelector('input[name="total_cost"]'); function calculateTotal() { if (amountInput && priceInput && totalInput) { const amount = parseFloat(amountInput.value) || 0; const price = parseFloat(priceInput.value) || 0; const total = amount * price; totalInput.value = total.toFixed(2); } } if (amountInput) { amountInput.addEventListener('input', calculateTotal); } if (priceInput) { priceInput.addEventListener('input', calculateTotal); } // Also calculate total when total is changed (reverse calculation) if (totalInput && amountInput) { totalInput.addEventListener('input', function() { const total = parseFloat(this.value) || 0; const amount = parseFloat(amountInput.value) || 0; if (amount > 0) { const pricePerLiter = total / amount; if (priceInput) { priceInput.value = pricePerLiter.toFixed(3); } } }); } }); // Check if page is served over HTTPS function isSecureContext() { return location.protocol === 'https:' || location.hostname === 'localhost' || location.hostname === '127.0.0.1'; } // Debug function for location issues function debugLocationInfo() { console.log('=== Location Debug Info ==='); console.log('Protocol:', location.protocol); console.log('Hostname:', location.hostname); console.log('Is secure context:', isSecureContext()); console.log('Geolocation supported:', !!navigator.geolocation); console.log('Permissions API supported:', !!navigator.permissions); if (navigator.permissions) { navigator.permissions.query({name: 'geolocation'}).then(function(result) { console.log('Geolocation permission:', result.state); }).catch(function(error) { console.log('Permission query error:', error); }); } } // Fuel Station Search Functions window.findNearbyStations = function() { // Debug location setup debugLocationInfo(); // Check if we're in a secure context if (!isSecureContext()) { showStationSearchError('Geolocation requires HTTPS. Please access this page via HTTPS or use manual entry.'); return; } const modal = new bootstrap.Modal(document.getElementById('stationSearchModal')); modal.show(); // Reset modal content document.getElementById('stationSearchResults').innerHTML = `
Searching...

Finding nearby fuel stations...

`; // Get user's location with improved error handling if (navigator.geolocation) { console.log('Starting geolocation request...'); // Update status to show we're requesting location document.getElementById('stationSearchResults').innerHTML = `
Getting location...

Requesting your location...

Please allow location access when prompted
`; navigator.geolocation.getCurrentPosition( function(position) { const lat = position.coords.latitude; const lon = position.coords.longitude; console.log('Location obtained:', lat, lon); console.log('Accuracy:', position.coords.accuracy + 'm'); console.log('Timestamp:', new Date(position.timestamp)); searchNearbyStations(lat, lon); }, function(error) { console.error('Geolocation error:', error); console.error('Error code:', error.code); console.error('Error message:', error.message); let errorMessage = 'Unable to get your location. '; let showRetryOption = false; switch(error.code) { case error.PERMISSION_DENIED: errorMessage += 'Location access was denied. Please enable location services, refresh the page, and try again.'; if (!isSecureContext()) { errorMessage += ' Note: This page requires HTTPS for location access.'; } break; case error.POSITION_UNAVAILABLE: errorMessage += 'Location information is unavailable. Please check your GPS settings or try again.'; showRetryOption = true; break; case error.TIMEOUT: errorMessage += 'Location request timed out. Trying with lower accuracy...'; // Try again with lower accuracy tryLowAccuracyLocation(); return; default: errorMessage += 'An unknown error occurred while retrieving location.'; showRetryOption = true; break; } showStationSearchError(errorMessage, showRetryOption); }, { enableHighAccuracy: true, timeout: 15000, // Increased timeout to 15 seconds maximumAge: 300000 // 5 minutes } ); } else { showStationSearchError('Geolocation is not supported by this browser. Please enter station details manually.'); } }; // Fallback function for low accuracy location function tryLowAccuracyLocation() { document.getElementById('stationSearchResults').innerHTML = `
Trying low accuracy...

Trying with lower accuracy...

`; navigator.geolocation.getCurrentPosition( function(position) { const lat = position.coords.latitude; const lon = position.coords.longitude; console.log('Low accuracy location obtained:', lat, lon); searchNearbyStations(lat, lon); }, function(error) { console.error('Low accuracy geolocation error:', error); showStationSearchError('Unable to get your location even with low accuracy. Please enter station details manually or try again later.'); }, { enableHighAccuracy: false, // Use less accurate but faster location timeout: 30000, // Longer timeout for low accuracy maximumAge: 600000 // 10 minutes cache for low accuracy } ); }; function searchNearbyStations(lat, lon) { console.log('Searching for stations near:', lat, lon); // Update status to show we're searching document.getElementById('stationSearchResults').innerHTML = `
Searching...

Searching for nearby fuel stations...

This may take a few seconds
`; const overpassUrl = 'https://overpass-api.de/api/interpreter'; const query = ` [out:json][timeout:30]; ( node["amenity"="fuel"](around:5000,${lat},${lon}); way["amenity"="fuel"](around:5000,${lat},${lon}); relation["amenity"="fuel"](around:5000,${lat},${lon}); ); out center meta; `; console.log('Overpass query:', query); // Add timeout to the fetch request const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout fetch(overpassUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'data=' + encodeURIComponent(query), signal: controller.signal }) .then(response => { clearTimeout(timeoutId); console.log('API response status:', response.status); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { console.log('API response data:', data); if (data.remark && data.remark.includes('timeout')) { throw new Error('API request timed out'); } if (data.elements && data.elements.length > 0) { console.log(`Found ${data.elements.length} stations`); displayStationResults(data.elements, lat, lon); } else { console.log('No stations found in API response'); showStationSearchError('No fuel stations found within 5km of your location. Try searching manually or check a different area.'); } }) .catch(error => { clearTimeout(timeoutId); console.error('Error searching for stations:', error); let errorMessage = 'Error searching for fuel stations. '; if (error.name === 'AbortError') { errorMessage += 'The search timed out. Please try again or enter station details manually.'; } else if (error.message.includes('HTTP error')) { errorMessage += 'The map service is temporarily unavailable. Please try again later.'; } else if (error.message.includes('Failed to fetch')) { errorMessage += 'Network error. Please check your internet connection and try again.'; } else { errorMessage += 'Please try again or enter station details manually.'; } showStationSearchError(errorMessage); }); } function displayStationResults(stations, userLat, userLon) { // Calculate distances and sort by distance const stationsWithDistance = stations.map(station => { const stationLat = station.lat || (station.center && station.center.lat); const stationLon = station.lon || (station.center && station.center.lon); if (stationLat && stationLon) { const distance = calculateDistance(userLat, userLon, stationLat, stationLon); return { ...station, distance: distance, displayLat: stationLat, displayLon: stationLon }; } return null; }).filter(station => station !== null); stationsWithDistance.sort((a, b) => a.distance - b.distance); const resultsHTML = stationsWithDistance.map(station => { const name = station.tags.name || station.tags.brand || 'Unknown Station'; const address = [ station.tags['addr:street'], station.tags['addr:housenumber'], station.tags['addr:city'], station.tags['addr:postcode'] ].filter(Boolean).join(' '); const brand = station.tags.brand || ''; const operator = station.tags.operator || ''; const displayName = brand || operator || name; return `
${displayName}

${address}

${brand && brand !== displayName ? `${brand}` : ''}
${station.distance.toFixed(1)} km
`; }).join(''); document.getElementById('stationSearchResults').innerHTML = resultsHTML || '
No fuel stations found nearby.
'; } function calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371; // Earth's radius in km const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } function showStationSearchError(message, showRetryOption = false) { const retryButton = showRetryOption ? ` ` : ''; document.getElementById('stationSearchResults').innerHTML = `
${retryButton}
`; } window.showManualEntry = function() { document.getElementById('stationSearchResults').innerHTML = `
`; }; window.selectManualStation = function(event) { event.preventDefault(); const stationName = document.getElementById('manual-station-name').value; const stationBrand = document.getElementById('manual-station-brand').value; const stationAddress = document.getElementById('manual-station-address').value; // Use brand name if provided, otherwise use the entered name const finalName = stationBrand && stationBrand !== 'Other' ? stationBrand : stationName; // Fill form fields document.querySelector('input[name="station_name"]').value = finalName; document.querySelector('input[name="location"]').value = stationAddress; // Close modal const modal = bootstrap.Modal.getInstance(document.getElementById('stationSearchModal')); if (modal) { modal.hide(); } // Show success message const toast = document.createElement('div'); toast.className = 'toast align-items-center text-white bg-success border-0'; toast.setAttribute('role', 'alert'); toast.setAttribute('aria-live', 'assertive'); toast.setAttribute('aria-atomic', 'true'); toast.innerHTML = `
Station entered manually: ${finalName}
`; document.body.appendChild(toast); const bsToast = new bootstrap.Toast(toast); bsToast.show(); // Remove toast after it hides toast.addEventListener('hidden.bs.toast', function() { document.body.removeChild(toast); }); return false; }; window.selectStation = function(name, address, lat, lon) { // Fill form fields document.querySelector('input[name="station_name"]').value = name; document.querySelector('input[name="location"]').value = address; // Close modal const modal = bootstrap.Modal.getInstance(document.getElementById('stationSearchModal')); if (modal) { modal.hide(); } // Show success message const toast = document.createElement('div'); toast.className = 'toast align-items-center text-white bg-success border-0'; toast.setAttribute('role', 'alert'); toast.setAttribute('aria-live', 'assertive'); toast.setAttribute('aria-atomic', 'true'); toast.innerHTML = `
Station selected: ${name}
`; document.body.appendChild(toast); const bsToast = new bootstrap.Toast(toast); bsToast.show(); // Remove toast after it hides toast.addEventListener('hidden.bs.toast', function() { document.body.removeChild(toast); }); }; }