Merge remote-tracking branch 'origin/master' into fixtures/fix-loading-people

This commit is contained in:
Julien Fastré 2021-08-17 21:52:00 +02:00
commit 609deba325
80 changed files with 2590 additions and 1436 deletions

View File

@ -28,7 +28,7 @@ Then, render the pagination using the dedicated twig function.
.. code-block:: html+twig
{% extends "ChillPersonBundle::layout.html.twig" %}
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% block title 'Item list'|trans %}

View File

@ -149,7 +149,7 @@ It proposes a new block :
* where to display the admin content
ChillPersonBundle::layout.html.twig
@ChillPersonBundle/Person/layout.html.twig
-----------------------------------
This layout extend `ChillMainBundle::layoutWithVerticalMenu.html.twig` add the person details in the block `top_banner`, set the menu `person` as the vertical menu.

View File

@ -22,7 +22,9 @@
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Search\SearchApiNoQueryException;
use Chill\MainBundle\Serializer\Model\Collection;
use GuzzleHttp\Psr7\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Chill\MainBundle\Search\UnknowSearchDomainException;
@ -33,6 +35,7 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\HttpFoundation\JsonResponse;
use Chill\MainBundle\Search\SearchProvider;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Contracts\Translation\TranslatorInterface;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Search\SearchApi;
@ -159,9 +162,13 @@ class SearchController extends AbstractController
." one type");
}
try {
$collection = $this->searchApi->getResults($query, $types, []);
} catch (SearchApiNoQueryException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
return $this->json($collection);
return $this->json($collection, \Symfony\Component\HttpFoundation\Response::HTTP_OK, [], [ "groups" => ["read"]]);
}
public function advancedSearchListAction(Request $request)

View File

