801 lines
27 KiB
Vue

<template>
<!-- step0 -->
<show-pane v-if="flag.showPane"
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
v-bind:useDatePane="this.useDatePane"
@openEditPane="openEditPane"
ref="showAddress">
</show-pane>
<!-- step 1 -->
<teleport to="body" v-if="inModal">
<modal v-if="flag.suggestPane"
modalDialogClass="modal-dialog-scrollable modal-xl"
@close="resetPane">
<template v-slot:header>
<h2 class="modal-title">{{ $t(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
<span class="sr-only">{{ $t('loading') }}</span>
</span>
</h2>
</template>
<template v-slot:body>
<suggest-pane
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
@pick-address="this.pickAddress"
ref="suggestAddress">
</suggest-pane>
</template>
<template v-slot:footer>
<button @click="openEditPane"
class="btn btn-create">
{{ $t('create_a_new_address')}}
</button>
</template>
</modal>
</teleport>
<div class="mt-4" v-else>
<suggest-pane v-if="flag.suggestPane"
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
v-bind:insideModal="false"
@pick-address="this.pickAddress"
ref="suggestAddress">
<template v-slot:before v-if="!bypassFirstStep">
<a class="btn btn-cancel" @click="resetPane">
{{ $t('action.cancel') }}
</a>
</template>
<template v-slot:action>
<li>
<button @click="openEditPane"
class="btn btn-create">
{{ $t('create_a_new_address')}}
</button>
</li>
</template>
</suggest-pane>
</div>
<!-- step 2 -->
<teleport to="body" v-if="inModal">
<modal v-if="flag.editPane"
modalDialogClass="modal-dialog-scrollable modal-xl"
@close="resetPane">
<template v-slot:header>
<h2 class="modal-title">{{ $t(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
<span class="sr-only">{{ $t('loading') }}</span>
</span>
</h2>
</template>
<template v-slot:body>
<edit-pane
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
v-bind:errors="this.errors"
v-bind:checkErrors="this.checkErrors"
@getCities="getCities"
@getReferenceAddresses="getReferenceAddresses">
</edit-pane>
</template>
<template v-slot:footer>
<!--<button class="btn btn-cancel change-icon" @click="resetPane">{{ $t('action.cancel') }}</button>-->
<button v-if="!this.context.edit && this.useDatePane" class="btn btn-update change-icon" @click="closeEditPane">
{{ $t('nav.next')}}
<i class="fa fa-fw fa-arrow-right"></i>
</button>
<button v-else class="btn btn-save" @click="closeEditPane">
{{ $t('action.save')}}
</button>
</template>
</modal>
</teleport>
<div class="mt-4" v-else>
<edit-pane v-if="flag.editPane"
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
v-bind:errors="this.errors"
v-bind:checkErrors="this.checkErrors"
v-bind:insideModal="false"
@getCities="getCities"
@getReferenceAddresses="getReferenceAddresses">
<template v-slot:before>
<a class="btn btn-cancel" @click="resetPane">
{{ $t('action.cancel') }}
</a>
</template>
<template v-slot:action>
<li v-if="!this.context.edit && this.useDatePane">
<button class="btn btn-update change-icon" @click="closeEditPane">
{{ $t('nav.next')}}
<i class="fa fa-fw fa-arrow-right"></i>
</button>
</li>
<li v-else>
<button class="btn btn-save" @click="closeEditPane">
{{ $t('action.save')}}
</button>
</li>
</template>
</edit-pane>
</div>
<!-- step 3 -->
<teleport to="body" v-if="inModal">
<modal v-if="flag.datePane"
modalDialogClass="modal-dialog-scrollable modal-xl"
@close="resetPane">
<template v-slot:header>
<h2 class="modal-title">{{ $t(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
<span class="sr-only">{{ $t('loading') }}</span>
</span>
</h2>
</template>
<template v-slot:body>
<date-pane
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
ref="dateAddress">
</date-pane>
</template>
<template v-slot:footer>
<button class="btn btn-misc" @click="openEditPane">
<i class="fa fa-fw fa-arrow-left"></i>
{{ $t('nav.previous')}}
</button>
<button class="btn btn-save" @click="closeDatePane">
{{ $t('action.save')}}
</button>
<!-- -->
</template>
</modal>
</teleport>
<div class="mt-4" v-else>
<date-pane v-if="flag.datePane"
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
v-bind:insideModal="false"
ref="dateAddress">
<template v-slot:before>
<button class="btn btn-misc" @click="openEditPane">
<i class="fa fa-fw fa-arrow-left"></i>
{{ $t('nav.previous')}}
</button>
</template>
<template v-slot:action>
<li>
<button class="btn btn-save" @click="closeDatePane">
{{ $t('action.save')}}
</button>
</li>
</template>
</date-pane>
</div>
</template>
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import {
duplicateAddress,
fetchCountries,
fetchCities,
fetchReferenceAddresses,
getAddress,
patchAddress,
postAddress,
postPostalCode,
} from '../api';
import { postAddressToPerson, postAddressToHousehold } from "ChillPersonAssets/vuejs/_api/AddAddress.js";
import ShowPane from './ShowPane.vue';
import SuggestPane from './SuggestPane.vue';
import EditPane from './EditPane.vue';
import DatePane from './DatePane.vue';
export default {
name: "AddAddress",
props: ['context', 'options', 'addressChangedCallback'],
components: {
Modal,
ShowPane,
SuggestPane,
EditPane,
DatePane
},
emits: {
pickAddress: null
},
data() {
return {
flag: {
showPane: true, // begin with showPane
suggestPane: false,
editPane: false,
datePane: false,
loading: false,
success: false,
dirty: false
},
errors: [],
defaultz: {
button: {
text: { create: 'add_an_address_title', edit: 'edit_address' },
type: { create: 'btn-create', edit: 'btn-update'},
displayText: true
},
title: { create: 'add_an_address_title', edit: 'edit_address' },
openPanesInModal: true,
stickyActions: false,
// show a message when no address.
// if set to undefined, the value will be equivalent to false if stickyActions is false, true otherwise.
showMessageWhenNoAddress: undefined,
useDate: {
validFrom: false,
validTo: false
},
onlyButton: false
},
entity: {
address: {}, // <== loaded and returned
loaded: {
countries: [],
cities: [],
addresses: [],
},
selected: { // <== make temporary changes
isNoAddress: false,
country: {},
city: {},
postcode: {
code: null,
name: null
},
address: {},
writeNew: {
address: false,
postcode: false
},
valid: {
from: new Date(),
to: null
}
},
addressMap: {
// Note: LeafletJs demands [lat, lon]
// cfr https://macwright.com/lonlat/
center : [48.8589, 2.3469],
zoom: 12
},
},
errorMsg: []
}
},
computed: {
inModal() {
return (typeof this.options.openPanesInModal !== 'undefined') ?
this.options.openPanesInModal : this.defaultz.openPanesInModal;
},
validFrom() {
return (typeof this.options.useDate !== 'undefined' && typeof this.options.useDate.validFrom !== 'undefined') ?
this.options.useDate.validFrom : this.defaultz.useDate.validFrom;
},
validTo() {
return (typeof this.options.useDate !== 'undefined' && typeof this.options.useDate.validTo !== 'undefined') ?
this.options.useDate.validTo : this.defaultz.useDate.validTo ;
},
useDatePane() {
return (this.validFrom || this.validTo) ? true : false;
},
hasSuggestions() {
if (typeof(this.context.suggestions) !== 'undefined') {
console.log('hasSuggestions', this.context.suggestions);
return this.context.suggestions.length > 0;
}
return false;
},
displaySuggestions() {
return !this.context.edit && this.hasSuggestions;
},
getTextTitle() {
if ( typeof this.options.title !== 'undefined'
&& ( this.options.title.edit !== null
|| this.options.title.create !== null
)) {
return (this.context.edit) ? this.options.title.edit : this.options.title.create;
}
return (this.context.edit) ? this.defaultz.title.edit : this.defaultz.title.create;
},
bypassFirstStep() {
// exception: passing step0 if new address and pane are not in modal
return !this.context.edit && !this.inModal;
},
forceRedirect() {
return (!(this.context.backUrl === null || typeof this.context.backUrl === 'undefined'));
}
},
mounted() {
//console.log('validFrom', this.validFrom);
//console.log('validTo', this.validTo);
//console.log('useDatePane', this.useDatePane);
console.log('Mounted now !');
if (this.context.edit) {
console.log('getInitialAddress', this.context.addressId);
this.getInitialAddress(this.context.addressId);
}
this.openShowPane();
},
methods: {
/*
* Opening and closing Panes when interacting with buttons
*/
openShowPane() {
if (this.flag.editPane === false && this.bypassFirstStep) {
console.log('bypassFirstStep');
this.closeShowPane();
this.openEditPane();
} else {
this.flag.showPane = true;
console.log('step0: open the Show Panel');
}
},
closeShowPane() {
// Show pane can be closed only when openPanesInModal is false
if (!this.inModal) {
this.flag.showPane = false;
console.log('step0: close the Show Panel');
}
},
openSuggestPane() {
this.flag.suggestPane = true;
console.log('step1: open the Suggestion Panel');
},
closeSuggestPane() {
this.flag.suggestPane = false;
console.log('step1: close the Suggestion Panel');
},
openEditPane() {
if (this.flag.suggestPane === false && this.displaySuggestions) {
console.log('displaySuggestions');
this.openSuggestPane();
} else {
if (this.flag.datePane === false) {
this.initForm(); // reset form except if we come back from datePane
}
this.getCountries(); // will open editPane when resolve promise
}
},
closeEditPane() {
this.flag.editPane = false;
console.log('step2: close the Edit Panel');
if (!this.context.edit && this.useDatePane) {
this.openDatePane();
} else {
this.applyChanges();
if (!this.forceRedirect) {
this.openShowPane();
}
}
},
openDatePane() {
this.flag.datePane = true;
console.log('step3: open the Date Panel');
},
closeDatePane() {
this.applyChanges();
this.flag.datePane = false;
console.log('step3: close the Date Panel');
},
resetPane() {
console.log('resetPane');
this.flag.suggestPane = false;
this.flag.editPane = false;
this.flag.datePane = false;
this.openShowPane();
},
/*
* What happens when submitting last Pane:
* - redirect or reset pane,
* - change context to editing
*/
afterLastPaneAction(params) {
this.initForm();
if (this.forceRedirect) {
console.log("redirect to backUrl");
window.location.assign(this.context.backUrl);
} else {
console.log("don't redirect");
this.resetPane();
if (!this.context.edit) {
this.context.edit = true;
this.context.addressId = params.addressId;
console.log("context is now edit, with address", params.addressId);
}
}
},
/*
* Async Fetch datas
*/
getInitialAddress(id) {
this.flag.loading = true;
getAddress(id).then(
address => new Promise((resolve, reject) => {
this.entity.address = address;
this.flag.loading = false;
resolve();
}))
.catch((error) => {
this.errorMsg.push(error.message);
this.flag.loading = false;
});
},
getCountries() {
this.flag.loading = true;
fetchCountries().then(
countries => new Promise((resolve, reject) => {
this.entity.loaded.countries = countries.results;
if (this.flag.showPane === true) {
this.closeShowPane();
}
if (this.flag.suggestPane === true) {
this.closeSuggestPane();
}
if (this.flag.datePane === true) {
this.flag.datePane = false;
}
console.log('step2: open the Edit panel');
this.flag.editPane = true;
this.flag.loading = false;
resolve()
}))
.catch((error) => {
this.errorMsg.push(error.message);
this.flag.loading = false;
});
},
getCities(country) {
this.flag.loading = true;
fetchCities(country).then(
cities => new Promise((resolve, reject) => {
this.entity.loaded.cities = cities.results.filter(c => c.origin !== 3); // filter out user-defined cities
this.flag.loading = false;
resolve();
}))
.catch((error) => {
this.errorMsg.push(error.message);
this.flag.loading = false;
});
},
getReferenceAddresses(city) {
this.flag.loading = true;
fetchReferenceAddresses(city).then(
addresses => new Promise((resolve, reject) => {
this.entity.loaded.addresses = addresses.results;
this.flag.loading = false;
resolve();
}))
.catch((error) => {
this.errorMsg.push(error.message);
this.flag.loading = false;
});
},
checkErrors() {
this.errors = [];
if (this.entity.selected.country === null) {
this.errors.push("Un pays doit être sélectionné.");
}
if (this.entity.selected.city === null) {
this.errors.push("Une ville doit être sélectionnée.");
}
},
/*
* Make form ready for new changes
*/
initForm() {
console.log('init form');
this.entity.loaded.addresses = [];
this.entity.loaded.cities = [];
this.entity.loaded.countries = [];
this.entity.selected.isNoAddress = (this.context.edit && this.entity.address.text === '') ? true : false;
this.entity.selected.country = this.context.edit ? this.entity.address.country : {};
this.entity.selected.postcode = this.context.edit ? this.entity.address.postcode : {};
this.entity.selected.city = {};
this.entity.selected.address = {};
this.entity.selected.address.street = this.context.edit ? this.entity.address.street: null;
this.entity.selected.address.streetNumber = this.context.edit ? this.entity.address.streetNumber: null;
this.entity.selected.address.floor = this.context.edit ? this.entity.address.floor: null;
this.entity.selected.address.corridor = this.context.edit ? this.entity.address.corridor: null;
this.entity.selected.address.steps = this.context.edit ? this.entity.address.steps: null;
this.entity.selected.address.flat = this.context.edit ? this.entity.address.flat: null;
this.entity.selected.address.buildingName = this.context.edit ? this.entity.address.buildingName: null;
this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: null;
this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: null;
this.entity.selected.writeNew.address = this.context.edit && this.entity.address.addressReference === null && this.entity.address.street.length > 0
this.entity.selected.writeNew.postcode = false // NB: this used to be this.context.edit, but think it was erroneous;
console.log('!! just set writeNew.postcode to', this.entity.selected.writeNew.postcode);
},
/*
* When changes are validated (get out step2 edit pane, button valid),
* apply some transformations before asyncing with backend
* from entity.selected to entity.address
*/
applyChanges()
{
console.log('apply changes');
let newAddress = {
'isNoAddress': this.entity.selected.isNoAddress,
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
'streetNumber': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.streetNumber,
'postcode': {'id': this.entity.selected.city.id },
'floor': this.entity.selected.address.floor,
'corridor': this.entity.selected.address.corridor,
'steps': this.entity.selected.address.steps,
'flat': this.entity.selected.address.flat,
'buildingName': this.entity.selected.address.buildingName,
'distribution': this.entity.selected.address.distribution,
'extra': this.entity.selected.address.extra
};
if (this.entity.selected.address.point !== undefined) {
newAddress = Object.assign(newAddress, {
'point': this.entity.selected.address.point.coordinates
});
} else {
if (this.entity.selected.postcode.coordinates) {
newAddress = Object.assign(newAddress, {
'point': this.entity.selected.postcode.coordinates
});
}
}
// add the address reference, if any
if (this.entity.selected.address.addressReference !== undefined) {
newAddress = Object.assign(newAddress, {
'addressReference': this.entity.selected.address.addressReference
});
}
if (this.validFrom) {
console.log('add validFrom in fetch body', this.entity.selected.valid.from);
newAddress = Object.assign(newAddress, {
'validFrom': {
datetime: `${this.entity.selected.valid.from.toISOString().split('T')[0]}T00:00:00+0100`
}
});
}
if (this.validTo && null !== this.entity.selected.valid.to) {
console.log('add validTo in fetch body', this.entity.selected.valid.to);
newAddress = Object.assign(newAddress, {
'validTo': {
datetime: `${this.entity.selected.valid.to.toISOString().split('T')[0]}T00:00:00+0100`
}
});
}
if (this.entity.selected.writeNew.postcode) {
let newPostcode = this.entity.selected.postcode;
newPostcode = Object.assign(newPostcode, {
'country': {'id': this.entity.selected.country.id },
});//TODO why not assign postcodeBody here = Object.assign(postcodeBody, {'origin': 3}); ?
console.log('writeNew postcode is true! newPostcode: ', newPostcode);
newAddress = Object.assign(newAddress, {
'newPostcode': newPostcode
});
}
if (!this.context.edit) {
this.addNewAddress(newAddress)
.then(payload => this.addressChangedCallback(payload));
} else {
this.updateAddress({
addressId: this.context.addressId,
newAddress: newAddress
})
.then(payload => this.addressChangedCallback(payload));
}
},
/*
* Async POST transactions,
* creating new address, and receive backend datas when promise is resolved
*/
addNewAddress(payload)
{
console.log('addNewAddress', payload);
this.flag.loading = true;
if ('newPostcode' in payload) {
let postcodeBody = payload.newPostcode;
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
console.log('juste before post new postcode', postcodeBody);
return postPostalCode(postcodeBody)
.then(postalCode => {
console.log('new postcode created', postalCode.id);
payload.postcode = {'id': postalCode.id };
return this.postNewAddress(payload);
});
} else {
return this.postNewAddress(payload);
}
},
postNewAddress(payload)
{
console.log('postNewAddress', payload);
return postAddress(payload)
.then(address => new Promise((resolve, reject) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;
resolve({
address,
targetOrigin: this.context.target,
// for "legacy" use:
target: this.context.target.name,
targetId: this.context.target.id,
addressId: this.entity.address.address_id,
}
);
}))
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
});
},
/*
* Async PATCH transactions,
* then update existing address with backend datas when promise is resolved
*/
updateAddress(payload) {
this.flag.loading = true;
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
// BUG réécrit un postcode à chaque édition !
if ('newPostcode' in payload.newAddress) {
let postcodeBody = payload.newAddress.newPostcode;
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
console.log('juste before post new postcode', postcodeBody);
return postPostalCode(postcodeBody)
.then(postalCode => {
console.log('new postcode created', postalCode.id);
payload.newAddress.postcode = {'id': postalCode.id };
return this.patchExistingAddress(payload);
});
} else {
return this.patchExistingAddress(payload);
}
},
patchExistingAddress(payload) {
console.log('patchExistingAddress', payload);
return patchAddress(payload.addressId, payload.newAddress)
.then(address => new Promise((resolve, reject) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;
return resolve({
address,
targetOrigin: this.context.target,
// for "legacy" use:
target: this.context.target.name,
targetId: this.context.target.id,
addressId: this.entity.address.address_id
});
})
)
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
});
},
/**
*
* Called when the event pick-address is emitted, which is, by the way,
* called when an address suggestion is picked.
*
* @param address the address selected
*/
pickAddress(address) {
console.log('pickAddress', address);
duplicateAddress(address).then(newAddress => {
this.entity.address = newAddress;
this.flag.loading = false;
this.flag.success = true;
let payload = {
address: newAddress,
targetOrigin: this.context.target,
// for "legacy" use:
target: this.context.target.name,
targetId: this.context.target.id,
addressId: this.entity.address.address_id
};
this.addressChangedCallback(payload);
this.closeSuggestPane();
});
}
}
}
</script>
<style lang="scss">
div.entity-address {
position: relative;
div.loading {
position: absolute;
right: 0; top: -55px;
}
}
h4.h3 {
font-weight: bold;
}
</style>