Added check if request wants JSON and replaced fetch requests with axios

This commit is contained in:
Noah Lancien 2023-06-15 12:08:37 +02:00
parent e74993964e
commit 2488198f7c
5 changed files with 376 additions and 231 deletions

View file

@ -3,8 +3,8 @@
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Shop\GetNearestShopRequest;
use App\Models\Shop;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ShopController extends Controller
@ -15,28 +15,35 @@ class ShopController extends Controller
* Get all shops
* @return json
*/
public function getAll()
public function index()
{
return response()
->json(Shop::all());
if (request()->wantsJson()) {
return response()
->json(Shop::all());
}
return abort(404);
}
/**
* Get the 5 nearest shops with the given coords in the request.
* @param Request $request
* @param App\Http\Requests\Fo\Shop\GetNearestShopRequest $request
* @return json
*/
public function getNearest(Request $request)
public function getNearest(GetNearestShopRequest $request)
{
$latitude = $request->input('latitude');
$longitude = $request->input('longitude');
$latitude = $request->latitude;
$longitude = $request->longitude;
$raw = DB::raw(
'ROUND ((6371*acos(cos(radians(' . $latitude . '))*cos(radians(latitude))*cos(radians(longitude)
- radians(' . $longitude . '))+sin(radians(' . $latitude . '))*sin(radians(latitude)))), 2)
AS distance'
);
$results = DB::table("shops")->select('*')->addSelect($raw)->orderBy('distance', 'ASC')->limit(5)->get();
return response()
->json($results);
if ($request->wantsJson()) {
return response()
->json($results);
}
return abort(404);
}
}

View file