@ -265,6 +265,17 @@ table.table-bordered {
}
}
div.metadata {
font-size: smaller;
color: $gray-600;
span.user, span.date {
text-decoration: underline dotted;
&:hover {
color: $gray-700;
}
}
}
/// display definition list
// with dt and dd on same line
@ -283,7 +294,6 @@ dl.definition-inline {
}
}
/// when there is no data
.custom_field_no_data,
.chill-no-data-statement {

View File

@ -43,6 +43,7 @@ ul.record_actions {
display: flex;
padding: 0.8em 1.6em;
border-radius: 0;
z-index: 1000;
}
/// EXCEPTIONS

View File

@ -93,9 +93,10 @@ section.chill-entity {
font-style: italic;
}
span.address-valid {
&.address-since {}
&.address-until {}
.address-valid {
margin-top: 2em;
&.date-since {}
&.date-until {}
}
}

View File

@ -1,147 +1,86 @@
<template>
<div class="chill-entity entity-address">
<h2 v-if="!edit">{{ $t('create_a_new_address') }}</h2>
<h2 v-else>{{ $t('edit_address') }}</h2>
<show-address
v-if="address"
v-bind:address="address">
</show-address>
<div v-for="error in displayErrors" class="alert alert-danger my-2">
{{ error }}
</div>
<add-address
@addNewAddress="addNewAddress">
v-bind:key="context.entity.type"
v-bind:context="context"
v-bind:options="addAddress.options"
v-bind:result="addAddress.result"
@submitAddress="submitAddress"
ref="addAddress">
</add-address>
</div>
<div v-if="!edit" class='person-address__valid'>
<h2>{{ $t('date') }}</h2>
<div class="input-group mb-3">
<span class="input-group-text" id="validFrom"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control form-control-lg" name="validFrom"
v-bind:placeholder="$t('validFrom')"
v-model="validFrom"
aria-describedby="validFrom" />
</div>
<div v-if="errors.length > 0">
{{ errors }}
</div>
<div v-if="loading">
{{ $t('loading') }}
</div>
<div v-if="success">
{{ $t('person_address_creation_success') }}
</div>
</div>
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a :href="backUrl" class="btn btn-cancel">{{ $t('back_to_the_list') }}</a>
</li>
<li v-if="!edit">
<button type="submit" class="btn btn-update" @click="addToPerson">
{{ $t('add_an_address_to_person') }}
</button>
</li>
</ul>
</template>
<script>
/*
* Address component is a uniq component for many contexts.
* Allow to create/attach/edit an address to
* - a person (new or edit address),
* - a household (move or edit address)
*
* */
import AddAddress from './components/AddAddress.vue';
import ShowAddress from './components/ShowAddress.vue';
export default {
name: 'App',
name: "App",
components: {
AddAddress,
ShowAddress
AddAddress
},
data() {
return {
context: {
edit: window.mode === 'edit',
personId: window.personId,
addressId: window.addressId,
backUrl: `/fr/person/${window.personId}/address/list`, //TODO better way to pass this
validFrom: new Date().toISOString().split('T')[0]
entity: {
type: window.entityType,
id: window.entityId
},
addressId: window.addressId | null,
backUrl: window.backUrl,
},
addAddress: {
options: {
/// Options override default.
/// null value take default component value defined in AddAddress data()
button: {
text: {
create: window.buttonText || null,
edit: window.buttonText || null
},
size: window.buttonSize || null,
displayText: window.buttonDisplayText //boolean, default: true
},
/// Modal title text if create or edit address (trans chain, see i18n)
title: {
create: window.modalTitle || null,
edit: window.modalTitle || null
},
/// Display each step in page or Modal
bindModal: {
step1: window.binModalStep1, //boolean, default: true
step2: window.binModalStep2 //boolean, default: true
}
}
}
},
computed: {
address() {
return this.$store.state.address;
},
errors() {
return this.$store.state.errorMsg;
},
loading() {
return this.$store.state.loading;
},
success() {
return this.$store.state.success;
}
},
methods: {
addNewAddress({ address, modal }) {
console.log('@@@ CLICK button addNewAdress', address);
let newAddress = {
'isNoAddress': address.isNoAddress,
'street': address.isNoAddress ? '' : address.street,
'streetNumber': address.isNoAddress ? '' : address.streetNumber,
'postcode': {'id': address.selected.city.id},
'floor': address.floor,
'corridor': address.corridor,
'steps': address.steps,
'flat': address.flat,
'buildingName': address.buildingName,
'distribution': address.distribution,
'extra': address.extra
};
if (address.selected.address.point !== undefined){
newAddress = Object.assign(newAddress, {
'point': address.selected.address.point.coordinates
});
}
if (address.writeNewPostalCode){
let newPostalCode = address.newPostalCode;
newPostalCode = Object.assign(newPostalCode, {
'country': {'id': address.selected.country.id },
});
newAddress = Object.assign(newAddress, {
'newPostalCode': newPostalCode
});
}
if (this.edit){
this.$store.dispatch('updateAddress', {
addressId: this.addressId,
newAddress: newAddress
});
} else {
this.$store.dispatch('addAddress', newAddress);
}
modal.showModal = false;
displayErrors() {
return this.$refs.addAddress.errorMsg;
},
addToPerson() {
this.$store.dispatch('addDateToAddressAndAddressToPerson', {
personId: this.personId,
addressId: this.$store.state.address.address_id,
body: { validFrom: {datetime: `${this.validFrom}T00:00:00+0100`}},
backUrl: this.backUrl
})
},
getEditAddress() {
this.$store.dispatch('getEditAddress', this.addressId);
submitAddress() {
console.log('@@@ click on Submit Address Button');
// Cast child method
this.$refs.addAddress.submitNewAddress();
// it fetch post request only for person and household
// else get returned payload then dispatch from here (parent)
}
}
},
mounted() {
if (this.edit) {
this.getEditAddress();
}
},
};
</script>

View File

@ -4,7 +4,7 @@
* @returns {Promise} a promise containing all Country object
*/
const fetchCountries = () => {
console.log('<<< fetching countries');
//console.log('<<< fetching countries');
const url = `/api/1.0/main/country.json?item_per_page=1000`;
return fetch(url)
@ -20,7 +20,7 @@ const fetchCountries = () => {
* @returns {Promise} a promise containing all Postal Code objects filtered with country
*/
const fetchCities = (country) => {
console.log('<<< fetching cities for', country);
//console.log('<<< fetching cities for', country);
const url = `/api/1.0/main/postal-code.json?item_per_page=1000&country=${country.id}`;
return fetch(url)
.then(response => {
@ -35,7 +35,7 @@ const fetchCities = (country) => {
* @returns {Promise} a promise containing all AddressReference objects filtered with postal code
*/
const fetchReferenceAddresses = (postalCode) => {
console.log('<<< fetching references addresses for', postalCode);
//console.log('<<< fetching references addresses for', postalCode);
const url = `/api/1.0/main/address-reference.json?item_per_page=1000&postal_code=${postalCode.id}`;
return fetch(url)
.then(response => {
@ -50,7 +50,7 @@ const fetchReferenceAddresses = (postalCode) => {
* @returns {Promise} a promise containing all AddressReference objects filtered with postal code
*/
const fetchAddresses = () => {
console.log('<<< fetching addresses');
//console.log('<<< fetching addresses');
//TODO deal with huge number of addresses... we should do suggestion...
const url = `/api/1.0/main/address.json?item_per_page=1000`;
return fetch(url)
@ -125,31 +125,6 @@ const postPostalCode = (postalCode) => {
});
};
/*
* Endpoint chill_api_single_person_address
* method POST, post Person instance
*
* @id integer - id of Person
* @body Object - dictionary with changes to post
*/
const postAddressToPerson = (personId, addressId) => {
console.log(personId);
console.log(addressId);
const body = {
'id': addressId
};
const url = `/api/1.0/person/person/${personId}/address.json`
return fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
/*
* Endpoint chill_api_single_address__index
* method GET, get Address Object
@ -157,7 +132,7 @@ const postAddressToPerson = (personId, addressId) => {
* @returns {Promise} a promise containing a Address object
*/
const getAddress = (id) => {
console.log('<<< get address');
//console.log('<< get address');
const url = `/api/1.0/main/address/${id}.json`;
return fetch(url)
.then(response => {
@ -174,6 +149,5 @@ export {
postAddress,
patchAddress,
postPostalCode,
postAddressToPerson,
getAddress
};

View File

@ -1,273 +1,618 @@
<template>
<button v-if="!edit" class="btn btn-create mt-4" @click="openModal">
{{ $t('add_an_address_title') }}
</button>
<button v-else class="btn btn-create mt-4" @click="openModal">
{{ $t('edit_address') }}
<!-- start with a button -->
<button v-if="step1WithModal"
@click="openShowPane"
class="btn" :class="getClassButton"
type="button" name="button" :title="$t(getTextButton)">
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
</button>
<teleport to="body">
<modal v-if="modal.showModal"
v-bind:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false">
<!-- step 1 -->
<teleport to="body" v-if="step1WithModal">
<modal v-if="flag.showPane"
modalDialogClass="modal-dialog-scrollable modal-xl"
@close="flag.showPane = false">
<template v-slot:header>
<h3 v-if="!edit" class="modal-title">{{ $t('add_an_address_title') }}</h3>
<h3 v-if="edit" class="modal-title">{{ $t('edit_an_address_title') }}</h3>
<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>
<div class="address-form">
<h4 class="h3">{{ $t('select_an_address_title') }}
<span v-if="loading">
<i class="fa fa-circle-o-notch fa-spin fa-lg"></i>
</span>
</h4>
<div class="row my-3">
<div class="col-lg-6">
<div class="form-check">
<input type="checkbox"
class="form-check-input"
id="isNoAddress"
v-model="isNoAddress"
v-bind:value="value" />
<label class="form-check-label" for="isNoAddress">
{{ $t('isNoAddress') }}
</label>
</div>
<country-selection
v-bind:address="address"
v-bind:getCities="getCities">
</country-selection>
<city-selection
v-bind:address="address"
v-bind:focusOnAddress="focusOnAddress"
v-bind:getReferenceAddresses="getReferenceAddresses">
</city-selection>
<address-selection
v-if="!isNoAddress"
v-bind:address="address"
v-bind:updateMapCenter="updateMapCenter">
</address-selection>
</div>
<div class="col-lg-6 mt-3 mt-lg-0">
<address-map
v-bind:address="address"
ref="addressMap">
</address-map>
</div>
</div>
<address-more
v-if="!isNoAddress"
v-bind:address="address">
</address-more>
</div>
<show-address-pane
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:default="this.default"
v-bind:entity="this.entity"
v-bind:valid="this.valid"
v-bind:flag="this.flag"
ref="showAddress">
</show-address-pane>
</template>
<template v-slot:footer>
<button class="btn btn-create"
@click.prevent="$emit('addNewAddress', { address, modal })">
{{ $t('action.add')}}
<button @click="openEditPane"
class="btn btn-update">
{{ $t('action.edit')}}
</button>
<button class="btn btn-save"
@click.prevent="$emit('submitAddress')">
{{ $t('action.save')}}
</button>
</template>
</modal>
</teleport>
<div class="mt-4" v-else>
<show-address-pane v-if="flag.showPane"
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:default="this.default"
v-bind:entity="this.entity"
v-bind:valid="this.valid"
v-bind:flag="this.flag"
ref="showAddress"
v-bind:insideModal="false" @openEditPane="openEditPane"
@submitAddress="$emit('submitAddress')">
</show-address-pane>
</div>
<!-- step 2 -->
<teleport to="body" v-if="step2WithModal">
<modal v-if="flag.editPane"
modalDialogClass="modal-dialog-scrollable modal-xl"
@close="flag.editPane = false">
<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-address-pane
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:default="this.default"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
@getCities="getCities"
@getReferenceAddresses="getReferenceAddresses">
</edit-address-pane>
</template>
<template v-slot:footer>
<button class="btn btn-cancel change-icon" @click="flag.showPane = true; flag.editPane = false;">
{{ $t('action.cancel') }}
</button>
<button class="btn btn-update"
@click="closeEditPane">
{{ $t('action.valid')}}
</button>
</template>
</modal>
</teleport>
<div class="mt-4" v-else>
<edit-address-pane v-if="flag.editPane"
v-bind:context="this.context"
v-bind:options="this.options"
v-bind:default="this.default"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
v-bind:insideModal="false" @closeEditPane="closeEditPane"
@getCities="getCities"
@getReferenceAddresses="getReferenceAddresses">
</edit-address-pane>
</div>
</template>
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import { fetchCountries, fetchCities, fetchReferenceAddresses } from '../api'
import CountrySelection from './AddAddress/CountrySelection';
import CitySelection from './AddAddress/CitySelection';
import AddressSelection from './AddAddress/AddressSelection';
import AddressMap from './AddAddress/AddressMap';
import AddressMore from './AddAddress/AddressMore'
import { getAddress, fetchCountries, fetchCities, fetchReferenceAddresses, patchAddress, postAddress, postPostalCode } from '../api';
import { postAddressToPerson, postAddressToHousehold } from "ChillPersonAssets/vuejs/_api/AddAddress.js";
import ShowAddressPane from './ShowAddressPane.vue';
import EditAddressPane from './EditAddressPane.vue';
export default {
name: 'AddAddresses',
name: "AddAddress",
props: ['context', 'options', 'result'],
emits: ['submitAddress'],
components: {
Modal,
CountrySelection,
CitySelection,
AddressSelection,
AddressMap,
AddressMore
ShowAddressPane,
EditAddressPane,
},
props: [
],
emits: ['addNewAddress'],
data() {
return {
edit: window.mode === 'edit',
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl"
},
flag: {
showPane: false,
editPane: false,
loading: false,
address: {
writeNewAddress: false,
writeNewPostalCode: false,
success: false
},
default: {
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' },
bindModal: {
step1: true,
step2: true,
},
},
entity: {
address: {}, // <== loaded and returned
loaded: {
countries: [],
cities: [],
addresses: [],
},
selected: {
selected: { // <== make temporary changes
isNoAddress: false,
country: {},
city: {},
address: {},
},
newPostalCode: {
postcode: {
code: null,
name: null
},
address: {},
writeNew: {
address: false,
postcode: false
}
},
addressMap: {
center : [48.8589, 2.3469], // Note: LeafletJs demands [lat, lon] cfr https://macwright.com/lonlat/
// Note: LeafletJs demands [lat, lon]
// cfr https://macwright.com/lonlat/
center : [48.8589, 2.3469],
zoom: 12
},
isNoAddress: false,
street: null,
streetNumber: null,
floor: null,
corridor: null,
steps: null,
floor: null,
flat: null,
buildingName: null,
extra: null,
distribution: null,
},
errorMsg: {},
valid: {
from: new Date(),
to: null
},
errorMsg: []
}
},
computed: {
isNoAddress: {
set(value) {
console.log('value', value);
this.address.isNoAddress = value;
step1WithModal() {
return (this.options.bindModal !== null && typeof this.options.bindModal.step1 !== 'undefined') ?
this.options.bindModal.step1 : this.default.bindModal.step1;
},
get() {
return this.address.isNoAddress;
step2WithModal() {
let step2 = (this.options.bindModal !== null && typeof this.options.bindModal.step2 !== 'undefined') ?
this.options.bindModal.step2 : this.default.bindModal.step2;
if (step2 === false && this.step1WithModal === true) {
console.log("step2 must open in a Modal");
return true;
}
return step2;
},
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.default.title.edit : this.default.title.create;
},
getTextButton() {
if ( typeof this.options.button.text !== 'undefined'
&& ( this.options.button.text.edit !== null
|| this.options.button.text.create !== null
)) {
return (this.context.edit) ? this.options.button.text.edit : this.options.button.text.create;
}
return (this.context.edit) ? this.default.button.text.edit : this.default.button.text.create;
},
getClassButton() {
let type = (this.context.edit) ? this.default.button.type.edit : this.default.button.type.create;
let size = (typeof this.options.button !== 'undefined' && this.options.button.size !== null) ?
`${this.options.button.size} ` : '';
return `${size}${type}`;
},
displayTextButton() {
return (typeof this.options.button !== 'undefined' && typeof this.options.button.displayText !== 'undefined') ?
this.options.button.displayText : this.default.button.displayText;
},
context() {
return this.context;
}
},
mounted() {
this.getCountries();
if (!this.step1WithModal) {
//console.log('Mounted now !');
this.openShowPane();
}
},
methods: {
openModal() {
this.modal.showModal = true;
this.resetAll();
//this.$nextTick(function() {
// this.$refs.search.focus(); // positionner le curseur à l'ouverture de la modale
//})
},
focusOnCity() {
const citySelector = document.getElementById('citySelector');
citySelector.focus();
},
focusOnAddress() {
const addressSelector = document.getElementById('addressSelector');
addressSelector.focus();
},
getCountries() {
console.log('getCountries');
this.loading = true;
fetchCountries().then(countries => new Promise((resolve, reject) => {
this.address.loaded.countries = countries.results;
resolve()
this.loading = false;
}))
.catch((error) => {
this.errorMsg.push(error.message);
this.loading = false;
});
},
getCities(country) {
console.log('getCities for', country.name);
this.loading = true;
fetchCities(country).then(cities => new Promise((resolve, reject) => {
this.address.loaded.cities = cities.results.filter(c => c.origin !== 3); // filter out user-defined cities
resolve();
this.loading = false;
}))
.catch((error) => {
this.errorMsg.push(error.message);
this.loading = false;
});
},
getReferenceAddresses(city) {
this.loading = true;
console.log('getReferenceAddresses for', city.name);
fetchReferenceAddresses(city).then(addresses => new Promise((resolve, reject) => {
console.log('addresses', addresses);
this.address.loaded.addresses = addresses.results;
resolve();
this.loading = false;
}))
.catch((error) => {
this.errorMsg.push(error.message);
this.loading = false;
});
},
updateMapCenter(point) {
console.log('point', point);
this.address.addressMap.center[0] = point.coordinates[1]; // TODO use reverse()
this.address.addressMap.center[1] = point.coordinates[0];
this.$refs.addressMap.update(); // cast child methods
},
resetAll() {
console.log('reset all selected');
this.address.loaded.addresses = [];
this.address.selected.address = {};
this.address.loaded.cities = [];
this.address.selected.city = {};
this.address.selected.country = {};
this.address.isNoAddress = this.edit ? this.$store.state.editAddress.isNoAddress: false;;
this.address.street = this.edit ? this.$store.state.editAddress.street: null;
this.address.streetNumber = this.edit ? this.$store.state.editAddress.streetNumber: null;
this.address.floor = this.edit ? this.$store.state.editAddress.floor: null;
this.address.corridor = this.edit ? this.$store.state.editAddress.corridor: null;
this.address.steps = this.edit ? this.$store.state.editAddress.steps: null;
this.address.flat = this.edit ? this.$store.state.editAddress.flat: null;
this.address.buildingName = this.edit ? this.$store.state.editAddress.buildingName: null;
this.address.distribution = this.edit ? this.$store.state.editAddress.distribution: null;
this.address.extra = this.edit ? this.$store.state.editAddress.extra: null;
this.address.writeNewAddress = this.edit;
this.address.writeNewPostalCode = this.edit;
this.address.newPostalCode = this.edit ?
{
code: this.$store.state.editAddress.postcode !== undefined ? this.$store.state.editAddress.postcode.code : null,
name: this.$store.state.editAddress.postcode !== undefined ? this.$store.state.editAddress.postcode.name : null
} : {};
console.log('cities and addresses', this.address.loaded.cities, this.address.loaded.addresses);
/*
* Opening and closing Panes when interacting with buttons
*/
openShowPane() {
console.log('open the Show Panel');
if (this.context.edit) {
this.getInitialAddress(this.context.addressId);
}
// when create new address, start first with editPane
if ( this.context.edit === false
&& this.flag.editPane === false
) {
this.openEditPane();
this.flag.editPane = true;
} else {
this.flag.showPane = true;
}
},
openEditPane() {
console.log('open the Edit panel');
this.initForm();
this.getCountries();
},
closeEditPane() {
console.log('close the Edit Panel');
this.applyChanges();
this.flag.showPane = true;
this.flag.editPane = false;
},
/*
* 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;
this.flag.showPane = false;
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;
});
},
/*
* 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.selected.writeNew.postcode = this.context.edit;
},
/*
* 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()
{
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
});
}
if (this.entity.selected.writeNew.postcode) {
let newPostcode = this.entity.selected.postcode;
newPostcode = Object.assign(newPostcode, {
'country': {'id': this.entity.selected.country.id },
});
newAddress = Object.assign(newAddress, {
'newPostcode': newPostcode
});
}
if (this.context.edit) {
this.updateAddress({
addressId: this.context.addressId,
newAddress: newAddress
});
} else {
this.addAddress(newAddress);
}
},
/*
* Async POST transactions,
* creating new address, and receive backend datas when promise is resolved
*/
addAddress(payload)
{
this.flag.loading = true;
if ('newPostcode' in payload) {
let postcodeBody = payload.newPostcode;
if (this.context.entity.type === 'person') {
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
}
postPostalCode(postcodeBody)
.then(postalCode => {
let body = payload;
body.postcode = {'id': postalCode.id },
postAddress(body)
.then(address => new Promise((resolve, reject) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;
resolve();
}))
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
});
})
} else {
postAddress(payload)
.then(address => new Promise((resolve, reject) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;
resolve();
}))
.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)
{
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
this.flag.loading = true;
if ('newPostcode' in payload.newAddress) {
let postcodeBody = payload.newAddress.newPostcode;
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
postPostalCode(postcodeBody)
.then(postalCode => {
let body = payload.newAddress;
body.postcode = {'id': postalCode.id },
patchAddress(payload.addressId, body)
.then(address => new Promise((resolve, reject) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;
resolve();
}))
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
});
})
} else {
patchAddress(payload.addressId, payload.newAddress)
.then(address => new Promise((resolve, reject) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;
resolve();
}))
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
});
}
},
/*
* When submit address
* (get out step1 show pane, submit button)
*/
submitNewAddress()
{
let payload = {
entity: this.context.entity.type,
entityId: this.context.entity.id,
addressId: this.entity.address.address_id,
body: {
validFrom: {
datetime: `${this.valid.from.toISOString().split('T')[0]}T00:00:00+0100`
}
},
backUrl: this.context.backUrl
}
if ( payload.entity !== 'person' && payload.entity !== 'household' ) {
// just return payload to parent
// (changes will be patched in parent store)
this.initForm();
this.flag.showPane = false;
return payload;
}
console.log('submitNewAddress with', payload);
this.addDateToAddressAndAddressTo(payload);
this.initForm();
this.flag.showPane = false;
},
addDateToAddressAndAddressTo(payload)
{
console.log('addDateToAddressAndAddressTo', payload.entity)
this.flag.loading = true;
return patchAddress(payload.addressId, payload.body)
.then(address => new Promise((resolve, reject) => {
this.valid.from = address.validFrom;
resolve();
})
.then(this.postAddressTo(payload))
)
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
});
},
postAddressTo(payload)
{
console.log('postAddressTo', payload.entity);
if (!this.context.edit) {
switch (payload.entity) {
case 'household':
postAddressToHousehold(payload.entityId, payload.addressId)
.then(household => new Promise((resolve, reject) => {
console.log('postAddressToHousehold', household);
this.flag.loading = false;
this.flag.success = true;
window.location.assign(payload.backUrl);
resolve();
}))
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
})
;
break;
case 'person':
postAddressToPerson(payload.entityId, payload.addressId)
.then(person => new Promise((resolve, reject) => {
console.log('postAddressToPerson', person);
this.flag.loading = false;
this.flag.success = true;
window.location.assign(payload.backUrl);
resolve();
}))
.catch((error) => {
this.errorMsg.push(error);
this.flag.loading = false;
})
;
break;
default:
this.errorMsg.push('That entity is not managed by address !');
}
} else {
// address is already linked, just finish !
window.location.assign(payload.backUrl);
}
},
}
}
</script>
<style lang="scss">
div.address-form {
h4.h3 {
font-weight: bold;
}
div#address_map {
height: 400px;
width: 100%;
div.entity-address {
position: relative;
div.loading {
position: absolute;
right: 0; top: -55px;
}
}
</style>

View File

@ -13,16 +13,18 @@ let marker;
export default {
name: 'AddressMap',
props: ['address'],
props: ['entity'],
computed: {
center() {
return this.address.addressMap.center;
return this.entity.selected.addressMap.center;
},
},
methods:{
init() {
map = L.map('address_map').setView([46.67059, -1.42683], 12);
map.scrollWheelZoom.disable();
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
@ -36,9 +38,9 @@ export default {
},
update() {
console.log('update map with : ', this.address.addressMap.center)
marker.setLatLng(this.address.addressMap.center);
map.setView(this.address.addressMap.center, 15);
//console.log('update map with : ', this.address.addressMap.center)
marker.setLatLng(this.entity.addressMap.center);
map.setView(this.entity.addressMap.center, 15);
}
},
mounted(){

View File

@ -74,69 +74,62 @@
<script>
export default {
name: "AddressMore",
props: ['address'],
props: ['entity'],
computed: {
floor: {
set(value) {
console.log('value', value);
this.address.floor = value;
this.entity.selected.address.floor = value;
},
get() {
return this.address.floor;
return this.entity.selected.address.floor;
}
},
corridor: {
set(value) {
console.log('value', value);
this.address.corridor = value;
this.entity.selected.address.corridor = value;
},
get() {
return this.address.corridor;
return this.entity.selected.address.corridor;
}
},
steps: {
set(value) {
console.log('value', value);
this.address.steps = value;
this.entity.selected.address.steps = value;
},
get() {
return this.address.steps;
return this.entity.selected.address.steps;
}
},
flat: {
set(value) {
console.log('value', value);
this.address.flat = value;
this.entity.selected.address.flat = value;
},
get() {
return this.address.flat;
return this.entity.selected.address.flat;
}
},
buildingName: {
set(value) {
console.log('value', value);
this.address.buildingName = value;
this.entity.selected.address.buildingName = value;
},
get() {
return this.address.buildingName;
return this.entity.selected.address.buildingName;
}
},
extra: {
set(value) {
console.log('value', value);
this.address.extra = value;
this.entity.selected.address.extra = value;
},
get() {
return this.address.extra;
return this.entity.selected.address.extra;
}
},
distribution: {
set(value) {
console.log('value', value);
this.address.distribution = value;
this.entity.selected.address.distribution = value;
},
get() {
return this.address.distribution;
return this.entity.selected.address.distribution;
}
}
}

View File

@ -47,7 +47,7 @@ import VueMultiselect from 'vue-multiselect';
export default {
name: 'AddressSelection',
components: { VueMultiselect },
props: ['address', 'updateMapCenter'],
props: ['entity', 'updateMapCenter'],
data() {
return {
value: null
@ -55,28 +55,28 @@ export default {
},
computed: {
writeNewAddress() {
return this.address.writeNewAddress;
return this.entity.selected.writeNew.address;
},
writeNewPostalCode() {
return this.address.writeNewPostalCode;
return this.entity.selected.writeNew.postCode;
},
addresses() {
return this.address.loaded.addresses;
return this.entity.loaded.addresses;
},
street: {
set(value) {
this.address.street = value;
this.entity.selected.address.street = value;
},
get() {
return this.address.street;
return this.entity.selected.address.street;
}
},
streetNumber: {
set(value) {
this.address.streetNumber = value;
this.entity.selected.address.streetNumber = value;
},
get() {
return this.address.streetNumber;
return this.entity.selected.address.streetNumber;
}
},
},
@ -85,13 +85,13 @@ export default {
return value.streetNumber === undefined ? value.street : `${value.streetNumber}, ${value.street}`
},
selectAddress(value) {
this.address.selected.address = value;
this.address.street = value.street;
this.address.streetNumber = value.streetNumber;
this.entity.selected.address = value;
this.entity.selected.address.street = value.street;
this.entity.selected.address.streetNumber = value.streetNumber;
this.updateMapCenter(value.point);
},
addAddress() {
this.address.writeNewAddress = true;
this.entity.selected.writeNew.address = true;
}
}
};

View File

@ -12,13 +12,13 @@
:placeholder="$t('select_city')"
:taggable="true"
:multiple="false"
@tag="addPostalCode"
@tag="addPostcode"
:tagPlaceholder="$t('create_postal_code')"
:options="cities">
</VueMultiselect>
</div>
<div class="custom-postcode row g-1" v-if="writeNewPostalCode">
<div class="custom-postcode row g-1" v-if="writeNewPostcode">
<div class="col-4">
<div class="form-floating">
<input class="form-control"
@ -48,33 +48,34 @@ import VueMultiselect from 'vue-multiselect';
export default {
name: 'CitySelection',
components: { VueMultiselect },
props: ['address', 'getReferenceAddresses', 'focusOnAddress'],
props: ['entity', 'focusOnAddress'],
emits: ['getReferenceAddresses'],
data() {
return {
value: null
}
},
computed: {
writeNewPostalCode() {
return this.address.writeNewPostalCode;
writeNewPostcode() {
return this.entity.selected.writeNew.postcode;
},
cities() {
return this.address.loaded.cities;
return this.entity.loaded.cities;
},
name: {
set(value) {
this.address.newPostalCode.name = value;
this.entity.selected.postcode.name = value;
},
get() {
return this.address.newPostalCode.name;
return this.entity.selected.postcode.name;
}
},
code: {
set(value) {
this.address.newPostalCode.code= value;
this.entity.selected.postcode.code= value;
},
get() {
return this.address.newPostalCode.code;
return this.entity.selected.postcode.code;
}
},
},
@ -83,14 +84,14 @@ export default {
return `${value.code}-${value.name}`
},
selectCity(value) {
this.address.selected.city = value;
this.address.newPostalCode.name = value.name;
this.address.newPostalCode.code = value.code;
this.getReferenceAddresses(value);
this.entity.selected.city = value;
this.entity.selected.postcode.name = value.name;
this.entity.selected.postcode.code = value.code;
this.$emit('getReferenceAddresses', value);
this.focusOnAddress();
},
addPostalCode() {
this.address.writeNewPostalCode = true;
addPostcode() {
this.entity.selected.writeNew.postcode = true;
}
}
};

View File

@ -2,13 +2,13 @@
<div class="my-1">
<label class="col-form-label" for="countrySelect">{{ $t('country') }}</label>
<VueMultiselect
v-model="value"
id="countrySelect"
track-by="id"
label="name"
:custom-label="transName"
:placeholder="$t('select_country')"
:options="countries"
track-by="id"
v-bind:custom-label="transName"
v-bind:placeholder="$t('select_country')"
v-bind:options="sortedCountries"
v-model="value"
@select="selectCountry">
</VueMultiselect>
</div>
@ -20,43 +20,47 @@ import VueMultiselect from 'vue-multiselect';
export default {
name: 'CountrySelection',
components: { VueMultiselect },
props: ['address', 'getCities'],
props: ['context', 'entity'],
emits: ['getCities'],
data() {
return {
edit: window.mode === 'edit',
defaultCountry: this.edit ? this.$store.state.editAddress.country.code : 'FR',
value: this.address.loaded.countries.filter(c => c.countryCode === this.defaultCountry)[0]
value: this.selectCountryByCode(
this.context.edit ? this.entity.selected.country.code : 'FR'
)
}
},
computed: {
sortedCountries() {
//console.log('sorted countries');
const countries = this.entity.loaded.countries;
let sortedCountries = [];
sortedCountries.push(...countries.filter(c => c.countryCode === 'FR'))
sortedCountries.push(...countries.filter(c => c.countryCode === 'BE'))
sortedCountries.push(...countries.filter(c => c.countryCode !== 'FR').filter(c => c.countryCode !== 'BE'))
return sortedCountries;
}
},
mounted() {
this.init();
},
methods: {
init() {
this.value = this.edit ?
this.address.loaded.countries.filter(c => c.countryCode === this.$store.state.editAddress.country.code)[0]:
this.address.loaded.countries.filter(c => c.countryCode === 'FR')[0]
if (this.value !== undefined) {
this.selectCountry(this.value);
}
},
selectCountryByCode(countryCode) {
return this.entity.loaded.countries.filter(c => c.countryCode === countryCode)[0];
},
transName ({ name }) {
return name.fr //TODO multilang
},
selectCountry(value) {
this.address.selected.country = value;
this.getCities(value);
},
},
mounted(){
this.init()
},
computed: {
countries() {
const countries = this.address.loaded.countries;
let orderedCountries = [];
orderedCountries.push(...countries.filter(c => c.countryCode === 'FR'))
orderedCountries.push(...countries.filter(c => c.countryCode === 'BE'))
orderedCountries.push(...countries.filter(c => c.countryCode !== 'FR').filter(c => c.countryCode !== 'BE'))
return orderedCountries;
//console.log('select country', value);
this.entity.selected.country = value;
this.$emit('getCities', value);
}
}
};
</script>

View File

@ -0,0 +1,160 @@
<template>
<div class="address-form">
<!-- Not display in modal -->
<div v-if="insideModal == false" class="loading">
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
<span class="sr-only">Loading...</span>
</div>
<h4 class="h3">{{ $t('select_an_address_title') }}</h4>
<div class="row my-3">
<div class="col-lg-6">
<div class="form-check">
<input type="checkbox"
class="form-check-input"
id="isNoAddress"
v-model="isNoAddress"
v-bind:value="value" />
<label class="form-check-label" for="isNoAddress">
{{ $t('isNoAddress') }}
</label>
</div>
<country-selection
v-bind:context="context"
v-bind:entity="entity"
@getCities="$emit('getCities', selected.country)">
</country-selection>
<city-selection
v-bind:entity="entity"
v-bind:focusOnAddress="focusOnAddress"
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
</city-selection>
<address-selection v-if="!isNoAddress"
v-bind:entity="entity"
v-bind:updateMapCenter="updateMapCenter">
</address-selection>
</div>
<div class="col-lg-6 mt-3 mt-lg-0">
<address-map
v-bind:entity="entity"
ref="addressMap">
</address-map>
</div>
</div>
<address-more v-if="!isNoAddress"
v-bind:entity="entity">
</address-more>
<!-- Not display in modal -->
<ul v-if="insideModal == false"
class="record_actions sticky-form-buttons">
<li class="cancel">
<a class="btn btn-cancel" v-bind:href="context.backUrl">
{{ $t('back_to_the_list') }}
</a>
</li>
<li>
<a class="btn btn-cancel change-icon" @click="flag.showPane = true; flag.editPane = false;">
{{ $t('action.cancel') }}
</a>
</li>
<li>
<a class="btn btn-update" @click.prevent="$emit('closeEditPane')">
{{ $t('action.valid_and_see')}}
</a>
</li>
</ul>
</div>
</template>
<script>
import CountrySelection from './AddAddress/CountrySelection';
import CitySelection from './AddAddress/CitySelection';
import AddressSelection from './AddAddress/AddressSelection';
import AddressMap from './AddAddress/AddressMap';
import AddressMore from './AddAddress/AddressMore'
export default {
name: "EditAddressPane",
components: {
CountrySelection,
CitySelection,
AddressSelection,
AddressMap,
AddressMore
},
props: [
'context',
'options',
'default',
'flag',
'entity',
'errorMsg',
'insideModal'
],
emits: ['closeEditPane', 'getCities', 'getReferenceAddresses'],
data() {
return {
value: false
}
},
computed: {
address() {
return this.entity.address;
},
loaded() {
return this.entity.loaded;
},
selected() {
return this.entity.selected;
},
addressMap() {
return this.entity.addressMap;
},
isNoAddress: {
set(value) {
console.log('isNoAddress value', value);
this.entity.selected.isNoAddress = value;
},
get() {
return this.entity.selected.isNoAddress;
}
}
},
methods: {
focusOnAddress() {
const addressSelector = document.getElementById('addressSelector');
addressSelector.focus();
},
updateMapCenter(point) {
//console.log('point', point);
this.addressMap.center[0] = point.coordinates[1]; // TODO use reverse()
this.addressMap.center[1] = point.coordinates[0];
this.$refs.addressMap.update(); // cast child methods
}
}
};
</script>
<style lang="scss">
div.address-form {
h4.h3 {
font-weight: bold;
}
div#address_map {
height: 400px;
width: 100%;
}
}
</style>

View File

@ -1,4 +1,5 @@
<template>
<div class="chill-entity entity-address my-3">
<div class="address multiline">
<p v-if="address.text"
class="street">
@ -6,13 +7,14 @@
</p>
<p v-if="address.postcode"
class="postcode">
{{ address.postcode.name }}
{{ address.postcode.code }} {{ address.postcode.name }}
</p>
<p v-if="address.country"
class="country">
{{ address.country.name.fr }}
</p>
</div>
<div>
<div v-if="address.floor">
<span class="floor">
<b>{{ $t('floor') }}</b>: {{ address.floor }}
@ -49,16 +51,12 @@
</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ShowAddress',
props: ['address'],
data() {
return {
name: "ShowAddress",
props: ['address']
}
},
};
</script>

View File

@ -0,0 +1,101 @@
<template>
<div v-if="insideModal == false" class="loading">
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
<span class="sr-only">{{ $t('loading') }}</span>
</div>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
{{ errorMsg }}
</div>
<div v-if="flag.success" class="alert alert-success">
{{ $t(getSuccessText) }}
</div>
<show-address :address="address"></show-address>
<div v-if="showDateFrom" class='address-valid date-since'>
<h3>{{ $t(getValidFromDateText) }}</h3>
<div class="input-group mb-3">
<span class="input-group-text" id="validFrom"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control form-control-lg" name="validFrom"
v-bind:placeholder="$t(getValidFromDateText)"
v-model="validFrom"
aria-describedby="validFrom"
/>
</div>
</div>
<ul v-if="insideModal == false"
class="record_actions sticky-form-buttons">
<li class="cancel">
<a class="btn btn-cancel" v-bind:href="context.backUrl">
{{ $t('back_to_the_list') }}</a>
</li>
<li>
<a @click.prevent="$emit('openEditPane')"
class="btn btn-update">
{{ $t('action.edit')}}
</a>
</li>
<li>
<a class="btn btn-save"
@click.prevent="$emit('submitAddress')">
{{ $t('action.save')}}
</a>
</li>
</ul>
</template>
<script>
import { dateToISO, ISOToDate, ISOToDatetime } from 'ChillMainAssets/chill/js/date.js';
import ShowAddress from './ShowAddress.vue';
export default {
name: 'ShowAddressPane',
components: {
ShowAddress
},
props: [
'context',
'options',
'default',
'flag',
'entity',
'valid',
'errorMsg',
'insideModal'
],
emits: ['openEditPane', 'submitAddress'], //?
computed: {
address() {
return this.entity.address;
},
validFrom: {
set(value) {
this.valid.from = ISOToDate(value);
},
get() {
return dateToISO(this.valid.from);
}
},
getValidFromDateText() {
return (this.context.entity.type === 'household') ? 'move_date' : 'validFrom';
},
getSuccessText() {
switch (this.context.entity.type) {
/*
case 'household':
return (this.context.edit) ? 'household_address_edit_success' : 'household_address_move_success';
case 'person':
return (this.context.edit) ? 'person_address_edit_success' : 'person_address_creation_success';
*/
default:
return (this.context.edit) ? 'address_edit_success' : 'address_new_success';
}
},
showDateFrom() {
return !this.context.edit && !this.options.hideDateFrom;
}
}
};
</script>

View File

@ -0,0 +1,55 @@
const addressMessages = {
fr: {
add_an_address_title: 'Créer une adresse',
edit_an_address_title: 'Modifier une adresse',
create_a_new_address: 'Créer une nouvelle adresse',
edit_address: 'Modifier l\'adresse',
select_an_address_title: 'Sélectionner une adresse',
fill_an_address: 'Compléter l\'adresse',
select_country: 'Choisir le pays',
country: 'Pays',
select_city: 'Choisir une localité',
city: 'Localité',
other_city: 'Autre localité',
select_address: 'Choisir une adresse',
address: 'Adresse',
other_address: 'Autre adresse',
create_address: 'Adresse inconnue. Cliquez ici pour créer une nouvelle adresse',
isNoAddress: 'Pas d\'adresse complète',
street: 'Nom de rue',
streetNumber: 'Numéro',
floor: 'Étage',
corridor: 'Couloir',
steps: 'Escalier',
flat: 'Appartement',
buildingName: 'Nom du bâtiment',
extra: 'Complément d\'adresse',
distribution: 'Service particulier de distribution',
create_postal_code: 'Localité inconnue. Cliquez ici pour créer une nouvelle localité',
postalCode_name: 'Nom',
postalCode_code: 'Code postal',
date: 'Date de la nouvelle adresse',
validFrom: 'Date de la nouvelle adresse',
back_to_the_list: 'Retour à la liste',
loading: 'chargement en cours...',
address_new_success: 'La nouvelle adresse est enregistrée',
address_edit_success: 'L\'adresse a été mise à jour',
// person specific
add_an_address_to_person: 'Ajouter l\'adresse à la personne',
person_address_creation_success: 'La nouvelle adresse de la personne est enregistrée',
person_address_edit_success: 'L\'adresse de la personne a été mise à jour',
// household specific
move_date: 'Date du déménagement',
select_a_existing_address: 'Sélectionner une adresse existante',
add_an_address_to_household: 'Enregistrer',
household_address_move_success: 'La nouvelle adresse du ménage est enregistrée',
household_address_edit_success: 'L\'adresse du ménage a été mise à jour',
}
};
export {
addressMessages
};

View File

@ -1,16 +1,13 @@
import { createApp } from 'vue'
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { addressMessages } from './js/i18n'
import { store } from './store'
import { createApp } from 'vue';
import App from './App.vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { addressMessages } from './i18n';
const i18n = _createI18n( addressMessages );
const app = createApp({
template: `<app></app>`,
})
.use(store)
.use(i18n)
.component('app', App)
.mount('#address');

View File

@ -1,42 +0,0 @@
const addressMessages = {
fr: {
add_an_address_title: 'Créer une adresse',
edit_an_address_title: 'Modifier une adresse',
create_a_new_address: 'Créer une nouvelle adresse',
edit_address: 'Modifier l\'adresse',
select_an_address_title: 'Sélectionner une adresse',
fill_an_address: 'Compléter l\'adresse',
select_country: 'Choisir le pays',
country: 'Pays',
select_city: 'Choisir une localité',
city: 'Localité',
other_city: 'Autre localité',
select_address: 'Choisir une adresse',
address: 'Adresse',
other_address: 'Autre adresse',
create_address: 'Adresse inconnue. Cliquez ici pour créer une nouvelle adresse',
isNoAddress: 'Pas d\'adresse complète',
street: 'Nom de rue',
streetNumber: 'Numéro',
floor: 'Étage',
corridor: 'Couloir',
steps: 'Escalier',
flat: 'Appartement',
buildingName: 'Nom du bâtiment',
extra: 'Complément d\'adresse',
distribution: 'Service particulier de distribution',
create_postal_code: 'Localité inconnue. Cliquez ici pour créer une nouvelle localité',
postalCode_name: 'Nom',
postalCode_code: 'Code postal',
date: 'Date de la nouvelle adresse',
add_an_address_to_person: 'Ajouter l\'adresse à la personne',
validFrom: 'Date de la nouvelle adresse',
back_to_the_list: 'Retour à la liste',
person_address_creation_success: 'La nouvelle adresse de la personne est enregistrée',
loading: 'chargement en cours...'
}
};
export {
addressMessages
};

View File

@ -0,0 +1,14 @@
import 'es6-promise/auto';
import { createStore } from 'vuex';
const debug = process.env.NODE_ENV !== 'production';
const store = createStore({
strict: debug,
state: {},
getters: {},
mutations: {},
actions: {},
});
export { store };

View File

@ -1,158 +0,0 @@
import 'es6-promise/auto';
import { createStore } from 'vuex';
import { patchAddress, postAddress, postPostalCode, postAddressToPerson, getAddress } from '../api'
const debug = process.env.NODE_ENV !== 'production';
const store = createStore({
strict: debug,
state: {
address: {},
editAddress: {}, //TODO or should be address?
person: {},
errorMsg: [],
loading: false,
success: false
},
getters: {
},
mutations: {
catchError(state, error) {
state.errorMsg.push(error);
},
addAddress(state, address) {
console.log('@M addAddress address', address);
state.address = address;
},
updateAddress(state, address) {
console.log('@M updateAddress address', address);
state.address = address;
},
addAddressToPerson(state, person) {
console.log('@M addAddressToPerson person', person);
state.person = person;
},
addDateToAddress(state, validFrom) {
console.log('@M addDateToAddress address.validFrom', validFrom);
state.validFrom = validFrom;
},
getEditAddress(state, address) {
console.log('@M getEditAddress address', address);
state.editAddress = address;
},
setLoading(state, b) {
state.loading = b;
},
setSuccess(state, b) {
state.success = b;
}
},
actions: {
addAddress({ commit }, payload) {
console.log('@A addAddress payload', payload);
commit('setLoading', true);
if('newPostalCode' in payload){
let postalCodeBody = payload.newPostalCode;
postalCodeBody = Object.assign(postalCodeBody, {'origin': 3});
postPostalCode(postalCodeBody)
.then(postalCode => {
let body = payload;
body.postcode = {'id': postalCode.id},
postAddress(body)
.then(address => new Promise((resolve, reject) => {
commit('addAddress', address);
resolve();
commit('setLoading', false);
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
})
} else {
postAddress(payload)
.then(address => new Promise((resolve, reject) => {
commit('addAddress', address);
resolve();
commit('setLoading', false);
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
}
},
addDateToAddressAndAddressToPerson({ commit }, payload) {
console.log('@A addDateToAddressAndAddressToPerson payload', payload);
commit('setLoading', true);
patchAddress(payload.addressId, payload.body)
.then(address => new Promise((resolve, reject) => {
commit('addDateToAddress', address.validFrom);
resolve();
}).then(
postAddressToPerson(payload.personId, payload.addressId)
.then(person => new Promise((resolve, reject) => {
commit('addAddressToPerson', person);
commit('setLoading', false);
commit('setSuccess', true);
window.location.assign(payload.backUrl);
resolve();
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
})
))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
},
updateAddress({ commit }, payload) {
console.log('@A updateAddress payload', payload);
if('newPostalCode' in payload.newAddress){ // TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
let postalCodeBody = payload.newAddress.newPostalCode;
postalCodeBody = Object.assign(postalCodeBody, {'origin': 3});
postPostalCode(postalCodeBody)
.then(postalCode => {
let body = payload.newAddress;
body.postcode = {'id': postalCode.id },
patchAddress(payload.addressId, body)
.then(address => new Promise((resolve, reject) => {
commit('updateAddress', address);
resolve();
}))
.catch((error) => {
commit('catchError', error);
});
})
} else {
patchAddress(payload.addressId, payload.newAddress)
.then(address => new Promise((resolve, reject) => {
commit('updateAddress', address);
resolve();
}))
.catch((error) => {
commit('catchError', error);
});
}
},
getEditAddress({ commit }, payload) {
console.log('@A getEditAddress payload', payload);
getAddress(payload).then(address => new Promise((resolve, reject) => {
commit('getEditAddress', address);
resolve();
}))
.catch((error) => {
commit('catchError', error);
});
},
}
});
export { store };

View File

@ -71,7 +71,8 @@ export default {
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-enter
.modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
@ -80,4 +81,9 @@ export default {
font-size: 1.5rem;
font-weight: bold;
}
div.modal-footer {
button:first-child {
margin-right: auto;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<a class="btn" target="_blank"
<a class="btn btn-sm" target="_blank"
:class="classAction"
:title="$t(titleAction)"
@click="openModal">
@ -44,13 +44,11 @@
<template v-slot:footer>
<button v-if="action === 'show'"
@click="changeActionTo('edit')"
class="btn btn-update"> <!-- @click.prevent="$emit('..', ..)" -->
class="btn btn-update">
</button>
<button v-else
class="btn btn-save"
@click="saveAction"
> <!--
-->
@click="saveAction">
{{ $t('action.save')}}
</button>
</template>

View File

@ -32,6 +32,8 @@ const messages = {
remove: "Enlever",
delete: "Supprimer",
save: "Enregistrer",
valid: "Valider",
valid_and_see: "Valider et voir",
add: "Ajouter",
show_modal: "Ouvrir une modale",
ok: "OK",

View File

@ -71,12 +71,12 @@
{% macro validity(address, options) %}
{%- if options['with_valid_from'] == true -%}
<span class="address-valid address-since">
<span class="address-valid date-since">
{{ 'Since %date%'|trans( { '%date%' : address.validFrom|format_date('long') } ) }}
</span>
{%- endif -%}
{%- if options['with_valid_to'] == true -%}
<span class="address-valid address-until">
<span class="address-valid date-until">
{{ 'Until %date%'|trans( { '%date%' : address.validTo|format_date('long') } ) }}
</span>
{%- endif -%}

View File

@ -0,0 +1,59 @@
<?php
namespace Chill\MainBundle\Search\Entity;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Search\SearchApiInterface;
use Chill\MainBundle\Search\SearchApiQuery;
class SearchUserApiProvider implements SearchApiInterface
{
private UserRepository $userRepository;
/**
* @param UserRepository $userRepository
*/
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function provideQuery(string $pattern, array $parameters): SearchApiQuery
{
$query = new SearchApiQuery();
$query
->setSelectKey("user")
->setSelectJsonbMetadata("jsonb_build_object('id', u.id)")
->setSelectPertinence("GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical),
SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))", [ $pattern, $pattern ])
->setFromClause("users AS u")
->setWhereClause("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15
OR
SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15
", [ $pattern, $pattern ]);
return $query;
}
public function supportsTypes(string $pattern, array $types, array $parameters): bool
{
return \in_array('user', $types);
}
public function prepare(array $metadatas): void
{
$ids = \array_map(fn($m) => $m['id'], $metadatas);
$this->userRepository->findBy([ 'id' => $ids ]);
}
public function supportsResult(string $key, array $metadatas): bool
{
return $key === 'user';
}
public function getResult(string $key, array $metadata, float $pertinence)
{
return $this->userRepository->find($metadata['id']);
}
}

View File

@ -2,6 +2,7 @@
namespace Chill\MainBundle\Search;
use Chill\MainBundle\Search\Entity\SearchUserApiProvider;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\PersonBundle\Search\SearchPersonApiProvider;
@ -25,12 +26,14 @@ class SearchApi
EntityManagerInterface $em,
SearchPersonApiProvider $searchPerson,
ThirdPartyApiSearch $thirdPartyApiSearch,
SearchUserApiProvider $searchUser,
PaginatorFactory $paginator
)
{
$this->em = $em;
$this->providers[] = $searchPerson;
$this->providers[] = $thirdPartyApiSearch;
$this->providers[] = $searchUser;
$this->paginator = $paginator;
}
@ -41,6 +44,10 @@ class SearchApi
{
$queries = $this->findQueries($pattern, $types, $parameters);
if (0 === count($queries)) {
throw new SearchApiNoQueryException($pattern, $types, $parameters);
}
$total = $this->countItems($queries, $types, $parameters);
$paginator = $this->paginator->create($total);
@ -49,9 +56,7 @@ class SearchApi
$this->prepareProviders($rawResults);
$results = $this->buildResults($rawResults);
$collection = new Collection($results, $paginator);
return $collection;
return new Collection($results, $paginator);
}
private function findQueries($pattern, array $types, array $parameters): array

View File

@ -0,0 +1,23 @@
<?php
namespace Chill\MainBundle\Search;
use Throwable;
class SearchApiNoQueryException extends \RuntimeException
{
private string $pattern;
private array $types;
private array $parameters;
public function __construct(string $pattern = "", array $types = [], array $parameters = [], $code = 0, Throwable $previous = null)
{
$typesStr = \implode(", ", $types);
$message = "No query for this search: pattern : {$pattern}, types: {$typesStr}";
$this->pattern = $pattern;
$this->types = $types;
$this->parameters = $parameters;
parent::__construct($message, $code, $previous);
}
}

View File

@ -127,8 +127,6 @@ paths:
- person
- thirdparty
description: >
**Warning**: This is currently a stub (not really implemented
The search is performed across multiple entities. The entities must be listed into
`type` parameters.
@ -152,6 +150,7 @@ paths:
enum:
- person
- thirdparty
- user
responses:
200:
description: "OK"

View File

@ -7,3 +7,8 @@ services:
Chill\MainBundle\Search\SearchApi:
autowire: true
autoconfigure: true
Chill\MainBundle\Search\Entity\:
autowire: true
autoconfigure: true
resource: '../../Search/Entity'

View File

@ -99,6 +99,22 @@ class Household
return $this;
}
/**
* Force an address starting at the current day
* on the Household.
*
* This will force the startDate's address on today.
*
* Used on household creation.
*
* @Serializer\Groups({"create"})
*/
public function setForceAddress(Address $address)
{
$address->setValidFrom(new \DateTime('today'));
$this->addAddress($address);
}
/**
* @param Address $address
*/
@ -128,7 +144,7 @@ class Household
$at = $at === null ? new \DateTime('today') : $at;
$addrs = $this->getAddresses()->filter(function (Address $a) use ($at) {
return $a->getValidFrom() < $at && (
return $a->getValidFrom() <= $at && (
NULL === $a->getValidTo() || $at < $a->getValidTo()
);
});

View File

@ -0,0 +1,374 @@
import { createStore } from 'vuex';
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js';
import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js';
const debug = process.env.NODE_ENV !== 'production';
const concerned = window.household_members_editor_data.persons.map(p => {
return {
person: p,
position: null,
allowRemove: false,
holder: false,
comment: "",
};
});
console.log('expand suggestions', window.household_members_editor_expand_suggestions === 1);
const store = createStore({
strict: debug,
state: {
concerned,
household: window.household_members_editor_data.household,
positions: window.household_members_editor_data.positions.sort((a, b) => {
if (a.ordering < b.ordering) {
return -1;
}
if (a.ordering > b.ordering) {
return 1;
}
return 0;
}),
startDate: new Date(),
allowHouseholdCreate: window.household_members_editor_data.allowHouseholdCreate,
allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch,
allowLeaveWithoutHousehold: window.household_members_editor_data.allowLeaveWithoutHousehold,
forceLeaveWithoutHousehold: false,
householdSuggestionByAccompanyingPeriod: [],
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
addressesSuggestion: [],
warnings: [],
errors: []
},
getters: {
isHouseholdNew(state) {
if (state.household === null) {
return false;
}
return !Number.isInteger(state.household.id);
},
hasHousehold(state) {
return state.household !== null;
},
hasHouseholdOrLeave(state) {
return state.household !== null || state.forceLeaveWithoutHousehold;
},
hasHouseholdSuggestion(state, getters) {
return getters.filterHouseholdSuggestionByAccompanyingPeriod.length > 0;
},
countHouseholdSuggestion(state, getters) {
return getters.filterHouseholdSuggestionByAccompanyingPeriod.length;
},
filterHouseholdSuggestionByAccompanyingPeriod(state) {
if (state.household === null) {
return state.householdSuggestionByAccompanyingPeriod;
}
return state.householdSuggestionByAccompanyingPeriod
.filter(h => h.id !== state.household.id)
;
},
hasPersonsWellPositionnated(state, getters) {
return getters.needsPositionning === false
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
},
persons(state) {
return state.concerned.map(conc => conc.person);
},
concUnpositionned(state) {
return state.concerned
.filter(conc => conc.position === null)
;
},
positions(state) {
return state.positions;
},
personByPosition: (state) => (position_id) => {
return state.concerned
.filter(conc =>
conc.position !== null ? conc.position.id === position_id : false
)
.map(conc => conc.person)
;
},
concByPosition: (state) => (position_id) => {
return state.concerned
.filter(conc =>
conc.position !== null ? conc.position.id === position_id : false
)
;
},
concByPersonId: (state) => (person_id) => {
return state.concerned
.find(conc => conc.person.id === person_id)
;
},
needsPositionning(state) {
return state.forceLeaveWithoutHousehold === false;
},
buildPayload: (state) => {
let
conc,
payload_conc,
payload = {
concerned: [],
destination: null
}
;
if (state.forceLeaveWithoutHousehold === false) {
payload.destination = {
id: state.household.id,
type: state.household.type
};
}
for (let i in state.concerned) {
conc = state.concerned[i];
payload_conc = {
person: {
id: conc.person.id,
type: conc.person.type
},
start_date: {
datetime: datetimeToISO(state.startDate)
}
};
if (state.forceLeaveWithoutHousehold === false) {
payload_conc.position = {
id: conc.position.id,
type: conc.position.type
};
payload_conc.holder = conc.holder;
payload_conc.comment = conc.comment;
}
payload.concerned.push(payload_conc);
}
return payload;
},
},
mutations: {
addConcerned(state, person) {
let persons = state.concerned.map(conc => conc.person.id);
if (!persons.includes(person.id)) {
state.concerned.push({
person,
position: null,
allowRemove: true,
holder: false,
comment: "",
});
} else {
console.err("person already included");
}
},
markPosition(state, { person_id, position_id}) {
let
position = state.positions.find(pos => pos.id === position_id),
conc = state.concerned.find(c => c.person.id === person_id);
conc.position = position;
},
setComment(state, {conc, comment}) {
conc.comment = comment;
},
toggleHolder(state, conc) {
conc.holder = !conc.holder;
},
removePosition(state, conc) {
conc.holder = false;
conc.position = null;
},
removeConcerned(state, conc) {
state.concerned = state.concerned.filter(c =>
c.person.id !== conc.person.id
)
},
createHousehold(state) {
state.household = { type: 'household', members: [], current_address: null,
current_members_id: [] };
state.forceLeaveWithoutHousehold = false;
},
removeHousehold(state) {
state.household = null;
state.forceLeaveWithoutHousehold = false;
},
setHouseholdAddress(state, address) {
if (null === state.household) {
console.error("no household");
throw new Error("No household");
}
state.household.current_address = address;
state.household.force_new_address = address;
},
forceLeaveWithoutHousehold(state) {
state.household = null;
state.forceLeaveWithoutHousehold = true;
},
selectHousehold(state, household) {
state.household = household;
state.forceLeaveWithoutHousehold = false;
},
setHouseholdSuggestionByAccompanyingPeriod(state, households) {
let existingIds = state.householdSuggestionByAccompanyingPeriod
.map(h => h.id);
for (let i in households) {
if (!existingIds.includes(households[i].id)) {
state.householdSuggestionByAccompanyingPeriod.push(households[i]);
}
}
},
setStartDate(state, dateI) {
state.startDate = dateI;
},
toggleHouseholdSuggestion(state) {
state.showHouseholdSuggestion = !state.showHouseholdSuggestion;
},
setWarnings(state, warnings) {
state.warnings = warnings;
// reset errors, which should come from servers
state.errors.splice(0, state.errors.length);
},
setErrors(state, errors) {
state.errors = errors;
},
addAddressesSuggestion(state, addresses) {
let existingIds = state.addressesSuggestion
.map(a => a.id);
for (let i in addresses) {
if (!existingIds.includes(addresses[i].id)) {
state.addressesSuggestion.push(addresses[i]);
}
}
}
},
actions: {
addConcerned({ commit, dispatch }, person) {
commit('addConcerned', person);
dispatch('computeWarnings');
dispatch('fetchAddressSuggestions');
},
markPosition({ commit, state, dispatch }, { person_id, position_id }) {
commit('markPosition', { person_id, position_id });
dispatch('computeWarnings');
},
toggleHolder({ commit, dispatch }, conc) {
commit('toggleHolder', conc);
dispatch('computeWarnings');
},
removePosition({ commit, dispatch }, conc) {
commit('removePosition', conc);
dispatch('computeWarnings');
},
removeConcerned({ commit, dispatch }, conc) {
commit('removeConcerned', conc);
dispatch('computeWarnings');
dispatch('fetchAddressSuggestions');
},
removeHousehold({ commit, dispatch }) {
commit('removeHousehold');
dispatch('computeWarnings');
},
createHousehold({ commit, dispatch }) {
commit('createHousehold');
dispatch('computeWarnings');
},
forceLeaveWithoutHousehold({ commit, dispatch }) {
commit('forceLeaveWithoutHousehold');
dispatch('computeWarnings');
},
selectHousehold({ commit }, h) {
commit('selectHousehold', h);
dispatch('computeWarnings');
},
setStartDate({ commit, dispatch }, date) {
commit('setStartDate', date);
dispatch('computeWarnings');
},
setComment({ commit }, payload) {
commit('setComment', payload);
},
fetchHouseholdSuggestionForConcerned({ commit, state }, person) {
fetchHouseholdSuggestionByAccompanyingPeriod(person.id)
.then(households => {
commit('setHouseholdSuggestionByAccompanyingPeriod', households);
});
},
fetchAddressSuggestions({ commit, state }) {
for (let i in state.concerned) {
fetchAddressSuggestionByPerson(state.concerned[i].person.id)
.then(addresses => {
commit('addAddressesSuggestion', addresses);
})
.catch(e => {
console.log(e);
});
}
},
computeWarnings({ commit, state, getters }) {
let warnings = [],
payload;
if (!getters.hasHousehold && !state.forceLeaveWithoutHousehold) {
warnings.push({ m: 'household_members_editor.add_destination', a: {} });
}
if (state.concerned.length === 0) {
warnings.push({ m: 'household_members_editor.add_at_least_onePerson', a: {} });
}
if (getters.concUnpositionned.length > 0
&& !state.forceLeaveWithoutHousehold) {
warnings.push({ m: 'household_members_editor.give_a_position_to_every_person', a: {} })
}
commit('setWarnings', warnings);
},
confirm({ getters, state, commit }) {
let payload = getters.buildPayload,
errors = [],
person_id,
household_id,
error
;
householdMove(payload).then(household => {
if (household === null) {
person_id = getters.persons[0].id;
window.location.replace(`/fr/person/${person_id}/general`);
} else {
if (household.type === 'household') {
household_id = household.id;
// nothing to do anymore here, bye-bye !
window.location.replace(`/fr/person/household/${household_id}/summary`);
} else {
// we assume the answer was 422...
error = household;
for (let i in error.violations) {
let e = error.violations[i];
errors.push(e.title);
}
commit('setErrors', errors);
}
}
});
},
}
});
store.dispatch('computeWarnings');
store.dispatch('fetchAddressSuggestions');
if (concerned.length > 0) {
concerned.forEach(c => {
store.dispatch('fetchHouseholdSuggestionForConcerned', c.person);
});
}
export { store };

View File

@ -9,6 +9,7 @@
<origin-demand></origin-demand>
<requestor></requestor>
<social-issue></social-issue>
<course-location></course-location>
<referrer></referrer>
<resources></resources>
<comment v-if="accompanyingCourse.step === 'DRAFT'"></comment>
@ -24,6 +25,7 @@ import OriginDemand from './components/OriginDemand.vue';
import PersonsAssociated from './components/PersonsAssociated.vue';
import Requestor from './components/Requestor.vue';
import SocialIssue from './components/SocialIssue.vue';
import CourseLocation from './components/CourseLocation.vue';
import Referrer from './components/Referrer.vue';
import Resources from './components/Resources.vue';
import Comment from './components/Comment.vue';
@ -38,18 +40,23 @@ export default {
PersonsAssociated,
Requestor,
SocialIssue,
CourseLocation,
Referrer,
Resources,
Comment,
Confirm,
},
computed: mapState([
'accompanyingCourse'
'accompanyingCourse',
'addressContext'
])
};
</script>
<style lang="scss">
@import 'ChillMainAssets/module/bootstrap/shared';
$chill-accourse-context: #718596;
div#accompanying-course {
div.vue-component {
h2 {
@ -59,7 +66,7 @@ export default {
position: absolute;
content: "\f142"; //ellipsis-v
font-family: "ForkAwesome";
color: #718596ab;
color: tint-color($chill-accourse-context, 10%);
left: -22px;
top: 4px;
}
@ -70,10 +77,10 @@ export default {
}
padding: 0em 0em;
margin: 1em 0;
border: 1px dotted #718596ab;
border: 1px dotted tint-color($chill-accourse-context, 10%);
border-radius: 5px;
border-left: 1px dotted #718596ab;
border-right: 1px dotted #718596ab;
border-left: 1px dotted tint-color($chill-accourse-context, 10%);
border-right: 1px dotted tint-color($chill-accourse-context, 10%);
dd {
margin-left: 1em;
}
@ -83,6 +90,5 @@ export default {
table {
}
}
}
</style>

View File

@ -21,7 +21,7 @@ const getAccompanyingCourse = (id) => {
* @body Object - dictionary with changes to post
*/
const patchAccompanyingCourse = (id, body) => {
console.log('body', body);
//console.log('body', body);
const url = `/api/1.0/person/accompanying-course/${id}.json`;
return fetch(url, {
method: 'PATCH',

View File

@ -5,7 +5,8 @@
</teleport>
<teleport to="#header-accompanying_course-name #banner-status">
<span v-if="accompanyingCourse.step === 'DRAFT'" class="d-md-block">
<span v-if="accompanyingCourse.step === 'DRAFT'"
class="text-md-end d-md-block">
<span class="badge bg-secondary">
{{ $t('course.step.draft') }}
</span>

View File

@ -0,0 +1,68 @@
<template>
<li>
<button class="btn btn-sm btn-secondary"
@click="modal.showModal = true"
:title="$t('courselocation.assign_course_address')">
<i class="fa fa-map-marker"></i>
</button>
</li>
<teleport to="body">
<modal v-if="modal.showModal" :modalDialogClass="modal.modalDialogClass" @close="modal.showModal = false">
<template v-slot:header>
<h2 class="modal-title">{{ $t('courselocation.sure') }}</h2>
</template>
<template v-slot:body>
<show-address :address="person.current_household_address"></show-address>
<p>{{ $t('courselocation.sure_description') }}</p>
</template>
<template v-slot:footer>
<button class="btn btn-danger" @click="assignAddress">
{{ $t('courselocation.ok') }}
</button>
</template>
</modal>
</teleport>
</template>
<script>
import {mapState} from "vuex";
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import ShowAddress from "ChillMainAssets/vuejs/Address/components/ShowAddress";
export default {
name: "ButtonLocation",
components: {
ShowAddress,
Modal,
},
props: ['person'],
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-dialog-centered modal-md"
}
}
},
computed: {
...mapState({
context: state => state.addressContext
}),
},
methods: {
assignAddress() {
//console.log('assignAddress id', this.person.current_household_address);
let payload = {
entity: this.context.entity.type,
entityId: this.context.entity.id,
locationStatusTo: 'person',
personId: this.person.id
};
this.$store.dispatch('updateLocation', payload);
window.location.assign('#section-50');
this.modal.showModal = false;
}
}
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="vue-component">
<h2><a name="section-60"></a>{{ $t('comment.title') }}</h2>
<h2><a name="section-80"></a>{{ $t('comment.title') }}</h2>
<!--div class="error flash_message" v-if="errors.length > 0">
{{ errors[0] }}
@ -10,24 +10,23 @@
<div>
<form @submit.prevent="submitform">
<label for="content">{{ $t('comment.label') }}</label>
<label class="col-form-label" for="content">{{ $t('comment.label') }}</label>
<div v-if="initialComment">
<ckeditor
name="content"
v-bind:placeholder="$t('comment.content')"
:editor="editor"
v-model="content"
tag-name="textarea">
</ckeditor>
<div v-if="initialComment" class="metadata">
{{ $t('comment.created_by', [
initialComment.creator.text,
$d(initialComment.createdAt.datetime, 'long')
]) }}
</div>
<textarea
name="content"
v-bind:placeholder="$t('comment.content')"
rows="8"
cols="80"
ckeditor="ckeditor"
v-model="content">
</textarea>
<ul class="record_actions">
<li>
<button type="submit" class="btn btn-save">{{ $t('action.save') }}</button>
@ -47,10 +46,17 @@
</template>
<script>
import CKEditor from '@ckeditor/ckeditor5-vue';
import ClassicEditor from "ChillMainAssets/module/ckeditor5";
export default {
name: "Comment",
components: {
ckeditor: CKEditor.component,
},
data() {
return {
editor: ClassicEditor,
formdata: {
type: "accompanying_period_comment",
content: ''
@ -66,7 +72,7 @@ export default {
this.formdata.content = value;
},
get() {
return (this.initialComment)? this.initialComment.content : null;
return (this.initialComment)? this.initialComment.content : {};
}
},
errors() {
@ -75,12 +81,12 @@ export default {
},
methods: {
submitform() {
console.log('submit');
//console.log('submit');
this.$store.dispatch('postFirstComment', this.formdata);
},
removeComment() {
console.log('remove');
this.$store.dispatch('postFirstComment', null);
//console.log('remove');
this.$store.dispatch('postFirstComment', {});
}
}
}
@ -88,12 +94,11 @@ export default {
* TODO
* - [x] delete button in ul record_actions, but not in form
* - [ ] display updatedAt => initialComment fetch PATCH content changes MUST NOT change object id !!
* - [ ] ckeditor integration
*/
</script>
<style lang="scss" scoped>
div.vue-component > div {
//margin: 1em;
<style lang="scss">
div.ck-editor.ck-reset {
margin: 0.6em 0;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="vue-component">
<h2><a name="section-70"></a>
<h2><a name="section-90"></a>
{{ $t('confirm.title') }}
</h2>
@ -65,9 +65,9 @@ export default {
},
methods: {
confirmCourse() {
console.log('@@ CLICK confirmCourse');
//console.log('@@ CLICK confirmCourse');
this.$store.dispatch('confirmAccompanyingCourse');
console.log('confirm last');
//console.log('confirm last');
}
}
}

View File

@ -0,0 +1,147 @@
<template>
<div class="vue-component">
<h2><a name="section-50"></a>
{{ $t('courselocation.title') }}
</h2>
<div class="my-4">
<!-- {# include vue_address component #} -->
<div v-for="error in displayErrors" class="alert alert-danger my-2">
{{ error }}
</div>
<show-address
v-if="accompanyingCourse.location"
:address="accompanyingCourse.location">
</show-address>
<div v-if="isPersonLocation" class="alert alert-success">
{{ $t('courselocation.person_locator', [ accompanyingCourse.personLocation.text ]) }}
</div>
<div v-if="isTemporaryAddress" class="alert alert-warning">
<p>{{ $t('courselocation.temporary_address_must_be_changed') }}</p>
</div>
<ul class="record_actions">
<li>
<add-address
v-if="!isPersonLocation"
:context="context"
:key="addAddress.type"
:options="addAddress.options"
:result="addAddress.result"
@submitAddress="submitTemporaryAddress"
ref="addAddress">
</add-address>
</li>
<li v-if="isPersonLocation">
<button
class="btn btn-remove"
@click="removeAddress"
:title="$t('courselocation.remove_button')">
</button>
</li>
</ul>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress.vue';
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
export default {
name: "CourseLocation",
components: {
AddAddress,
ShowAddress
},
data() {
return {
addAddress: {
type: 'accompanying_course_location',
options: {
/// Options override default.
/// null value take default component value
button: {
text: {
create: 'courselocation.add_temporary_address',
edit: 'courselocation.edit_temporary_address'
}
},
title: {
create: 'courselocation.add_temporary_address',
edit: 'courselocation.edit_temporary_address'
},
/// Display each step in page or Modal
bindModal: {
//step1: false, step2: false
},
hideDateFrom: true
}
}
}
},
computed: {
...mapState({
accompanyingCourse: state => state.accompanyingCourse,
context: state => state.addressContext
}),
isTemporaryAddress() {
return this.accompanyingCourse.locationStatus === 'address';
},
isPersonLocation() {
return this.accompanyingCourse.locationStatus === 'person';
},
isContextEdit() {
return this.context.edit;
}
},
methods: {
initAddressContext() {
let context = {
entity: {
type: this.accompanyingCourse.type,
id: this.accompanyingCourse.id
},
edit: false,
addressId: null
}
if (this.accompanyingCourse.location) {
context['edit'] = true;
context['addressId'] = this.accompanyingCourse.location.address_id
}
this.$store.commit('setAddressContext', context);
},
removeAddress() {
//console.log('remove address');
let payload = {
entity: this.context.entity.type,
entityId: this.context.entity.id,
locationStatusTo: 'none'
};
this.$store.dispatch('updateLocation', payload);
},
displayErrors() {
return this.$refs.addAddress.errorMsg;
},
submitTemporaryAddress() {
//console.log('@@@ click on Submit Temporary Address Button');
let payload = this.$refs.addAddress.submitNewAddress();
payload['locationStatusTo'] = 'address'; // <== temporary, not none, not person
this.$store.dispatch('updateLocation', payload);
this.$store.commit('setEditContextTrue');
}
},
mounted() {
this.initAddressContext();
//console.log('ac.locationStatus', this.accompanyingCourse.locationStatus);
//console.log('ac.location (temporary location)', this.accompanyingCourse.location);
//console.log('ac.personLocation', this.accompanyingCourse.personLocation);
}
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="vue-component">
<h2><a name="section-15"></a>{{ $t('origin.title') }}</h2>
<h2><a name="section-20"></a>{{ $t('origin.title') }}</h2>
<div class="my-4">
<label for="selectOrigin">

View File

@ -69,15 +69,15 @@ export default {
}),
methods: {
removeParticipation(item) {
console.log('@@ CLICK remove participation: item', item);
//console.log('@@ CLICK remove participation: item', item);
this.$store.dispatch('removeParticipation', item);
},
closeParticipation(item) {
console.log('@@ CLICK close participation: item', item);
//console.log('@@ CLICK close participation: item', item);
this.$store.dispatch('closeParticipation', item);
},
addNewPersons({ selected, modal }) {
console.log('@@@ CLICK button addNewPersons', selected);
//console.log('@@@ CLICK button addNewPersons', selected);
selected.forEach(function(item) {
this.$store.dispatch('addParticipation', item);
}, this

View File

@ -12,6 +12,10 @@
</td>
<td>
<ul class="record_actions">
<button-location
v-if="hasCurrentHouseholdAddress"
v-bind:person="participation.person">
</button-location>
<li>
<on-the-fly
v-bind:type="participation.person.type"
@ -34,11 +38,11 @@
</li-->
<li>
<button v-if="!participation.endDate"
class="btn btn-remove"
class="btn btn-sm btn-remove"
v-bind:title="$t('action.remove')"
@click.prevent="$emit('close', participation)">
</button>
<button v-else class="btn btn-remove disabled"></button>
<button v-else class="btn btn-sm btn-remove" disabled></button>
</li>
</ul>
</td>
@ -47,13 +51,24 @@
<script>
import OnTheFly from 'ChillMainAssets/vuejs/_components/OnTheFly.vue';
import ButtonLocation from '../ButtonLocation.vue';
export default {
name: 'PersonItem',
components: {
OnTheFly
OnTheFly,
ButtonLocation
},
props: ['participation'],
emits: ['remove', 'close'],
computed: {
hasCurrentHouseholdAddress() {
if ( !this.participation.endDate
&& this.participation.person.current_household_address !== null ) {
return true;
}
return false;
}
}
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="vue-component">
<h2><a name="section-40"></a>{{ $t('referrer.title') }}</h2>
<h2><a name="section-60"></a>{{ $t('referrer.title') }}</h2>
<div class="my-4">
<label class="col-form-label" for="selectReferrer">

View File

@ -1,7 +1,7 @@
<template>
<div class="vue-component">
<h2><a name="section-20"></a>{{ $t('requestor.title') }}</h2>
<h2><a name="section-30"></a>{{ $t('requestor.title') }}</h2>
<div v-if="accompanyingCourse.requestor" class="flex-table">

View File

@ -1,7 +1,7 @@
<template>
<div class="vue-component">
<h2><a name="section-50"></a>{{ $t('resources.title')}}</h2>
<h2><a name="section-70"></a>{{ $t('resources.title')}}</h2>
<div>
<label class="col-form-label">{{ $tc('resources.counter', counter) }}</label>
@ -68,11 +68,11 @@ export default {
}),
methods: {
removeResource(item) {
console.log('@@ CLICK remove resource: item', item);
//console.log('@@ CLICK remove resource: item', item);
this.$store.dispatch('removeResource', item);
},
addNewPersons({ selected, modal }) {
console.log('@@@ CLICK button addNewPersons', selected);
//console.log('@@@ CLICK button addNewPersons', selected);
selected.forEach(function(item) {
this.$store.dispatch('addResource', item);
}, this

View File

@ -20,6 +20,10 @@
<td>
<ul class="record_actions">
<button-location
v-if="hasCurrentHouseholdAddress"
v-bind:person="resource.resource">
</button-location>
<li>
<on-the-fly
v-bind:type="resource.resource.type"
@ -36,7 +40,7 @@
</li>
<li>
<button
class="btn btn-remove"
class="btn btn-sm btn-remove"
v-bind:title="$t('action.remove')"
@click.prevent="$emit('remove', resource)">
</button>
@ -48,13 +52,24 @@
<script>
import OnTheFly from 'ChillMainAssets/vuejs/_components/OnTheFly.vue';
import ButtonLocation from '../ButtonLocation.vue';
export default {
name: 'ResourceItem',
components: {
OnTheFly
OnTheFly,
ButtonLocation
},
props: ['resource'],
emits: ['remove']
emits: ['remove'],
computed: {
hasCurrentHouseholdAddress() {
if ( !this.resource.resource.type === 'person'
&& this.resource.resource.current_household_address !== null ) {
return true;
}
return false;
}
}
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="vue-component">
<h2><a name="section-30"></a>{{ $t('social_issue.title') }}</h2>
<h2><a name="section-40"></a>{{ $t('social_issue.title') }}</h2>
<div class="my-4">
<!--label for="field">{{ $t('social_issue.label') }}</label

View File

@ -1,6 +1,6 @@
<template>
<a
v-if="item.key <= 5"
v-if="item.key <= 7"
:href="item.name"
:class="{ 'active': isActive }"
>

View File

@ -12,7 +12,6 @@ const root = window.vueRootComponent;
* Load all App component, for AccompanyingCourse edition page
*/
if (root === 'app') {
initPromise.then(store => {
const i18n = _createI18n(appMessages);
@ -31,7 +30,6 @@ if (root === 'app') {
* Load only Banner sub-component, for all others AccompanyingCourse page
*/
if (root === 'banner') {
initPromise.then(store => {
const i18n = _createI18n(appMessages);

View File

@ -1,4 +1,5 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
import { addressMessages } from 'ChillMainAssets/vuejs/Address/i18n';
const appMessages = {
fr: {
@ -63,6 +64,18 @@ const appMessages = {
title: "Problématiques sociales",
label: "Choisir les problématiques sociales",
},
courselocation: {
title: "Localisation du parcours",
add_temporary_address: "Ajouter une adresse temporaire",
edit_temporary_address: "Modifier l'adresse temporaire",
assign_course_address: "Désigner comme l'adresse du parcours",
remove_button: "Enlever l'adresse",
temporary_address_must_be_changed: "Cette addresse est temporaire et doit être remplacée par celle d'un usager de référence.",
sure: "Êtes-vous sûr ?",
sure_description: "Voulez-vous faire de cette adresse l'adresse du parcours ?",
ok: "Désigner comme adresse du parcours",
person_locator: "Parcours localisé auprès de {0}",
},
referrer: {
title: "Référent du parcours",
label: "Vous pouvez choisir un TMS ou vous assigner directement comme référent",
@ -94,7 +107,7 @@ const appMessages = {
}
};
Object.assign(appMessages.fr, personMessages.fr);
Object.assign(appMessages.fr, personMessages.fr, addressMessages.fr);
export {
appMessages

View File

@ -8,6 +8,7 @@ import { getAccompanyingCourse,
postResource,
postSocialIssue } from '../api';
const debug = process.env.NODE_ENV !== 'production';
const id = window.accompanyingCourseId;
@ -20,6 +21,7 @@ let initPromise = getAccompanyingCourse(id)
},
state: {
accompanyingCourse: accompanying_course,
addressContext: {},
errorMsg: []
},
getters: {
@ -92,6 +94,20 @@ let initPromise = getAccompanyingCourse(id)
confirmAccompanyingCourse(state, response) {
//console.log('### mutation: confirmAccompanyingCourse: response', response);
state.accompanyingCourse.step = response.step;
},
setAddressContext(state, context) {
//console.log('define location context');
state.addressContext = context;
},
updateLocation(state, r) {
//console.log('### mutation: set location attributes', r);
state.accompanyingCourse.location = r.location;
state.accompanyingCourse.locationStatus = r.locationStatus;
state.accompanyingCourse.personLocation = r.personLocation;
},
setEditContextTrue(state) {
//console.log('### mutation: set edit context = true');
state.addressContext.edit = true;
}
},
actions: {
@ -209,6 +225,30 @@ let initPromise = getAccompanyingCourse(id)
resolve();
})).catch((error) => { commit('catchError', error) });
},
updateLocation({ commit }, payload) {
//console.log('## action: updateLocation', payload.locationStatusTo);
let body = { 'type': payload.entity, 'id': payload.entityId };
let location = {};
if (payload.locationStatusTo === 'person') { // patch for person address (don't remove addressLocation)
location = { 'personLocation': { 'type': 'person', 'id': payload.personId }};
}
else if (payload.locationStatusTo === 'address') { // patch for temporary address
location = { 'personLocation': null, 'addressLocation': { 'id': payload.addressId }};
}
else { // patch to remove person address
location = { 'personLocation': null };
}
Object.assign(body, location);
patchAccompanyingCourse(payload.entityId, body)
.then(accompanyingCourse => new Promise((resolve, reject) => {
commit('updateLocation', {
location: accompanyingCourse.location,
locationStatus: accompanyingCourse.locationStatus,
personLocation: accompanyingCourse.personLocation
});
resolve();
})).catch((error) => { commit('catchError', error) });
},
confirmAccompanyingCourse({ commit }) {
//console.log('## action: confirmAccompanyingCourse');
confirmAccompanyingCourse(id)

View File

@ -1,155 +0,0 @@
<template>
<div class='household__address-move'>
<div class='household__address-move__create'>
<div>
<h2 v-if="!edit">{{ $t('create_a_new_address') }}</h2>
<h2 v-else>{{ $t('edit_a_new_address') }}</h2>
<add-address
@addNewAddress="addNewAddress">
</add-address>
</div>
<div>
<show-address
v-if="newAddress"
v-bind:address="newAddress">
</show-address>
</div>
</div>
<div v-if="!edit" class='household__address-move__valid'>
<h2>{{ $t('move_date') }}</h2>
<div class="input-group mb-3">
<span class="input-group-text" id="validFrom"><i class="fa fa-fw fa-calendar"></i></span>
<input type="date" class="form-control form-control-lg" name="validFrom"
v-bind:placeholder="$t('validFrom')"
v-model="validFrom"
aria-describedby="validFrom" />
</div>
<div v-if="errors.length > 0">
{{ errors }}
</div>
<div v-if="loading">
{{ $t('loading') }}
</div>
<div v-if="success && !edit">
{{ $t('household_address_move_success') }}
</div>
</div>
<div v-if="success && edit">
{{ $t('household_address_edit_success') }}
</div>
<div>
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a :href="backUrl" class="btn btn-cancel">{{ $t('back_to_the_list') }}</a>
</li>
<li v-if="!edit">
<button type="submit" class="btn btn-update" @click="addToHousehold">
{{ $t('add_an_address_to_household') }}
</button>
</li>
</ul>
</div>
</div>
</template>
<script>
import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress.vue';
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
export default {
name: 'App',
components: {
AddAddress,
ShowAddress
},
data() {
return {
edit: window.mode === 'edit',
householdId: window.householdId,
addressId: window.addressId,
backUrl: `/fr/person/household/${householdId}/addresses`, //TODO better way to pass this
validFrom: new Date().toISOString().split('T')[0]
}
},
computed: {
newAddress() {
return this.$store.state.newAddress;
},
errors() {
return this.$store.state.errorMsg;
},
loading() {
return this.$store.state.loading;
},
success() {
return this.$store.state.success;
}
},
methods: {
addNewAddress({ address, modal }) {
console.log('@@@ CLICK button addNewAdress', address);
let createdAddress = {
'isNoAddress': address.isNoAddress,
'street': address.isNoAddress ? '' : address.street,
'streetNumber': address.isNoAddress ? '' : address.streetNumber,
'postcode': {'id': address.selected.city.id},
'floor': address.floor,
'corridor': address.corridor,
'steps': address.steps,
'flat': address.flat,
'buildingName': address.buildingName,
'distribution': address.distribution,
'extra': address.extra
};
if (address.selected.address.point !== undefined){
createdAddress = Object.assign(createdAddress, {
'point': address.selected.address.point.coordinates
});
}
if(address.writeNewPostalCode){
let newPostalCode = address.newPostalCode;
newPostalCode = Object.assign(newPostalCode, {
'country': {'id': address.selected.country.id },
});
createdAddress = Object.assign(createdAddress, {
'newPostalCode': newPostalCode
});
}
if (this.edit){
this.$store.dispatch('updateAddress', {
addressId: this.addressId,
newAddress: createdAddress
});
} else {
this.$store.dispatch('addAddress', createdAddress);
}
modal.showModal = false;
},
addToHousehold() {
this.$store.dispatch('addDateToAddressAndAddressToHousehold', {
householdId: this.householdId,
addressId: this.$store.state.newAddress.address_id,
body: { validFrom: {datetime: `${this.validFrom}T00:00:00+0100`}},
backUrl: this.backUrl
})
},
getEditAddress() {
this.$store.dispatch('getEditAddress', this.addressId);
}
},
mounted() {
if (this.edit) {
this.getEditAddress();
}
},
};
</script>

View File

@ -1,23 +0,0 @@
/*
* Endpoint household
* method POST, post Household instance
*
* @id integer - id of household
* @body Object - dictionary with changes to post
*/
export const postAddressToHousehold = (householdId, addressId) => {
const body = {
'id': addressId
};
const url = `/api/1.0/person/household/${householdId}/address.json`
return fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};

View File

@ -1,25 +0,0 @@
import { createApp } from 'vue'
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { appMessages } from './js/i18n'
import { store } from './store'
import App from './App.vue';
const root = window.vueRootComponent;
/*
* Load all App component, for Household edition page
*/
const i18n = _createI18n(appMessages);
const app = createApp({
template: `<app></app>`,
})
.use(store)
.use(i18n)
.component('app', App)
.mount('#household-address');

View File

@ -1,4 +1,4 @@
import { addressMessages } from 'ChillMainAssets/vuejs/Address/js/i18n'
import { addressMessages } from 'ChillMainAssets/vuejs/Address/i18n'
const appMessages = {
fr: {

View File

@ -1,160 +0,0 @@
import 'es6-promise/auto';
import { createStore } from 'vuex';
import { postAddress, postPostalCode, patchAddress, getAddress } from 'ChillMainAssets/vuejs/Address/api';
import { postAddressToHousehold } from '../api';
const debug = process.env.NODE_ENV !== 'production';
const store = createStore({
strict: debug,
state: {
newAddress: {},
editAddress: {}, //TODO or should be newAddress?
household: {},
validFrom: {},
errorMsg: [],
loading: false,
success: false
},
getters: {
},
mutations: {
catchError(state, error) {
state.errorMsg.push(error);
},
addAddress(state, address) {
console.log('@M addAddress address', address);
state.newAddress = address;
},
updateAddress(state, address) {
console.log('@M updateAddress address', address);
state.newAddress = address;
},
addAddressToHousehold(state, household) {
console.log('@M addAddressToHousehold household', household);
state.household = household;
},
addDateToAddress(state, validFrom) {
console.log('@M addDateToAddress address.validFrom', validFrom);
state.validFrom = validFrom;
},
getEditAddress(state, address) {
console.log('@M getEditAddress address', address);
state.editAddress = address;
},
setLoading(state, b) {
state.loading = b;
},
setSuccess(state, b) {
state.success = b;
}
},
actions: {
addAddress({ commit }, payload) {
console.log('@A addAddress payload', payload);
commit('setLoading', true);
if('newPostalCode' in payload){
postPostalCode(payload.newPostalCode)
.then(postalCode => {
let body = payload;
body.postcode = {'id': postalCode.id },
postAddress(body)
.then(address => new Promise((resolve, reject) => {
commit('addAddress', address);
resolve();
commit('setLoading', false);
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
})
} else {
postAddress(payload)
.then(address => new Promise((resolve, reject) => {
commit('addAddress', address);
resolve();
commit('setLoading', false);
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
}
},
addDateToAddressAndAddressToHousehold({ commit }, payload) {
console.log('@A addDateToAddressAndAddressToHousehold payload', payload);
commit('setLoading', true);
patchAddress(payload.addressId, payload.body)
.then(address => new Promise((resolve, reject) => {
commit('addDateToAddress', address.validFrom);
resolve();
}).then(
postAddressToHousehold(payload.householdId, payload.addressId)
.then(household => new Promise((resolve, reject) => {
commit('addAddressToHousehold', household);
commit('setSuccess', true);
commit('setLoading', false);
window.location.assign(payload.backUrl);
resolve();
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
})
))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
},
updateAddress({ commit }, payload) {
console.log('@A updateAddress payload', payload);
if('newPostalCode' in payload.newAddress){ // TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
let postalCodeBody = payload.newAddress.newPostalCode;
postalCodeBody = Object.assign(postalCodeBody, {'origin': 3});
postPostalCode(postalCodeBody)
.then(postalCode => {
let body = payload.newAddress;
body.postcode = {'id': postalCode.id },
patchAddress(payload.addressId, body)
.then(address => new Promise((resolve, reject) => {
commit('updateAddress', address);
commit('setSuccess', true);
resolve();
}))
.catch((error) => {
commit('catchError', error);
});
})
} else {
patchAddress(payload.addressId, payload.newAddress)
.then(address => new Promise((resolve, reject) => {
commit('updateAddress', address);
commit('setSuccess', true);
resolve();
}))
.catch((error) => {
commit('catchError', error);
});
}
},
getEditAddress({ commit }, payload) {
console.log('@A getEditAddress payload', payload);
getAddress(payload).then(address => new Promise((resolve, reject) => {
commit('getEditAddress', address);
resolve();
}))
.catch((error) => {
commit('catchError', error);
});
},
}
});
export { store };

View File

@ -37,7 +37,20 @@ const fetchHouseholdSuggestionByAccompanyingPeriod = (personId) => {
;
};
const fetchAddressSuggestionByPerson = (personId) => {
const url = `/api/1.0/person/address/suggest/by-person/${personId}.json`;
return window.fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw Error({m: 'Error while fetch address suggestion', status: response.status});
});
}
export {
householdMove,
fetchHouseholdSuggestionByAccompanyingPeriod,
fetchAddressSuggestionByPerson,
};

View File

@ -5,6 +5,38 @@
<div>
<household-viewer :household="household"></household-viewer>
</div>
<div v-if="isHouseholdNew && !hasHouseholdAddress">
<h3>À quelle adresse habite ce ménage ?</h3>
<ul v-if="filterAddressesSuggestion.length > 0">
<li v-for="a in filterAddressesSuggestion">
<show-address :address="a"></show-address>
<button class="btn" @click="setHouseholdAddress(a)">
Le ménage habite cette adresse
</button>
</li>
</ul>
<ul class="record_actions">
<li >
<button class="btn">
Créer une adresse
</button>
</li>
</ul>
</div>
<div v-if="isHouseholdNew && hasHouseholdAddress">
<ul class="record_actions">
<li >
<button class="btn" @click="removeHouseholdAddress">
Supprimer cette adresse
</button>
</li>
</ul>
</div>
</div>
<div v-else-if="isForceLeaveWithoutHousehold">
{{ $t('household_members_editor.household.will_leave_any_household') }}
@ -23,7 +55,7 @@
countHouseholdSuggestion) }}
</button>
</li>
<li v-if="showHouseholdSuggestion">
<li v-if="showHouseholdSuggestion && hasHouseholdSuggestion">
<button
class="btn btn-misc"
@click="toggleHouseholdSuggestion"
@ -46,10 +78,15 @@
<i class="fa fa-sign-out"></i>{{ $t('household_members_editor.household.leave_without_household') }}
</button>
</li>
<li v-if="allowRemoveHousehold">
<button @click="removeHousehold" class="btn">
{{ $t('household_members_editor.household.change') }}
</button>
</li>
</ul>
<div class="householdSuggestions">
<div v-if="showHouseholdSuggestion">
<div v-if="showHouseholdSuggestion && hasHouseholdSuggestion">
<p>{{ $t('household_members_editor.household_for_participants_accompanying_period') }}:</p>
<div class="householdSuggestionList">
<div
@ -98,11 +135,13 @@
import { mapGetters, mapState } from 'vuex';
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
export default {
name: 'Household',
components: {
HouseholdViewer,
ShowAddress,
},
computed: {
...mapGetters([
@ -111,6 +150,8 @@ export default {
'hasHouseholdSuggestion',
'countHouseholdSuggestion',
'filterHouseholdSuggestionByAccompanyingPeriod',
'filterAddressesSuggestion',
'hasHouseholdAddress',
]),
...mapState([
'showHouseholdSuggestion',
@ -119,14 +160,22 @@ export default {
return this.$store.state.household;
},
allowHouseholdCreate() {
return this.$store.state.allowHouseholdCreate;
return this.$store.state.allowHouseholdCreate && !this.$store.getters.hasHousehold;
},
allowHouseholdSearch() {
return false;
return this.$store.state.allowHouseholdSearch;
return this.$store.state.allowHouseholdSearch && !this.$store.getters.hasHousehold;
},
allowLeaveWithoutHousehold() {
return this.$store.state.allowLeaveWithoutHousehold;
return this.$store.state.allowLeaveWithoutHousehold && !this.$store.getters.hasHousehold;
},
allowRemoveHousehold() {
return this.$store.getters.hasHousehold &&
(
this.allowHouseholdCreate || this.allowHouseholdSearch ||
this.allowLeaveWithoutHousehold
)
;
},
allowChangeHousehold() {
return this.allowHouseholdCreate || this.allowHouseholdSearch ||
@ -148,6 +197,15 @@ export default {
},
selectHousehold(h) {
this.$store.dispatch('selectHousehold', h);
},
removeHousehold() {
this.$store.dispatch('removeHousehold');
},
setHouseholdAddress(a) {
this.$store.commit('setHouseholdAddress', a);
},
removeHouseholdAddress() {
this.$store.commit('removeHouseholdAddress');
}
},
};

View File

@ -1,5 +1,5 @@
import { createStore } from 'vuex';
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod } from './../api.js';
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js';
import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js';
const debug = process.env.NODE_ENV !== 'production';
@ -37,6 +37,7 @@ const store = createStore({
forceLeaveWithoutHousehold: false,
householdSuggestionByAccompanyingPeriod: [],
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
addressesSuggestion: [],
warnings: [],
errors: []
},
@ -47,6 +48,12 @@ const store = createStore({
}
return !Number.isInteger(state.household.id);
},
hasHouseholdAddress(state) {
if (null === state.household) {
return false;
}
return state.household.current_address !== null;
},
hasHousehold(state) {
return state.household !== null;
},
@ -68,6 +75,18 @@ const store = createStore({
.filter(h => h.id !== state.household.id)
;
},
filterAddressesSuggestion(state) {
if (state.household === null) {
return state.addressesSuggestion;
}
if (state.household.current_address === null) {
return state.addressesSuggestion;
}
return state.addressesSuggestion
.filter(a => a.address_id !== state.household.current_address.address_id);
},
hasPersonsWellPositionnated(state, getters) {
return getters.needsPositionning === false
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
@ -106,7 +125,7 @@ const store = createStore({
needsPositionning(state) {
return state.forceLeaveWithoutHousehold === false;
},
buildPayload: (state) => {
buildPayload: (state, getters) => {
let
conc,
payload_conc,
@ -119,8 +138,13 @@ const store = createStore({
if (state.forceLeaveWithoutHousehold === false) {
payload.destination = {
id: state.household.id,
type: state.household.type
type: state.household.type,
};
if (getters.isHouseholdNew && state.household.current_address !== null) {
console.log(state.household);
payload.destination.forceAddress = { id: state.household.current_address.address_id };
}
}
for (let i in state.concerned) {
@ -187,9 +211,29 @@ const store = createStore({
)
},
createHousehold(state) {
state.household = { type: 'household', members: [], current_address: null }
state.household = { type: 'household', members: [], current_address: null, current_members_id: [] }
state.forceLeaveWithoutHousehold = false;
},
removeHousehold(state) {
state.household = null;
state.forceLeaveWithoutHousehold = false;
},
setHouseholdAddress(state, address) {
if (null === state.household) {
console.error("no household");
throw new Error("No household");
}
state.household.current_address = address;
},
removeHouseholdAddress(state, address) {
if (null === state.household) {
console.error("no household");
throw new Error("No household");
}
state.household.current_address = null;
},
forceLeaveWithoutHousehold(state) {
state.household = null;
state.forceLeaveWithoutHousehold = true;
@ -221,11 +265,22 @@ const store = createStore({
setErrors(state, errors) {
state.errors = errors;
},
addAddressesSuggestion(state, addresses) {
let existingIds = state.addressesSuggestion
.map(a => a.address_id);
for (let i in addresses) {
if (!existingIds.includes(addresses[i].address_id)) {
state.addressesSuggestion.push(addresses[i]);
}
}
}
},
actions: {
addConcerned({ commit, dispatch }, person) {
commit('addConcerned', person);
dispatch('computeWarnings');
dispatch('fetchAddressSuggestions');
},
markPosition({ commit, state, dispatch }, { person_id, position_id }) {
commit('markPosition', { person_id, position_id });
@ -242,6 +297,11 @@ const store = createStore({
removeConcerned({ commit, dispatch }, conc) {
commit('removeConcerned', conc);
dispatch('computeWarnings');
dispatch('fetchAddressSuggestions');
},
removeHousehold({ commit, dispatch }) {
commit('removeHousehold');
dispatch('computeWarnings');
},
createHousehold({ commit, dispatch }) {
commit('createHousehold');
@ -268,6 +328,17 @@ const store = createStore({
commit('setHouseholdSuggestionByAccompanyingPeriod', households);
});
},
fetchAddressSuggestions({ commit, state }) {
for (let i in state.concerned) {
fetchAddressSuggestionByPerson(state.concerned[i].person.id)
.then(addresses => {
commit('addAddressesSuggestion', addresses);
})
.catch(e => {
console.log(e);
});
}
},
computeWarnings({ commit, state, getters }) {
let warnings = [],
payload;
@ -321,6 +392,7 @@ const store = createStore({
});
store.dispatch('computeWarnings');
store.dispatch('fetchAddressSuggestions');
if (concerned.length > 0) {
concerned.forEach(c => {

View File

@ -0,0 +1,51 @@
/*
* Endpoint chill_api_single_person_address
* method POST, post Person instance
*
* @id integer - id of Person
* @body Object - dictionary with changes to post
*/
const postAddressToPerson = (personId, addressId) => {
//console.log(personId);
//console.log(addressId);
const body = {
'id': addressId
};
const url = `/api/1.0/person/person/${personId}/address.json`
return fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
/*
* Endpoint household
* method POST, post Household instance
*
* @id integer - id of household
* @body Object - dictionary with changes to post
*/
const postAddressToHousehold = (householdId, addressId) => {
const body = {
'id': addressId
};
const url = `/api/1.0/person/household/${householdId}/address.json`
return fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
export { postAddressToPerson, postAddressToHousehold };

View File

@ -0,0 +1,5 @@
<div class="alert alert-danger alert-with-actions mb-0">
<div class="message">
{{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}
</div>
</div>

View File

@ -19,7 +19,7 @@
<div class="accompanyingcourse-resume">
{% if 'DRAFT' == accompanyingCourse.step %}
<div class="col-8 alert alert-danger flash_message mb-5">
<div class="alert alert-danger flash_message mb-5">
<span>
{{ 'This accompanying course is still a draft'|trans }}
<a href="{{ path('chill_person_accompanying_course_edit', { 'accompanying_period_id': accompanyingCourse.id } ) }}">
@ -50,9 +50,14 @@
{% endif %}
{% endfor %}
</div>
{% if 'DRAFT' != accompanyingCourse.step %}
{% if withoutHousehold|length > 0 %}
{% include '@ChillPerson/AccompanyingCourse/_join_household.html.twig' with {} %}
{% endif %}
{% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %}
{% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' with {} %}
{% endif %}
{% endif %}
</div>
<div class="requestor mb-5">

View File

@ -2,7 +2,7 @@
{% set activeRouteKey = 'chill_person_accompanying_period_list' %}
{% block title %}{{ 'Person accompanying period - %name%'|trans({ '%name%' : person.__toString}) }}{% endblock title %}
{% block title %}{{ 'Accompanying period list'|trans }}{% endblock title %}
{% block personcontent %}
<div class="accompanyingcourse-list">

View File

@ -0,0 +1,72 @@
{#
This Twig template include load vue_address component.
It push all variables from context in Address/App.vue.
OPTIONS
* mode string ['edit*'|'new'|'create']
* address_id integer
* backUrl twig route: path('route', {parameters})
* modalTitle twig translated chain
* buttonText twig translated chain
* buttonSize bootstrap class like 'btn-sm'
#}
<div id="address"></div>
<script type="text/javascript">
window.vueRootComponent = 'app';
{% if person is defined %}
window.entityType = 'person';
window.entityId = {{ person.id|e('js') }};
{% elseif household is defined %}
window.entityType = 'household';
window.entityId = {{ household.id|e('js') }};
{% endif %}
{% if 'edit' in app.request.get('_route') %}
window.addressId = {{ app.request.get('address_id')|e('js') }};
window.mode = 'edit';
{% elseif mode is defined and mode == 'edit' %}
window.addressId = {{ address_id|e('js') }};
window.mode = 'edit';
{% endif %}
{% if backUrl is not defined %}
{% if person is defined %}
window.backUrl = '{{ path('chill_person_address_list', { 'person_id': person.id })|e('js') }}';
{% elseif household is defined %}
window.backUrl = '{{ path('chill_person_household_addresses', { 'household_id': household.id })|e('js') }}';
{% endif %}
{% else %}
window.backUrl = '{{ backUrl|e('js') }}';
{% endif %}
{% if modalTitle is defined %}
window.modalTitle = '{{ modalTitle|trans|e('js') }}';
{% endif %}
{% if buttonText is defined %}
window.buttonText = '{{ buttonText|trans|e('js') }}';
{% endif %}
{% if buttonSize is defined %}
window.buttonSize = '{{ buttonSize|e('js') }}';
{% endif %}
{% if buttonDisplayText is defined and buttonDisplayText == false %}
window.buttonDisplayText = false;
{% endif %}
{% if binModalStep1 is defined and binModalStep1 == false %}
window.binModalStep1 = false;
{% endif %}
{% if binModalStep2 is defined and binModalStep2 == false %}
window.binModalStep2 = false;
{% endif %}
</script>
{{ encore_entry_script_tags('vue_address') }}
{{ encore_entry_link_tags('vue_address') }}

View File

@ -18,29 +18,21 @@
{% set activeRouteKey = '' %}
{% block title 'Modify address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) %}
{% block title 'Edit an address'|trans %}
{% block personcontent %}
<div class="address-new">
<div class="address-edit">
{% block content %}
<h1>{{ block('title') }}</h1>
<div id="address"></div>
{# include vue_address component #}
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
binModalStep1: false,
binModalStep2: false,
} %}
{% endblock %}
</div>
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('vue_address') }}
{% endblock %}
{% block js %}
<script type="text/javascript">
window.personId = {{ person.id|e('js') }};
window.addressId = {{ address.id|e('js') }};
window.mode = 'edit';
window.vueRootComponent = 'app';
</script>
{{ encore_entry_script_tags('vue_address') }}
{% endblock %}

View File

@ -27,10 +27,15 @@
<ul class="record_actions my-3">
<li style="margin: auto;">
<a class="btn btn-lg btn-create"
href="{{ path('chill_person_address_new', { 'person_id' : person.id } ) }}">
{{ 'Add an address'|trans }}
</a>
{# include vue_address component #}
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
mode: 'new',
buttonSize: 'btn-lg',
buttonText: 'Add an address',
modalTitle: 'Add an address',
} %}
</li>
</ul>
@ -58,10 +63,32 @@
{% set row = row + 1 %}
{% if address.validTo is not empty and address.validTo < previousRowFrom %}
<div class="{{ 'row' ~ row ~ ' ' }}col-a action">
{# include vue_address component #}
<a href="" class="btn btn-sm btn-create">{{ 'Insert an address'|trans }}</a></div>
{#
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
address_id: address.id,
mode: 'edit',
binModalStep1: false,
binModalStep2: false,
} %}
#}
<div class="{{ 'row' ~ row ~ ' ' }}col-b"></div>
<div class="{{ 'row' ~ row ~ ' ' }}col-c action">
{# include vue_address component #}
<a href="" class="btn btn-sm btn-create">{{ 'Insert an address'|trans }}</a></div>
{#
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
address_id: address.id,
mode: 'edit',
binModalStep1: false,
binModalStep2: false,
} %}
#}
<div class="date">
{% if address.validTo is not empty %}
{{ address.validTo|format_date('short') }}
@ -81,7 +108,10 @@
'has_no_address': true
}) }}
<ul class="record_actions">
{# include vue_address component #}
<li><a href="{{ path('chill_person_address_edit', { 'person_id': person.id, 'address_id' : address.id } ) }}" class="btn btn-edit"></a></li>
</ul>
</div>
{# endif #}
@ -130,6 +160,7 @@
</a>
</li>
<li>
{# include vue_address component #}
<a class="btn btn-create"
href="{{ path('chill_person_address_new', { 'person_id' : person.id } ) }}">
{{ 'Add an address'|trans }}

View File

@ -18,28 +18,22 @@
{% set activeRouteKey = '' %}
{% block title %}{{ 'New address for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %}
{% block title %}{{ 'New address'|trans }}{% endblock %}
{% block personcontent %}
<div class="address-new">
{% block content %}
<h1>{{ block('title') }}</h1>
<div id="address"></div>
{# include vue_address component #}
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
binModalStep1: false,
binModalStep2: false,
} %}
{% endblock %}
</div>
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('vue_address') }}
{% endblock %}
{% block js %}
<script type="text/javascript">
window.personId = {{ person.id|e('js') }};
window.mode = 'new';
window.vueRootComponent = 'app';
</script>
{{ encore_entry_script_tags('vue_address') }}
{% endblock %}

View File

@ -6,21 +6,11 @@
<h1>{{ block('title') }}</h1>
<div>
<div id="household-address"></div>
{# include vue_address component #}
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
binModalStep1: false,
binModalStep2: false,
} %}
</div>
{% block stylesheets %}
{{ encore_entry_link_tags('vue_address') }}
{% endblock %}
{% block js %}
<script type="text/javascript">
window.householdId = {{ household.id|e('js') }};
window.addressId = {{ address.id|e('js') }};
window.mode = 'edit';
window.vueRootComponent = 'app';
</script>
{{ encore_entry_script_tags('vue_household_address') }}
{% endblock %}
{% endblock %}

View File

@ -6,19 +6,11 @@
<h1>{{ block('title') }}</h1>
<div>
<div id="household-address"></div>
{# include vue_address component #}
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
binModalStep1: false,
binModalStep2: false,
} %}
</div>
{% block stylesheets %}
{{ encore_entry_link_tags('vue_address') }}
{% endblock %}
{% block js %}
<script type="text/javascript">
window.householdId = {{ household.id|e('js') }};
window.vueRootComponent = 'app';
</script>
{{ encore_entry_script_tags('vue_household_address') }}
{% endblock %}
{% endblock %}

View File

@ -13,10 +13,16 @@
<ul class="record_actions my-3">
<li style="margin: auto;">
<a class="btn btn-lg btn-create"
href="{{ chill_path_add_return_path('chill_person_household_address_move', { 'household_id': household.id }) }}">
{{ 'Move household'|trans }}
</a>
{# include vue_address component #}
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
mode: 'new',
backUrl: chill_path_add_return_path('chill_person_household_address_move', { 'household_id': household.id }),
buttonSize: 'btn-lg',
buttonText: 'Move household',
modalTitle: 'Move household',
} %}
</li>
</ul>
@ -34,7 +40,10 @@
<div class="{{ 'row' ~ row ~ ' ' }}col-b"></div>
<div class="{{ 'row' ~ row ~ ' ' }}col-c action">
{# include vue_address component #}
<a href="" class="btn btn-sm btn-create">{{ 'Insert an address'|trans }}</a></div>
<div class="date">
{% if address.validTo is not empty %}
{{ address.validTo|format_date('short') }}
@ -54,7 +63,11 @@
}) }}
<ul class="record_actions">
<li>
<a href="{{ path('chill_person_household_address_edit', { 'household_id': household.id, 'address_id' : address.id } ) }}" class="btn btn-edit"></a>
{# include vue_address component #}
<a href="{{ path('chill_person_household_address_edit', { 'household_id': household.id, 'address_id' : address.id } ) }}"
class="btn btn-edit"></a>
</li>
</ul>
</div>
@ -77,10 +90,13 @@
</a>
</li>
<li>
{# include vue_address component #}
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_person_household_address_move', { 'household_id': household.id }) }}">
{{ 'Move household'|trans }}
</a>
</li>
</ul>

View File

@ -200,10 +200,15 @@ This view should receive those arguments:
<ul class="list-inline text-right mt-2">
<li class="list-inline-item">
<a class="btn btn-warning btn-sm" title="{{ 'Edit'|trans }}"
href="{{ path('chill_person_address_edit', { 'person_id': person.id, 'address_id' : person.lastAddress.id } ) }}">
<i class="fa fa-pencil fa-fw"></i>
</a>
{# include vue_address component #}
{% include '@ChillPerson/Address/_insert_vue_address.html.twig' with {
mode: 'edit',
address_id: person.lastAddress.id,
backUrl: path('chill_person_view', { 'person_id': person.id }),
modalTitle: 'Edit address', buttonText: 'Edit address',
buttonSize: 'btn-sm',
buttonDisplayText: false
} %}
</li>
<li class="list-inline-item">
<a class="btn btn-secondary btn-sm" title="{{ "Addresses history"|trans }}"

View File

@ -77,8 +77,11 @@ class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwar
protected function denormalizeMove($data, string $type, string $format, array $context = [])
{
$householdContext = $context;
$householdContext['groups'][] = 'create';
$household = $this->denormalizer->denormalize($data['destination'], Household::class,
$format, $context);
$format, $householdContext);
if (NULL === $household) {
throw new Exception\InvalidArgumentException("household could not be denormalized. Impossible to process");

View File

@ -1169,14 +1169,32 @@ paths:
id: 0
type: person
position:
type: position
type: household_position
id: 1
start_date:
datetime: 2021-06-01T00:00:00+02:00
datetime: "2021-06-01T00:00:00+02:00"
comment: "This is my comment for moving"
holder: false
destination:
type: household
Moving person to a new household and set an address to this household:
value:
concerned:
-
person:
id: 0
type: person
position:
type: household_position
id: 1
start_date:
datetime: "2021-06-01T00:00:00+02:00"
comment: "This is my comment for moving"
holder: false
destination:
type: household
forceAddress:
id: 0
Moving person to an existing household:
value:
concerned:
@ -1185,7 +1203,7 @@ paths:
id: 0
type: person
position:
type: position
type: household_position
id: 1
start_date:
datetime: 2021-06-01T00:00:00+02:00

View File

@ -8,7 +8,7 @@ module.exports = function(encore, entries)
ChillPersonAssets: __dirname + '/Resources/public'
});
encore.addEntry('vue_household_address', __dirname + '/Resources/public/vuejs/HouseholdAddress/index.js');
encore.addEntry('vue_household_members_editor', __dirname + '/Resources/public/vuejs/HouseholdMembersEditor/index.js');
encore.addEntry('vue_accourse', __dirname + '/Resources/public/vuejs/AccompanyingCourse/index.js');
encore.addEntry('vue_accourse_work_create', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js');

View File

@ -207,8 +207,11 @@ No address given: Pas d'adresse renseignée
The address has been successfully updated: L'adresse a été mise à jour avec succès
Update address for %name%: Mettre à jour une adresse pour %name%
Modify address for %name%: Modifier une adresse pour %name%
Edit an address: Modifier une adresse
Edit address: Modifier l'adresse
Addresses history for %name%: Historique des adresses de %name%
Addresses history: Historique des adresses
New address : Nouvelle adresse
New address for %name% : Nouvelle adresse pour %name%
The new address was created successfully: La nouvelle adresse a été créée
Add an address: Ajouter une adresse

View File

@ -547,7 +547,7 @@ class SingleTaskController extends AbstractController
$viewParams['tasks_count'] = $tasks_count;
if ($viewParams['person'] !== null){
$viewParams['layout'] = 'ChillPersonBundle::layout.html.twig';
$viewParams['layout'] = '@ChillPerson/Person/layout.html.twig';
} else {
$viewParams['layout'] = '@ChillMain/layout.html.twig';
}

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% extends layout %}
{% extends '@ChillPerson/Person/layout.html.twig' %}
{% set activeRouteKey = 'chill_task_single_task_new' %}