940 lines
34 KiB
Vue

<template>
<!-- step0 -->
<show-pane
v-if="flag.showPane"
:context="this.context"
:options="this.options"
:defaultz="this.defaultz"
:entity="this.entity"
:flag="this.flag"
:use-date-pane="this.useDatePane"
@open-edit-pane="openEditPane"
ref="showAddress"
/>
<!-- step 1 -->
<teleport to="body" v-if="inModal">
<modal
v-if="flag.suggestPane"
modal-dialog-class="modal-dialog-scrollable modal-xl"
@close="resetPane"
>
<template #header>
<h2 class="modal-title">
{{ trans(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
<span class="sr-only">{{
trans(ADDRESS_LOADING)
}}</span>
</span>
</h2>
</template>
<template #body>
<suggest-pane
:context="this.context"
:options="this.options"
:defaultz="this.defaultz"
:entity="this.entity"
:flag="this.flag"
@pick-address="this.pickAddress"
ref="suggestAddress"
/>
</template>
<template #footer>
<button @click="openEditPane" class="btn btn-create">
{{ trans(CREATE_A_NEW_ADDRESS) }}
</button>
</template>
</modal>
</teleport>
<template v-else>
<div v-if="flag.suggestPane" class="mt-4 flex-grow-1">
<suggest-pane
:context="this.context"
:options="this.options"
:defaultz="this.defaultz"
:entity="this.entity"
:flag="this.flag"
:inside-modal="false"
@pick-address="this.pickAddress"
ref="suggestAddress"
>
<template #before v-if="!bypassFirstStep">
<a class="btn btn-cancel" @click="resetPane">
{{ trans(CANCEL) }}
</a>
</template>
<template #action>
<li>
<button @click="openEditPane" class="btn btn-create">
{{ trans(CREATE_A_NEW_ADDRESS) }}
</button>
</li>
</template>
</suggest-pane>
</div>
</template>
<!-- step 2 -->
<teleport to="body" v-if="inModal">
<modal
v-if="flag.editPane"
modal-dialog-class="modal-dialog-scrollable modal-xl"
@close="resetPane"
>
<template #header>
<h2 class="modal-title">
{{ trans(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
<span class="sr-only">{{
trans(ADDRESS_LOADING)
}}</span>
</span>
</h2>
</template>
<template #body>
<edit-pane
:context="this.context"
:options="this.options"
:defaultz="this.defaultz"
:entity="this.entity"
:flag="this.flag"
:errors="this.errors"
:check-errors="this.checkErrors"
@get-cities="getCities"
@get-reference-addresses="getReferenceAddresses"
/>
</template>
<template #footer>
<!--<button class="btn btn-cancel change-icon" @click="resetPane">{{ trans(CANCEL) }}</button>-->
<button
v-if="!this.context.edit && this.useDatePane"
class="btn btn-update change-icon"
@click="closeEditPane"
>
{{ trans(NEXT) }}
<i class="fa fa-fw fa-arrow-right" />
</button>
<button v-else class="btn btn-save" @click="closeEditPane">
{{ trans(SAVE) }}
</button>
</template>
</modal>
</teleport>
<template v-else>
<div v-if="flag.editPane" class="mt-4 flex-grow-1">
<edit-pane
:context="this.context"
:options="this.options"
:defaultz="this.defaultz"
:entity="this.entity"
:flag="this.flag"
:errors="this.errors"
:check-errors="this.checkErrors"
:inside-modal="false"
@get-cities="getCities"
@get-reference-addresses="getReferenceAddresses"
>
<template #before>
<a class="btn btn-cancel" @click="resetPane">
{{ trans(CANCEL) }}
</a>
</template>
<template #action>
<li v-if="!this.context.edit && this.useDatePane">
<button
class="btn btn-update change-icon"
@click="closeEditPane"
>
{{ trans(NEXT) }}
<i class="fa fa-fw fa-arrow-right" />
</button>
</li>
<li v-else>
<button class="btn btn-save" @click="closeEditPane">
{{ trans(SAVE) }}
</button>
</li>
</template>
</edit-pane>
</div>
</template>
<!-- step 3 -->
<teleport to="body" v-if="inModal">
<modal
v-if="flag.datePane"
modal-dialog-class="modal-dialog-scrollable modal-xl"
@close="resetPane"
>
<template #header>
<h2 class="modal-title">
{{ trans(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
<span class="sr-only">{{
trans(ADDRESS_LOADING)
}}</span>
</span>
</h2>
</template>
<template #body>
<date-pane
:context="this.context"
:options="this.options"
:defaultz="this.defaultz"
:entity="this.entity"
:flag="this.flag"
ref="dateAddress"
/>
</template>
<template #footer>
<button class="btn btn-misc" @click="openEditPane">
<i class="fa fa-fw fa-arrow-left" />
{{ trans(PREVIOUS) }}
</button>
<button class="btn btn-save" @click="closeDatePane">
{{ trans(SAVE) }}
</button>
<!-- -->
</template>
</modal>
</teleport>
<template v-else>
<div v-if="flag.datePane" class="mt-4 flex-grow-1">
<date-pane
:context="this.context"
:options="this.options"
:defaultz="this.defaultz"
:entity="this.entity"
:flag="this.flag"
:inside-modal="false"
ref="dateAddress"
>
<template #before>
<button class="btn btn-misc" @click="openEditPane">
<i class="fa fa-fw fa-arrow-left" />
{{ trans(PREVIOUS) }}
</button>
</template>
<template #action>
<li>
<button class="btn btn-save" @click="closeDatePane">
{{ trans(SAVE) }}
</button>
</li>
</template>
</date-pane>
</div>
</template>
</template>
<script>
import Modal from "ChillMainAssets/vuejs/_components/Modal";
import {
duplicateAddress,
fetchCountries,
fetchCities,
fetchReferenceAddresses,
getAddress,
patchAddress,
postAddress,
postPostalCode,
} from "../api";
import {
CREATE_A_NEW_ADDRESS,
ADDRESS_LOADING,
ACTIVITY_CREATE_ADDRESS,
ACTIVITY_EDIT_ADDRESS,
CANCEL,
SAVE,
PREVIOUS,
NEXT,
trans,
} from "translator";
import ShowPane from "./ShowPane.vue";
import SuggestPane from "./SuggestPane.vue";
import EditPane from "./EditPane.vue";
import DatePane from "./DatePane.vue";
export default {
name: "AddAddress",
setup() {
return {
trans,
CREATE_A_NEW_ADDRESS,
ADDRESS_LOADING,
CANCEL,
SAVE,
PREVIOUS,
NEXT,
};
},
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,
},
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: [
this.context.defaults.map_center.x,
this.context.defaults.map_center.y,
],
zoom: this.context.defaults.map_center.z,
},
},
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)
) {
console.log("this.options.title", this.options.title);
return this.context.edit
? ACTIVITY_EDIT_ADDRESS
: ACTIVITY_CREATE_ADDRESS;
}
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) => {
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) => {
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) => {
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) => {
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.");
} else {
if (Object.keys(this.entity.selected.city).length === 0) {
this.errors.push("Une ville doit être sélectionnée.");
}
}
if (!this.entity.selected.isNoAddress) {
if (
this.entity.selected.address.street === null ||
this.entity.selected.address.street === "" ||
this.entity.selected.address.streetNumber === null ||
this.entity.selected.address.streetNumber === ""
) {
this.errors.push("Une adresse 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.confidential = this.context.edit
? this.entity.address.confidential
: false;
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.context.edit
? this.entity.address.postcode
: {};
this.entity.selected.address = {};
this.entity.selected.address.street = this.context.edit
? this.entity.address.street
: "";
this.entity.selected.address.streetNumber = this.context.edit
? this.entity.address.streetNumber
: "";
this.entity.selected.address.floor = this.context.edit
? this.entity.address.floor
: "";
this.entity.selected.address.corridor = this.context.edit
? this.entity.address.corridor
: "";
this.entity.selected.address.steps = this.context.edit
? this.entity.address.steps
: "";
this.entity.selected.address.flat = this.context.edit
? this.entity.address.flat
: "";
this.entity.selected.address.buildingName = this.context.edit
? this.entity.address.buildingName
: "";
this.entity.selected.address.distribution = this.context.edit
? this.entity.address.distribution
: "";
this.entity.selected.address.extra = this.context.edit
? this.entity.address.extra
: "";
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,
);
this.checkErrors();
},
/*
* 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 = {
confidential: this.entity.selected.confidential,
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,
});
} else {
newAddress = Object.assign(newAddress, {
addressReference: null,
});
}
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) => {
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) => {
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>