@ -1,234 +1,369 @@
<template>
<div class="col-12">
<div id="map" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import * as L from "leaflet";
<div class="col-12">
<div id="map" />
</div>
</template>
export default defineComponent({
props: {
signsStickers: {
default: null
},
signsLabels: {
default: null
},
formId: {
type: String,
default: null
},
<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 * as L from "leaflet";
import { defineComponent } from "vue";
export default defineComponent({
mixins: [route, trans, toast, parseLaravelErrorMessages],
props: {
signsStickers: {
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: Array<string>,
labels: Array<string>,
} {
return {
formAddress: document.getElementById(this.formId) as HTMLFormElement,
addressInput: "",
address: "",
fullAddress: [],
searchedAddress: null,
userPos: {},
map: null,
centerPos: {},
shops: [],
stickers: [],
labels: [],
};
signsLabels: {
default: null,
},
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);
}
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: Array<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;
}
fetch("/api/shops")
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) => {
return response.json().then((data) => {
this.shops = data;
this.initMap();
return data;
}).catch((err) => {
console.log(err);
});
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);
});
// fetch("https://api-adresse.data.gouv.fr/search/?q=" + this.address).then(
// (response) => {
// return response
// .json()
// .then((data) => {
// this.userPos = {};
// this.searchedAddress = data.features[0];
// this.calcPosition();
// return data;
// })
// .catch((err) => {
// console.log(err);
// });
// }
// );
},
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);
});
},
methods: {
searchAddress(event?: Event) {
event?.preventDefault();
const formInput = this.formAddress.firstElementChild as HTMLInputElement;
if (formInput.value) {
this.address = formInput.value.split(" ").join("+");
}
fetch("https://api-adresse.data.gouv.fr/search/?q=" + this.address)
.then((response) => {
return response.json().then((data) => {
this.userPos = {};
this.searchedAddress = data.features[0];
this.calcPosition();
return data;
}).catch((err) => {
console.log(err);
});
});
},
findNearest(userLat: number, userLon: number) {
fetch("/api/shops/search?latitude=" + userLat + "&longitude=" + userLon)
.then((response) => {
return response.json().then((data) => {
this.shops = data;
this.displayShopsList();
this.displayMarkers(userLat, userLon);
return data;
}).catch((err) => {
console.log(err);
});
});
},
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://{s}.tile.jawg.io/jawg-sunny/{z}/{x}/{y}{r}.png?access-token={accessToken}", {
attribution: "<a href='http://jawg.io' title='Tiles Courtesy of Jawg Maps' target='_blank'>&copy; <b>Jawg</b>Maps</a> &copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
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://{s}.tile.jawg.io/jawg-sunny/{z}/{x}/{y}{r}.png?access-token={accessToken}",
{
attribution:
"<a href='http://jawg.io' title='Tiles Courtesy of Jawg Maps' target='_blank'>&copy; <b>Jawg</b>Maps</a> &copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
minZoom: 0,
maxZoom: 22,
subdomains: "abcd",
accessToken: "K1juMdkCgPLIjLt2KgXkeMDxenwtl9ZJqIZmNDbLoNU9P672uoA9ffNAty6UcoQU"
}).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").setView([47, 2], 6);
L.tileLayer("https://{s}.tile.jawg.io/jawg-sunny/{z}/{x}/{y}{r}.png?access-token={accessToken}", {
attribution: "<a href='http://jawg.io' title='Tiles Courtesy of Jawg Maps' target='_blank'>&copy; <b>Jawg</b>Maps</a> &copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
minZoom: 0,
maxZoom: 22,
accessToken: "K1juMdkCgPLIjLt2KgXkeMDxenwtl9ZJqIZmNDbLoNU9P672uoA9ffNAty6UcoQU"
}).addTo(this.map);
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 < 1) {
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', (event: Event) => {
this.defineZoom(shop)
})
list.appendChild(shopListElement);
i++;
accessToken:
"K1juMdkCgPLIjLt2KgXkeMDxenwtl9ZJqIZmNDbLoNU9P672uoA9ffNAty6UcoQU",
}
).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);
}
},
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("+");
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").setView([47, 2], 6);
L.tileLayer(
"https://{s}.tile.jawg.io/jawg-sunny/{z}/{x}/{y}{r}.png?access-token={accessToken}",
{
attribution:
"<a href='http://jawg.io' title='Tiles Courtesy of Jawg Maps' target='_blank'>&copy; <b>Jawg</b>Maps</a> &copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
minZoom: 0,
maxZoom: 22,
accessToken:
"K1juMdkCgPLIjLt2KgXkeMDxenwtl9ZJqIZmNDbLoNU9P672uoA9ffNAty6UcoQU",
}
).addTo(this.map);
for (let shop of this.shops) {
L.marker([shop.latitude, shop.longitude])
.bindPopup(shop.name)
.addTo(this.map);
}
}
});
</script>
<style>
#map {
height: 500px;
}
</style>
},
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 < 1) {
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", (event: Event) => {
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>

View file

@ -20,6 +20,7 @@
@stack('scriptsConstants')
@include('modules.scriptConstants')
@include('modules.scriptRoutes')
<script src="{{ mix('js/fo.js', 'build') }}"></script>
@stack('scripts')

View file

@ -3,4 +3,6 @@
if (!window.__SYSTEM._routes) {
window.__SYSTEM._routes = {};
}
window.__SYSTEM._routes['shops.index'] = '{{ route('shops.index') }}';
window.__SYSTEM._routes['shops.nearest'] = '{{ route('shops.nearest') }}';
</script>

View file

@ -47,5 +47,5 @@ Route::view('/politique-de-donnees-personnelles', 'fo.pages.policies')->name('po
Route::view('/mentions-legales', 'fo.pages.legals')->name('legals');
// * ROUTES TO GET SHOPS FOR MARKET FINDER
Route::get('/api/shops', [ShopController::class, 'getAll']);
Route::get('/api/shops/search', [ShopController::class, 'getNearest']);
Route::get('/api/shops', [ShopController::class, 'index'])->name('shops.index');
Route::get('/api/shops/search', [ShopController::class, 'getNearest'])->name('shops.nearest');