344 lines
11 KiB
Vue
344 lines
11 KiB
Vue
<template>
|
|
<div class="col-12">
|
|
<div id="map" />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import axios from "axios";
|
|
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
|
|
import route from "./../../modules/route";
|
|
import toast from "./../../modules/toast";
|
|
import trans from "./../../modules/trans";
|
|
import L from "leaflet";
|
|
import { defineComponent } from "vue";
|
|
|
|
|
|
export default defineComponent({
|
|
mixins: [route, trans, toast, parseLaravelErrorMessages],
|
|
props: {
|
|
signsStickers: {
|
|
type: Array,
|
|
default: null,
|
|
},
|
|
signsLabels: {
|
|
type: Array,
|
|
default: null,
|
|
},
|
|
formId: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
},
|
|
data(): {
|
|
formAddress: HTMLFormElement;
|
|
addressInput: string;
|
|
address: string;
|
|
fullAddress: Array<string>;
|
|
searchedAddress: any;
|
|
shops: Array<{
|
|
address: string;
|
|
city: string;
|
|
distance: number;
|
|
latitude: number;
|
|
longitude: number;
|
|
name: string;
|
|
postcode: number;
|
|
signs: any;
|
|
}>;
|
|
userPos: { latitude?: number; longitude?: number };
|
|
map: any;
|
|
centerPos: { latitude?: number; longitude?: number };
|
|
stickers: Record<string, string>;
|
|
labels: Array<string>;
|
|
loading: boolean;
|
|
currentPage: number;
|
|
} {
|
|
return {
|
|
formAddress: document.getElementById(this.formId) as HTMLFormElement,
|
|
addressInput: "",
|
|
address: "",
|
|
fullAddress: [],
|
|
searchedAddress: null,
|
|
userPos: {},
|
|
map: null,
|
|
centerPos: {},
|
|
shops: [],
|
|
stickers: {},
|
|
labels: [],
|
|
loading: false,
|
|
currentPage: 1,
|
|
};
|
|
},
|
|
mounted() {
|
|
const json = String(this.$attrs.json ?? "{}");
|
|
if (json.length) {
|
|
const data = JSON.parse(json);
|
|
if (data.signsStickers) {
|
|
this.stickers = data.signsStickers;
|
|
}
|
|
if (data.signsLabels) {
|
|
this.labels = data.signsLabels;
|
|
}
|
|
if (data.formId) {
|
|
this.formAddress = document.getElementById(
|
|
data.formId
|
|
) as HTMLFormElement;
|
|
this.formAddress?.addEventListener("submit", this.searchAddress);
|
|
}
|
|
}
|
|
const getAllShopsRoute = this.route("shops.index");
|
|
if (!getAllShopsRoute) {
|
|
throw Error("Undefined route 'shops.index'");
|
|
}
|
|
this.loading = true;
|
|
axios
|
|
.get(getAllShopsRoute)
|
|
.then((response) => {
|
|
this.shops = response.data;
|
|
this.initMap();
|
|
})
|
|
.then(() => (this.loading = false))
|
|
.catch((e) => {
|
|
console.log(e);
|
|
let message = this.__("http-statuses.0");
|
|
if (e.response.status === 422) {
|
|
message =
|
|
this.parseValidationErrors(e.response?.data?.errors ?? {}) ||
|
|
message;
|
|
if (window.vueDebug) {
|
|
console.warn(e.response.data.errors);
|
|
}
|
|
} else if (e.response.status === 419) {
|
|
// * CSRF TOKEN MISMATCH (On ne pourrais plus faire d'appels AJAX sans recharger la page)
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else if (e.response.status === 403) {
|
|
// * Access denied
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else {
|
|
if (window.vueDebug) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
this.errorNotify(this.__(`http-statuses.${e.response.status}`));
|
|
this.errorNotify(message);
|
|
});
|
|
},
|
|
methods: {
|
|
searchAddress(event?: Event) {
|
|
event?.preventDefault();
|
|
const formInput = this.formAddress.firstElementChild as HTMLInputElement;
|
|
if (formInput.value) {
|
|
this.address = formInput.value.split(" ").join("+");
|
|
}
|
|
var instance = axios.create();
|
|
delete instance.defaults.headers.common["X-CSRF-TOKEN"];
|
|
|
|
instance
|
|
.get("https://api-adresse.data.gouv.fr/search/?q=" + this.address)
|
|
.then((response) => {
|
|
this.userPos = {};
|
|
this.searchedAddress = response.data.features[0];
|
|
this.calcPosition();
|
|
})
|
|
.then(() => (this.loading = false))
|
|
.catch((e) => {
|
|
console.log(e);
|
|
let message = this.__("http-statuses.0");
|
|
if (e.response.status === 422) {
|
|
message =
|
|
this.parseValidationErrors(e.response?.data?.errors ?? {}) ||
|
|
message;
|
|
if (window.vueDebug) {
|
|
console.warn(e.response.data.errors);
|
|
}
|
|
} else if (e.response.status === 419) {
|
|
// * CSRF TOKEN MISMATCH (On ne pourrais plus faire d'appels AJAX sans recharger la page)
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else if (e.response.status === 403) {
|
|
// * Access denied
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else {
|
|
if (window.vueDebug) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
this.errorNotify(this.__(`http-statuses.${e.response.status}`));
|
|
this.errorNotify(message);
|
|
});
|
|
},
|
|
findNearest(userLat: number, userLon: number) {
|
|
const getShopsRoute = this.route("shops.nearest", {
|
|
latitude: userLat,
|
|
longitude: userLon
|
|
});
|
|
if (!getShopsRoute) {
|
|
throw Error("Undefined route 'shops.nearest'");
|
|
}
|
|
this.loading = true;
|
|
axios
|
|
.get(getShopsRoute)
|
|
.then((response) => {
|
|
this.shops = response.data;
|
|
this.displayShopsList();
|
|
this.displayMarkers(userLat, userLon);
|
|
})
|
|
.then(() => (this.loading = false))
|
|
.catch((e) => {
|
|
console.log(e);
|
|
let message = this.__("http-statuses.0");
|
|
if (e.response.status === 422) {
|
|
message =
|
|
this.parseValidationErrors(e.response?.data?.errors ?? {}) ||
|
|
message;
|
|
if (window.vueDebug) {
|
|
console.warn(e.response.data.errors);
|
|
}
|
|
} else if (e.response.status === 419) {
|
|
// * CSRF TOKEN MISMATCH (On ne pourrais plus faire d'appels AJAX sans recharger la page)
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else if (e.response.status === 403) {
|
|
// * Access denied
|
|
setTimeout(() => window.location.reload(), 2000);
|
|
} else {
|
|
if (window.vueDebug) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
this.errorNotify(this.__(`http-statuses.${e.response.status}`));
|
|
this.errorNotify(message);
|
|
});
|
|
},
|
|
setPosition() {
|
|
this.userPos = {};
|
|
this.searchAddress();
|
|
},
|
|
calcPosition() {
|
|
let userLon = null;
|
|
let userLat = null;
|
|
if (this.userPos.latitude == null) {
|
|
userLon = this.searchedAddress.geometry.coordinates[0];
|
|
userLat = this.searchedAddress.geometry.coordinates[1];
|
|
} else {
|
|
userLon = this.userPos.longitude;
|
|
userLat = this.userPos.latitude;
|
|
}
|
|
this.findNearest(userLat, userLon);
|
|
},
|
|
setCurrentPosition() {
|
|
navigator.geolocation.getCurrentPosition(this.savePosition);
|
|
},
|
|
savePosition(pos: any) {
|
|
this.userPos = pos.coords;
|
|
this.calcPosition();
|
|
},
|
|
displayMarkers(userLat: number, userLon: number) {
|
|
let map = this.map;
|
|
map.eachLayer(function (layer: L.Layer) {
|
|
map.removeLayer(layer);
|
|
});
|
|
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
maxZoom: 16,
|
|
attribution: "© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>"
|
|
}).addTo(this.map);
|
|
this.map.setView([userLat, userLon], 8);
|
|
for (let shop of this.shops) {
|
|
L.marker([shop.latitude, shop.longitude])
|
|
.bindPopup(shop.name)
|
|
.addTo(map);
|
|
}
|
|
},
|
|
defineZoom(object: any) {
|
|
this.centerPos.latitude = object.latitude;
|
|
this.centerPos.longitude = object.longitude;
|
|
return this.zoom();
|
|
},
|
|
zoom() {
|
|
this.map.setView([this.centerPos.latitude, this.centerPos.longitude], 13);
|
|
},
|
|
// Initialize map with markers for all shops in database
|
|
initMap() {
|
|
// View set with coords and zoom for France.
|
|
this.map = L.map("map", {attributionControl: false}).setView([47, 2], 6);
|
|
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
maxZoom: 16,
|
|
attribution: "© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>"
|
|
}).addTo(this.map);
|
|
// Remove Ukrainian flag.
|
|
const myAttrControl = L.control.attribution().addTo(this.map);
|
|
myAttrControl.setPrefix("<a href=\"https://leafletjs.com/\">Leaflet</a>");
|
|
for (let shop of this.shops) {
|
|
L.marker([shop.latitude, shop.longitude])
|
|
.bindPopup(shop.name)
|
|
.addTo(this.map);
|
|
}
|
|
},
|
|
displayShopsList() {
|
|
const list = document.getElementById("shops-list") as HTMLElement;
|
|
const baseItem = document.getElementById("shop-list-item") as HTMLElement;
|
|
|
|
list.replaceChildren(baseItem);
|
|
let i = 1;
|
|
for (let shop of this.shops) {
|
|
let shopListElement = baseItem?.cloneNode(true) as HTMLElement;
|
|
// Set the shop sign logo
|
|
const imgTag = shopListElement.childNodes[1]
|
|
.childNodes[1] as HTMLElement;
|
|
imgTag.setAttribute("src", this.stickers[shop.signs]);
|
|
imgTag.setAttribute("alt", "Logo " + this.labels[shop.signs]);
|
|
// Display the name and address
|
|
shopListElement.childNodes[1].childNodes[3].childNodes[1].textContent =
|
|
shop.name;
|
|
shopListElement.childNodes[1].childNodes[3].childNodes[3].textContent =
|
|
shop.address + ", " + shop.postcode + " " + shop.city;
|
|
// Display the distance
|
|
let unit = " km";
|
|
if (shop.distance === undefined) {
|
|
unit = " mètres";
|
|
shop.distance = shop.distance * 1000;
|
|
}
|
|
shopListElement.childNodes[1].childNodes[3].childNodes[5].textContent =
|
|
shop.distance?.toString() + unit;
|
|
// Link to get the journey on Google Maps
|
|
let aTag = shopListElement.childNodes[3] as HTMLElement;
|
|
aTag.setAttribute(
|
|
"href",
|
|
"https://www.google.com/maps/dir/?api=1&origin=" +
|
|
this.searchedAddress.geometry.coordinates[1] +
|
|
"," +
|
|
this.searchedAddress.geometry.coordinates[0] +
|
|
"&destination=" +
|
|
shop.latitude +
|
|
"," +
|
|
shop.longitude
|
|
);
|
|
aTag.setAttribute("target", "_blank");
|
|
|
|
shopListElement.classList.remove("d-none");
|
|
shopListElement.setAttribute("id", "shop-list-item-" + i);
|
|
shopListElement.addEventListener("click", () => {
|
|
this.defineZoom(shop);
|
|
});
|
|
list.appendChild(shopListElement);
|
|
i++;
|
|
}
|
|
},
|
|
},
|
|
watch: {
|
|
addressInput() {
|
|
let addressInputField = document.getElementById(
|
|
"address"
|
|
) as HTMLInputElement;
|
|
this.addressInput = addressInputField.value;
|
|
this.fullAddress = this.addressInput.split(",");
|
|
this.address = this.fullAddress[0].split(" ").join("+");
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
#map {
|
|
height: 500px;
|
|
}
|
|
</style>
|