You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xshop/resources/js/components/AddressInput.vue

425 lines
13 KiB
Vue

<template>
<div id="address-input">
<table class="w-100">
<tr v-for="ad in addresses">
<td>
{{ ad.address }}
</td>
<td class="py-1">
<div class="btn btn-outline-danger btn-sm float-end mx-2" @click="removing(ad.id)">
<i class="ri-close-line"></i>
</div>
<div class="btn btn-outline-primary btn-sm float-end" @click="editing(ad)">
<i class="ri-edit-2-line"></i>
</div>
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @click="adding">
<i class="ri-add-line"></i>
</button>
<div id="address-modal" v-if="modal" @click.self="modal = false">
<div class="card">
<div class="card-header">
Address editor <!-- WIP Translate -->
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label for="st">
State: <!-- WIP Translate -->
</label>
<select @change="updateState" class="form-control" v-model="state_id" id="st">
<option :data-lat="s.lat" :data-lng="s.lng" :value="s.id" v-for="s in states">
{{ s.name }}
</option>
</select>
</div>
<div class="col-md-6">
<label for="st">
City: <!-- WIP Translate -->
</label>
<select @change="updateCity" class="form-control" v-model="city_id" id="st">
<option :value="c.id" v-for="c in cities"> {{ c.name }}</option>
</select>
</div>
<div class="col-12 my-3">
<div ref="mapContainer" :style="'height: 300px;'+mapStyle"></div>
</div>
<div class="col-12">
<textarea rows="2" class="form-control" placeholder="Address" v-model="address"></textarea>
<!-- WIP Translate -->
</div>
<div class="col-12">
<label for="zip">
Postcode: <!-- WIP Translate -->
</label>
<input type="text" class="form-control" v-model="zip">
</div>
</div>
</div>
<div class="card-footer">
<button class="btn btn-primary w-100" type="button" @click="save">
<i class="ri-save-2-line"></i>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import {useToast} from 'vue-toast-notification';
const $toast = useToast();
export default {
name: "address-input",
components: {},
data: () => {
return {
id: null,
action: 'add',
modal: false,
addresses: [],
states: [],
cities: [],
state_id: null,
city_id: null,
map: null,
marker: null,
zoom: 10,
address: '',
zip: '',
lat: null,
lng: null,
}
},
props: {
listLink: {
type: String,
required: true,
},
addLink: {
type: String,
required: true,
},
updateLink: {
type: String,
required: true,
},
remLink: {
type: String,
required: true,
},
stateLink: {
type: String,
required: true,
},
citiesLink: {
type: String,
required: true,
},
darkMode: {
type: Boolean,
default: false,
}
},
async mounted() {
try {
let res = await axios.get(this.stateLink);
this.states = res.data.data;
// console.log(res.data);
} catch (e) {
$toast.error(e.message);
}
await this.updateList();
// await this.initMap();
// if (this.states[0].lat != null && this.states[0].lng != null){
// this.changeMapCenter(this.states[0].lat,this.states[0].lng)
// }
},
computed: {
mapStyle() {
if (this.darkMode) {
return 'filter: invert(100%) hue-rotate(120deg) brightness(95%) contrast(90%);';
}
return '';
}
},
methods: {
async save() {
let canSave = true;
if (this.state_id == null) {
$toast.error("State is required"); // WIP translate
canSave = false;
}
if (this.city_id == null) {
$toast.error("City is required"); // WIP translate
canSave = false;
}
if (this.address.length < 10) {
$toast.error("Address is required"); // WIP translate
canSave = false;
}
if (this.zip.length < 5) {
$toast.error("Post code is required"); // WIP translate
canSave = false;
}
if (!canSave) {
return false;
}
if (this.action == 'add') {
let data = {
address: this.address,
state_id: this.state_id,
city_id: this.city_id,
zip: this.zip,
lat: this.lat,
lng: this.lng
};
try {
let r = await axios.post(this.addLink, data);
if (r.data.OK) {
this.addresses = r.data.list;
$toast.success(r.data.message);
this.modal = false;
}
} catch (e) {
$toast.error('err!' + e.message);
}
} else {
let data = {
address: this.address,
state_id: this.state_id,
city_id: this.city_id,
zip: this.zip,
lat: this.lat,
lng: this.lng
};
try {
const url = this.updateLink + '/' + this.id;
let r = await axios.post(url, data);
if (r.data.OK) {
$toast.success(r.data.message);
await this.updateList();
this.modal = false;
}
} catch (e) {
$toast.error('err!' + e.message);
}
}
},
showModal() {
this.modal = true;
setTimeout(() => {
this.initMap();
}, 50);
},
async removing(id) {
if (!confirm('Sure?')) { //WIP: translate
return;
}
const url = this.remLink + '/' + id;
try {
let r = await axios.get(url);
if (r.data.OK) {
$toast.success(r.data.message);
this.updateList();
}
} catch(e) {
$toast.error('err!' + e.message);
}
},
async editing(dt) {
this.showModal();
this.action = 'edit';
this.id = dt.id;
this.lat = dt.lat;
this.lng = dt.lng;
this.zip = dt.zip;
this.address = dt.address;
this.state_id = dt.state_id;
await this.updateState();
this.city_id = dt.city_id;
if (this.lng != null && this.lat != null) {
this.zoom = 16;
setTimeout(() => {
this.changeMapCenter(this.lat, this.lng);
this.marker = L.marker({lat:this.lat, lng: this.lng}).addTo(this.map);
}, 100);
}
},
adding() {
this.action = 'add';
this.address = '';
this.zip = '';
this.state_id = null;
this.showModal();
},
initMap() {
this.map = L.map(this.$refs.mapContainer).setView([35.83266000, 50.99155000], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; openstreetmap',
attributionControl: false,
}).addTo(this.map);
this.map.on('click', this.onMapClick);
this.map.attributionControl.setPrefix('xShop');
},
onMapClick(e) {
if (this.marker) {
this.map.removeLayer(this.marker);
}
this.marker = L.marker(e.latlng).addTo(this.map);
// You can emit the selected location or perform any other desired action here
// console.log('Selected location:', e.latlng);
this.getAddress(e.latlng);
this.lat = e.latlng.lat;
this.lng = e.latlng.lng;
},
changeMapCenter(lat, lng) {
try {
this.map.setView([lat, lng], this.zoom);
} catch (e) {
// console.log(e.message);
setTimeout(() => {
console.log('repeat');
this.changeMapCenter(lat, lng);
}, 10);
}
// Change the map center to [40.7128, -74.0059] (New York City) with zoom level 12
},
async updateList() {
try {
let res = await axios.get(this.listLink);
this.addresses = res.data;
} catch (e) {
$toast.error('err!' + e.message);
}
},
async updateState() {
for (const st of this.states) {
if (st.id == this.state_id) {
// console.log(st);
if (st.lat != null && st.lng != null) {
this.zoom = 10;
this.changeMapCenter(st.lat, st.lng)
}
break;
}
}
try {
let res = await axios.get(this.citiesLink + '/' + this.state_id);
this.cities = res.data.data;
} catch (e) {
$toast.error('err!' + e.message);
}
},
async updateCity() {
for (const c of this.cities) {
if (c.id == this.city_id) {
if (c.lat != null && c.lng != null) {
this.zoom = 12;
this.changeMapCenter(c.lat, c.lng)
}
break;
}
}
},
getAddress(latlng) {
const url = `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${latlng.lat}&lon=${latlng.lng}&addressdetails=1&accept-language=en`;
fetch(url)
.then(response => response.json())
.then(data => {
const address = this.formatAddress(data.address);
this.address = address;
})
.catch(error => {
$toast.error('err!' + error.message);
});
},
formatAddress(addressData) {
let formattedAddress = '';
if (addressData.road) {
formattedAddress += addressData.road;
}
if (addressData.neighbourhood) {
formattedAddress += addressData.neighbourhood;
}
//
// if (addressData.house_number) {
// formattedAddress += ` ${addressData.house_number}`;
// }
//
if (addressData.postcode) {
// formattedAddress += `, ${addressData.postcode}`;
let x = addressData.postcode.split('-');
this.zip = x.join('');
}
if (addressData.city) {
formattedAddress += `, ${addressData.city}`;
}
//
// if (addressData.country) {
// formattedAddress += `, ${addressData.country}`;
// }
return formattedAddress;
},
}
}
</script>
<style scoped>
#address-input {
padding: 0 .75rem 1rem;
}
#address-modal {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
backdrop-filter: blur(4px);
background: #ffffff33;
z-index: 9;
display: flex;
align-items: center;
justify-content: center;
}
#address-modal .card {
max-width: 900px;
width: 100%;
}
</style>