Compare commits

..

No commits in common. "92f18ce7f0e2ad2747cad9f33f988360b08ff174" and "0a784beb415032aba028fa25425eacfd7158ce92" have entirely different histories.

View File

@ -5,7 +5,7 @@
{% block content %} {% block content %}
<div class="container py-5"> <div class="container py-5">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-lg-10"> <div class="col-md-8">
<!-- Back to Dashboard --> <!-- Back to Dashboard -->
<div class="mb-4"> <div class="mb-4">
<a href="{% url 'dashboard' %}" class="btn btn-link text-decoration-none text-muted ps-0"> <a href="{% url 'dashboard' %}" class="btn btn-link text-decoration-none text-muted ps-0">
@ -14,7 +14,7 @@
</div> </div>
<div class="card border-0 shadow-sm p-4" style="border-radius: 20px;"> <div class="card border-0 shadow-sm p-4" style="border-radius: 20px;">
<h2 class="mb-4 text-center">{% trans "Request a Shipment" %}</h2> <h2 class="mb-4">{% trans "Request a Shipment" %}</h2>
{% if not google_maps_api_key %} {% if not google_maps_api_key %}
<div class="alert alert-warning"> <div class="alert alert-warning">
@ -35,141 +35,112 @@
{{ form.driver_amount }} {{ form.driver_amount }}
{{ form.platform_fee_percentage }} {{ form.platform_fee_percentage }}
<!-- General Info Section --> <div class="row g-3">
<div class="card bg-light border-0 mb-4"> <!-- General Info -->
<div class="card-body"> <div class="col-12">
<h5 class="card-title text-secondary mb-3">{% trans "Shipment Information" %}</h5> <label class="form-label" for="{{ form.description.id_for_label }}">{{ form.description.label }}</label>
<div class="row g-3"> {{ form.description }}
<div class="col-12"> {% if form.description.errors %}
<label class="form-label fw-bold" for="{{ form.description.id_for_label }}">{{ form.description.label }}</label> <div class="text-danger small">{{ form.description.errors }}</div>
{{ form.description }} {% endif %}
{% if form.description.errors %} </div>
<div class="text-danger small">{{ form.description.errors }}</div> <div class="col-md-6">
{% endif %} <label class="form-label" for="{{ form.weight.id_for_label }}">{{ form.weight.label }}</label>
</div> {{ form.weight }}
<div class="col-md-6"> {% if form.weight.errors %}
<label class="form-label fw-bold" for="{{ form.weight.id_for_label }}">{{ form.weight.label }}</label> <div class="text-danger small">{{ form.weight.errors }}</div>
{{ form.weight }} {% endif %}
{% if form.weight.errors %} </div>
<div class="text-danger small">{{ form.weight.errors }}</div> <div class="col-md-6">
{% endif %} <label class="form-label" for="{{ form.price.id_for_label }}">{{ form.price.label }}</label>
</div> {{ form.price }}
<div class="col-md-6"> <small class="text-muted">{% trans "Calculated automatically based on distance and weight." %}</small>
<label class="form-label fw-bold" for="{{ form.price.id_for_label }}">{{ form.price.label }}</label> {% if form.price.errors %}
{{ form.price }} <div class="text-danger small">{{ form.price.errors }}</div>
<small class="text-muted">{% trans "Calculated automatically based on distance and weight." %}</small> {% endif %}
{% if form.price.errors %}
<div class="text-danger small">{{ form.price.errors }}</div>
{% endif %}
</div>
</div>
</div> </div>
</div>
<div class="row"> <!-- Pickup Details -->
<!-- Pickup Container --> <div class="col-12 mt-4">
<div class="col-md-6 mb-4"> <h4 class="mb-3 text-secondary border-bottom pb-2 d-flex justify-content-between align-items-center">
<div class="card h-100 border shadow-sm"> {% trans "Pickup Details" %}
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center"> {% if google_maps_api_key %}
<h5 class="mb-0 text-primary"> <button type="button" class="btn btn-sm btn-outline-primary" onclick="openMap('pickup')">
<i class="bi bi-box-seam me-2"></i>{% trans "Pickup Details" %} <i class="bi bi-geo-alt-fill"></i> {% trans "Select on Map" %}
</h5> </button>
{% if google_maps_api_key %} {% endif %}
<button type="button" class="btn btn-sm btn-outline-primary" onclick="openMap('pickup')"> </h4>
<i class="bi bi-geo-alt-fill"></i> {% trans "Map" %} </div>
</button> <div class="col-md-6">
{% endif %} <label class="form-label" for="{{ form.pickup_country.id_for_label }}">{{ form.pickup_country.label }}</label>
{{ form.pickup_country }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.pickup_governate.id_for_label }}">{{ form.pickup_governate.label }}</label>
{{ form.pickup_governate }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.pickup_city.id_for_label }}">{{ form.pickup_city.label }}</label>
{{ form.pickup_city }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.pickup_address.id_for_label }}">{{ form.pickup_address.label }}</label>
{{ form.pickup_address }}
</div>
<!-- Delivery Details -->
<div class="col-12 mt-4">
<h4 class="mb-3 text-secondary border-bottom pb-2 d-flex justify-content-between align-items-center">
{% trans "Delivery Details" %}
{% if google_maps_api_key %}
<button type="button" class="btn btn-sm btn-outline-primary" onclick="openMap('delivery')">
<i class="bi bi-geo-alt-fill"></i> {% trans "Select on Map" %}
</button>
{% endif %}
</h4>
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.delivery_country.id_for_label }}">{{ form.delivery_country.label }}</label>
{{ form.delivery_country }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.delivery_governate.id_for_label }}">{{ form.delivery_governate.label }}</label>
{{ form.delivery_governate }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.delivery_city.id_for_label }}">{{ form.delivery_city.label }}</label>
{{ form.delivery_city }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.delivery_address.id_for_label }}">{{ form.delivery_address.label }}</label>
{{ form.delivery_address }}
</div>
<!-- Receiver Details -->
<div class="col-12 mt-4">
<h4 class="mb-3 text-secondary border-bottom pb-2">{% trans "Receiver Details" %}</h4>
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.receiver_name.id_for_label }}">{{ form.receiver_name.label }}</label>
{{ form.receiver_name }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.receiver_phone.id_for_label }}">{{ form.receiver_phone.label }}</label>
<div class="d-flex gap-2">
<div class="flex-shrink-0" style="width: 140px;">
{{ form.receiver_phone_code }}
</div> </div>
<div class="card-body"> <div class="flex-grow-1">
<div class="row g-3"> {{ form.receiver_phone }}
<div class="col-12">
<label class="form-label" for="{{ form.pickup_country.id_for_label }}">{{ form.pickup_country.label }}</label>
{{ form.pickup_country }}
</div>
<div class="col-12">
<label class="form-label" for="{{ form.pickup_governate.id_for_label }}">{{ form.pickup_governate.label }}</label>
{{ form.pickup_governate }}
</div>
<div class="col-12">
<label class="form-label" for="{{ form.pickup_city.id_for_label }}">{{ form.pickup_city.label }}</label>
{{ form.pickup_city }}
</div>
<div class="col-12">
<label class="form-label" for="{{ form.pickup_address.id_for_label }}">{{ form.pickup_address.label }}</label>
{{ form.pickup_address }}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Delivery Container --> <div class="col-12 mt-4 d-flex gap-2">
<div class="col-md-6 mb-4"> <a href="{% url 'dashboard' %}" class="btn btn-outline-secondary w-50 py-3">{% trans "Cancel" %}</a>
<div class="card h-100 border shadow-sm"> <button type="submit" class="btn btn-masarx-primary w-50 py-3">{% trans "Submit Request" %}</button>
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 text-success">
<i class="bi bi-geo-alt me-2"></i>{% trans "Delivery Details" %}
</h5>
{% if google_maps_api_key %}
<button type="button" class="btn btn-sm btn-outline-success" onclick="openMap('delivery')">
<i class="bi bi-geo-alt-fill"></i> {% trans "Map" %}
</button>
{% endif %}
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label" for="{{ form.delivery_country.id_for_label }}">{{ form.delivery_country.label }}</label>
{{ form.delivery_country }}
</div>
<div class="col-12">
<label class="form-label" for="{{ form.delivery_governate.id_for_label }}">{{ form.delivery_governate.label }}</label>
{{ form.delivery_governate }}
</div>
<div class="col-12">
<label class="form-label" for="{{ form.delivery_city.id_for_label }}">{{ form.delivery_city.label }}</label>
{{ form.delivery_city }}
</div>
<div class="col-12">
<label class="form-label" for="{{ form.delivery_address.id_for_label }}">{{ form.delivery_address.label }}</label>
{{ form.delivery_address }}
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- Receiver Details -->
<div class="card border mb-4">
<div class="card-header bg-white py-3">
<h5 class="mb-0 text-secondary">{% trans "Receiver Details" %}</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="{{ form.receiver_name.id_for_label }}">{{ form.receiver_name.label }}</label>
{{ form.receiver_name }}
</div>
<div class="col-md-6">
<label class="form-label" for="{{ form.receiver_phone.id_for_label }}">{{ form.receiver_phone.label }}</label>
<div class="d-flex gap-2">
<div class="flex-shrink-0" style="width: 140px;">
{{ form.receiver_phone_code }}
</div>
<div class="flex-grow-1">
{{ form.receiver_phone }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex gap-3 justify-content-end">
<a href="{% url 'dashboard' %}" class="btn btn-outline-secondary px-5 py-2">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-masarx-primary px-5 py-2">{% trans "Submit Request" %}</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@ -184,8 +155,7 @@
<h5 class="modal-title">{% trans "Choose Location" %}</h5> <h5 class="modal-title">{% trans "Choose Location" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body p-0 position-relative"> <div class="modal-body p-0">
<div id="mapError" class="alert alert-danger m-3 d-none"></div>
<div id="map" style="height: 450px; width: 100%;"></div> <div id="map" style="height: 450px; width: 100%;"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -197,70 +167,30 @@
</div> </div>
{% if google_maps_api_key %} {% if google_maps_api_key %}
<script src="https://maps.googleapis.com/maps/api/js?key={{ google_maps_api_key }}&libraries=places"></script>
<script> <script>
// Global Auth Failure Handler (must be defined before script load)
window.gm_authFailure = function() {
const errorDiv = document.getElementById('mapError');
if (errorDiv) {
errorDiv.innerHTML = `
<strong>Google Maps Error:</strong> Authentication failed.
<br>Please check your API Key and enable <strong>Billing</strong> in Google Cloud Console.
<br>Also ensure <strong>Maps JavaScript API</strong> and <strong>Geocoding API</strong> are enabled.
`;
errorDiv.classList.remove('d-none');
}
console.error("Google Maps Auth Failure");
}
let map; let map;
let marker; let marker;
let currentMode = 'pickup'; // 'pickup' or 'delivery' let currentMode = 'pickup'; // 'pickup' or 'delivery'
let mapModal; let mapModal;
let mapInitialized = false;
// This function is called by the Google Maps script when loaded
function initMap() { function initMap() {
console.log("Google Maps API loaded.");
}
function initializeMapInstance() {
if (mapInitialized && map) return;
console.log("Initializing Map Instance...");
const defaultLocation = { lat: 23.5880, lng: 58.3829 }; // Muscat, Oman const defaultLocation = { lat: 23.5880, lng: 58.3829 }; // Muscat, Oman
const mapElement = document.getElementById("map"); map = new google.maps.Map(document.getElementById("map"), {
if (!mapElement) { zoom: 12,
console.error("Map element not found!"); center: defaultLocation,
return; });
}
try { marker = new google.maps.Marker({
map = new google.maps.Map(mapElement, { map: map,
zoom: 12, draggable: true,
center: defaultLocation, animation: google.maps.Animation.DROP,
}); });
marker = new google.maps.Marker({ map.addListener("click", (e) => {
map: map, placeMarkerAndPanTo(e.latLng, map);
draggable: true, });
animation: google.maps.Animation.DROP,
});
map.addListener("click", (e) => {
placeMarkerAndPanTo(e.latLng, map);
});
mapInitialized = true;
} catch (e) {
console.error("Error creating map:", e);
const errorDiv = document.getElementById('mapError');
if (errorDiv) {
errorDiv.innerText = "Error initializing map: " + e.message;
errorDiv.classList.remove('d-none');
}
}
} }
function placeMarkerAndPanTo(latLng, map) { function placeMarkerAndPanTo(latLng, map) {
@ -268,74 +198,32 @@
map.panTo(latLng); map.panTo(latLng);
} }
function getSelectedText(elementId) {
const el = document.getElementById(elementId);
if (el && el.selectedIndex !== -1 && el.options[el.selectedIndex].value) {
return el.options[el.selectedIndex].text;
}
return '';
}
function openMap(mode) { function openMap(mode) {
currentMode = mode; currentMode = mode;
const modalEl = document.getElementById('mapModal'); mapModal = new bootstrap.Modal(document.getElementById('mapModal'));
// Hide previous errors
const errorDiv = document.getElementById('mapError');
if (errorDiv) errorDiv.classList.add('d-none');
mapModal = new bootstrap.Modal(modalEl);
mapModal.show(); mapModal.show();
// Ensure map is initialized and resized when modal is fully shown // Resize map when modal opens
modalEl.addEventListener('shown.bs.modal', function () { document.getElementById('mapModal').addEventListener('shown.bs.modal', function () {
initializeMapInstance(); google.maps.event.trigger(map, "resize");
if (map) { // Set marker if existing value
google.maps.event.trigger(map, "resize"); let latField = document.getElementById(`id_${mode}_lat`);
let lngField = document.getElementById(`id_${mode}_lng`);
// 1. Check if lat/lng are already set (editing existing parcel or re-opening map)
let latField = document.getElementById(`id_${mode}_lat`); if (latField.value && lngField.value) {
let lngField = document.getElementById(`id_${mode}_lng`); let loc = { lat: parseFloat(latField.value), lng: parseFloat(lngField.value) };
marker.setPosition(loc);
if (latField.value && lngField.value) { map.setCenter(loc);
let loc = { lat: parseFloat(latField.value), lng: parseFloat(lngField.value) }; } else {
marker.setPosition(loc); // Default to Muscat if no value
map.setCenter(loc); map.setCenter({ lat: 23.5880, lng: 58.3829 });
map.setZoom(15);
} else {
// 2. If no lat/lng, try to find the selected City/Governate/Country
const city = getSelectedText(`id_${mode}_city`);
const governate = getSelectedText(`id_${mode}_governate`);
const country = getSelectedText(`id_${mode}_country`);
if (city) {
let addressQuery = city;
if (governate) addressQuery += `, ${governate}`;
if (country) addressQuery += `, ${country}`;
console.log(`Geocoding selected location: ${addressQuery}`);
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: addressQuery }, (results, status) => {
if (status === "OK" && results[0]) {
const location = results[0].geometry.location;
map.setCenter(location);
map.setZoom(14);
// Optional: place marker at city center
marker.setPosition(location);
} else {
console.warn("Could not geocode city: " + status);
}
});
}
}
} }
}, { once: true }); });
} }
function confirmLocation() { function confirmLocation() {
if (!marker || !marker.getPosition()) return; if (!marker.getPosition()) return;
const lat = marker.getPosition().lat(); const lat = marker.getPosition().lat();
const lng = marker.getPosition().lng(); const lng = marker.getPosition().lng();
@ -343,16 +231,15 @@
document.getElementById(`id_${currentMode}_lat`).value = lat; document.getElementById(`id_${currentMode}_lat`).value = lat;
document.getElementById(`id_${currentMode}_lng`).value = lng; document.getElementById(`id_${currentMode}_lng`).value = lng;
// Try reverse geocoding to fill address // Try reverse geocoding to fill address (optional but nice)
const geocoder = new google.maps.Geocoder(); const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: { lat: lat, lng: lng } }, (results, status) => { geocoder.geocode({ location: { lat: lat, lng: lng } }, (results, status) => {
if (status === "OK" && results[0]) { if (status === "OK" && results[0]) {
// Simple fill of address field if empty
const addrField = document.getElementById(`id_${currentMode}_address`); const addrField = document.getElementById(`id_${currentMode}_address`);
// Only overwrite address if it's empty or user confirms? if (!addrField.value) {
// Currently existing behavior is to overwrite. addrField.value = results[0].formatted_address;
addrField.value = results[0].formatted_address; }
} else {
console.warn("Geocoder failed due to: " + status);
} }
}); });
@ -390,7 +277,9 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.error) { if (data.error) {
console.error(data.error); // console.error(data.error);
// Maybe show error to user?
// alert(data.error);
} else { } else {
document.getElementById('id_price').value = data.price; document.getElementById('id_price').value = data.price;
document.getElementById('id_distance_km').value = data.distance_km; document.getElementById('id_distance_km').value = data.distance_km;
@ -404,15 +293,11 @@
} }
// Attach listener to weight // Attach listener to weight
const weightInput = document.getElementById('id_weight'); document.getElementById('id_weight').addEventListener('change', calculatePrice);
if (weightInput) { document.getElementById('id_weight').addEventListener('keyup', calculatePrice);
weightInput.addEventListener('change', calculatePrice);
weightInput.addEventListener('keyup', calculatePrice);
}
window.initMap = initMap; window.initMap = initMap;
</script> </script>
<script src="https://maps.googleapis.com/maps/api/js?key={{ google_maps_api_key }}&loading=async&callback=initMap" async defer></script>
{% endif %} {% endif %}
<script> <script>
@ -466,4 +351,4 @@ document.addEventListener('DOMContentLoaded', function() {
setupDependentDropdowns('id_delivery_country', 'id_delivery_governate', 'id_delivery_city'); setupDependentDropdowns('id_delivery_country', 'id_delivery_governate', 'id_delivery_city');
}); });
</script> </script>
{% endblock %} {% endblock %}