Merge remote-tracking branch 'origin/master' into issue377_addFields_toPerson

This commit is contained in:
Julien Fastré 2022-01-19 17:47:34 +01:00
commit b3aca957ff
54 changed files with 425 additions and 156 deletions

View File

@ -11,15 +11,28 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* vuejs: add dead information on all on-the-fly person render boxes, in vis graph and other templates (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/271)
* [thirdparty] fix bug in 3rd party view: types was replaced by thirdPartyTypes
* [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246)
* [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176)
* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
## Test releases
### test release 2022-01-10
* [main] Add editableByUser field to locationType entity, adapt the admin template and add this condition in the location-type endpoint (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/297) * [main] Add editableByUser field to locationType entity, adapt the admin template and add this condition in the location-type endpoint (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/297)
* [main] Add mainLocation field to User entity and add it in user form type * [main] Add mainLocation field to User entity and add it in user form type
* rewrite page which allow to select activity * rewrite page which allow to select activity
* [main] Add mainLocation field to User entity and add it in user form type * [main] Add mainLocation field to User entity and add it in user form type
* [course list in person context] show full username/label for ref * [course list in person context] show full username/label for ref
* [accompanying period work] remove the possibility to generate document from an accompanying period work * [accompanying period work] remove the possibility to generate document from an accompanying period work
* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
## Test releases ## Test releases
* vuejs: add validation on required fields for AddPerson, Address and Location components
* vuejs: treat 422 validation errors in locations and AddPerson components
>>>>>>> origin/master
### test release 2022-01-12 ### test release 2022-01-12

View File

@ -307,6 +307,7 @@ class ActivityType extends AbstractType
'allow_add' => true, 'allow_add' => true,
'button_add_label' => 'activity.Insert a document', 'button_add_label' => 'activity.Insert a document',
'button_remove_label' => 'activity.Remove a document', 'button_remove_label' => 'activity.Remove a document',
'empty_collection_explain' => 'No documents',
]); ]);
} }

View File

@ -11,7 +11,7 @@ import Location from './components/Location.vue';
export default { export default {
name: "App", name: "App",
props: ['hasSocialIssues', 'hasLocation', 'hasPerson'], props: ['hasSocialIssues', 'hasLocation', 'hasPerson'],
components: { components: {
ConcernedGroups, ConcernedGroups,
SocialIssuesAcc, SocialIssuesAcc,

View File

@ -24,7 +24,7 @@
v-model="location" v-model="location"
> >
</VueMultiselect> </VueMultiselect>
<new-location v-bind:locations="locations"></new-location> <new-location v-bind:availableLocations="availableLocations"></new-location>
</div> </div>
</div> </div>
</teleport> </teleport>

View File

@ -18,15 +18,6 @@
</template> </template>
<template v-slot:body> <template v-slot:body>
<form> <form>
<div class="form-floating mb-3">
<p v-if="errors.length">
<b>{{ $t('activity.errors') }}</b>
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</p>
</div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<select class="form-select form-select-lg" id="type" required v-model="selectType"> <select class="form-select form-select-lg" id="type" required v-model="selectType">
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option> <option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
@ -62,6 +53,12 @@
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder /> <input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
<label for="email">{{ $t('activity.location_fields.email') }}</label> <label for="email">{{ $t('activity.location_fields.email') }}</label>
</div> </div>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
</form> </form>
</template> </template>
<template v-slot:footer> <template v-slot:footer>
@ -81,7 +78,8 @@
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue'; import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"; import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import { getLocationTypes, postLocation } from "../../api"; import { getLocationTypes } from "../../api";
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
export default { export default {
name: "NewLocation", name: "NewLocation",
@ -89,7 +87,7 @@ export default {
Modal, Modal,
AddAddress, AddAddress,
}, },
props: ['locations'], props: ['availableLocations'],
data() { data() {
return { return {
errors: [], errors: [],
@ -223,7 +221,6 @@ export default {
}, },
saveNewLocation() { saveNewLocation() {
if (this.checkForm()) { if (this.checkForm()) {
console.log('saveNewLocation', this.selected);
let body = { let body = {
type: 'location', type: 'location',
name: this.selected.name, name: this.selected.name,
@ -242,23 +239,28 @@ export default {
} }
}); });
} }
postLocation(body)
.then( makeFetch('POST', '/api/1.0/main/location.json', body)
location => new Promise(resolve => { .then(response => {
this.locations.push(location); this.$store.dispatch('addAvailableLocationGroup', {
this.$store.dispatch('updateLocation', location); locationGroup: 'Localisations nouvellement créées',
resolve(); locations: [response]
this.modal.showModal = false; });
}) this.$store.dispatch('updateLocation', response);
).catch( this.modal.showModal = false;
err => { })
this.errors.push(err.message); .catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.errors.push(v);
}
} else {
this.errors.push('An error occurred');
} }
); })
}; };
}, },
submitNewAddress(payload) { submitNewAddress(payload) {
console.log('submitNewAddress', payload);
this.selected.addressId = payload.addressId; this.selected.addressId = payload.addressId;
this.addAddress.context.addressId = payload.addressId; this.addAddress.context.addressId = payload.addressId;
this.addAddress.context.edit = true; this.addAddress.context.edit = true;

View File

@ -12,7 +12,11 @@ const hasLocation = document.querySelector('#location') !== null;
const hasPerson = document.querySelector('#add-persons') !== null; const hasPerson = document.querySelector('#add-persons') !== null;
const app = createApp({ const app = createApp({
template: `<app :hasSocialIssues="hasSocialIssues", :hasLocation="hasLocation", :hasPerson="hasPerson"></app>`, template: `<app
:hasSocialIssues="hasSocialIssues"
:hasLocation="hasLocation"
:hasPerson="hasPerson"
></app>`,
data() { data() {
return { return {
hasSocialIssues, hasSocialIssues,

View File

@ -240,6 +240,9 @@ const store = createStore({
}); });
commit("updateActionsSelected", payload); commit("updateActionsSelected", payload);
}, },
addAvailableLocationGroup({ commit }, payload) {
commit("addAvailableLocationGroup", payload);
},
addPersonsInvolved({ commit }, payload) { addPersonsInvolved({ commit }, payload) {
//console.log('### action addPersonsInvolved', payload.result.type); //console.log('### action addPersonsInvolved', payload.result.type);
switch (payload.result.type) { switch (payload.result.type) {

View File

@ -8,6 +8,7 @@
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: type, id: entity.id }, targetEntity: { name: type, id: entity.id },
buttonText: entity|chill_entity_render_string, buttonText: entity|chill_entity_render_string,
isDead: entity.deathdate is not null,
parent: parent parent: parent
} %} } %}
{% endmacro %} {% endmacro %}

View File

@ -15,7 +15,7 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',

View File

@ -30,7 +30,7 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',

View File

@ -14,7 +14,7 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',

View File

@ -7,11 +7,13 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}

View File

@ -7,11 +7,13 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}

View File

@ -76,7 +76,7 @@ activity:
Insert a document: Insérer un document Insert a document: Insérer un document
Remove a document: Supprimer le document Remove a document: Supprimer le document
comment: Commentaire comment: Commentaire
No documents: Pas de documents
#timeline #timeline
'%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"' '%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"'

View File

@ -17,6 +17,7 @@ use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/** /**
* @ORM\Entity * @ORM\Entity
@ -32,6 +33,7 @@ class Notification implements TrackUpdateInterface
/** /**
* @ORM\ManyToMany(targetEntity=User::class) * @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_notification_addresses_user") * @ORM\JoinTable(name="chill_main_notification_addresses_user")
* @Assert\Count(min="1", minMessage="notification.At least one addressee")
*/ */
private Collection $addressees; private Collection $addressees;
@ -80,6 +82,7 @@ class Notification implements TrackUpdateInterface
/** /**
* @ORM\Column(type="text", options={"default": ""}) * @ORM\Column(type="text", options={"default": ""})
* @Assert\NotBlank(message="notification.Title must be defined")
*/ */
private string $title = ''; private string $title = '';
@ -286,9 +289,9 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
public function setMessage(string $message): self public function setMessage(?string $message): self
{ {
$this->message = $message; $this->message = (string) $message;
return $this; return $this;
} }
@ -314,9 +317,9 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
public function setTitle(string $title): Notification public function setTitle(?string $title): Notification
{ {
$this->title = $title; $this->title = (string) $title;
return $this; return $this;
} }

View File

@ -54,7 +54,6 @@ final class LocationFormType extends AbstractType
'label' => 'Address', 'label' => 'Address',
'use_valid_from' => false, 'use_valid_from' => false,
'use_valid_to' => false, 'use_valid_to' => false,
'mapped' => false,
]) ])
->add( ->add(
'active', 'active',

View File

@ -240,6 +240,10 @@ table.table-bordered {
color: $gray-800; color: $gray-800;
font-size: 90%; font-size: 90%;
p {
margin-bottom: 0.75rem !important;
}
// test a bottom right decoration (to be confirmed) // test a bottom right decoration (to be confirmed)
&.test { &.test {
position: relative; position: relative;
@ -318,6 +322,7 @@ dl.definition-inline {
.custom_field_no_data, .custom_field_no_data,
.chill-no-data-statement { .chill-no-data-statement {
font-style: italic; font-style: italic;
font-size: 90%;
} }
/// flash /// flash
@ -418,3 +423,8 @@ span.item-key {
background-color: #0000000a; background-color: #0000000a;
//text-decoration: dotted underline; //text-decoration: dotted underline;
} }
// increase toast message z-index (above all modals)
div.v-toast {
z-index: 10000!important;
}

View File

@ -22,6 +22,7 @@ $chill-theme-buttons: (
"choose": $gray-300, "choose": $gray-300,
"notify": $gray-300, "notify": $gray-300,
"unlink": $chill-red, "unlink": $chill-red,
"tpchild": $chill-pink,
); );
@each $button, $color in $chill-theme-buttons { @each $button, $color in $chill-theme-buttons {
@ -50,6 +51,7 @@ $chill-theme-buttons: (
&.btn-unlink, &.btn-unlink,
&.btn-action, &.btn-action,
&.btn-edit, &.btn-edit,
&.btn-tpchild,
&.btn-update { &.btn-update {
&, &:hover { &, &:hover {
color: $light; color: $light;
@ -75,6 +77,7 @@ $chill-theme-buttons: (
&.btn-remove::before, &.btn-remove::before,
&.btn-choose::before, &.btn-choose::before,
&.btn-notify::before, &.btn-notify::before,
&.btn-tpchild::before,
&.btn-cancel::before { &.btn-cancel::before {
font: normal normal normal 14px/1 ForkAwesome; font: normal normal normal 14px/1 ForkAwesome;
margin-right: 0.5em; margin-right: 0.5em;
@ -101,6 +104,7 @@ $chill-theme-buttons: (
&.btn-choose::before { content: "\f00c"; } // fa-check // f046 fa-check-square-o &.btn-choose::before { content: "\f00c"; } // fa-check // f046 fa-check-square-o
&.btn-unlink::before { content: "\f127"; } // fa-chain-broken &.btn-unlink::before { content: "\f127"; } // fa-chain-broken
&.btn-notify::before { content: "\f1d8"; } // fa-paper-plane &.btn-notify::before { content: "\f1d8"; } // fa-paper-plane
&.btn-tpchild::before { content: "\f007"; } // fa-user
} }

View File

@ -36,27 +36,45 @@ div.notification {
div.notification-list, div.notification-list,
div.notification-show { div.notification-show {
div.item-bloc { div.item-bloc {
div.item-row.header { div.item-row {
&.notification-header {
div.item-col { div.item-col {
&:first-child { &:first-child {
flex-grow: 1; flex-grow: 1;
}
&:last-child {
flex-grow: 0;
}
} }
&:last-child { ul.small_in_title {
flex-grow: 0; list-style-type: circle;
li {
span.item-key {
display: inline-block;
width: 3em;
}
}
} }
} }
div.notification-content {
ul.small_in_title { margin: 1.5rem;
list-style-type: circle; p {
li { margin-bottom: 0.75rem;
span.item-key {
display: inline-block;
width: 3em;
}
} }
} }
} }
} }
} }
// Override bootstrap accordion
div#notification-fold {
.accordion-button {
padding: 0;
background-color: unset;
&:not(.collapsed) {
background-color: unset;
box-shadow: unset;
}
}
}

View File

@ -85,7 +85,9 @@ const fetchScopes = () => {
const ValidationException = (response) => { const ValidationException = (response) => {
const error = {}; const error = {};
error.name = 'ValidationException'; error.name = 'ValidationException';
error.violations = response.violations.map((violation) => `${violation.title}`); error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`);
error.titles = response.violations.map((violation) => violation.title);
error.propertyPaths = response.violations.map((violation) => violation.propertyPath);
return error; return error;
} }

View File

@ -6,7 +6,7 @@ const i18n = _createI18n({});
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
document.querySelectorAll('.notification_toggle_read_status') document.querySelectorAll('.notification_toggle_read_status')
.forEach(function (el) { .forEach(function (el, i) {
createApp({ createApp({
template: '<notification-read-toggle ' + template: '<notification-read-toggle ' +
':notificationId="notificationId" ' + ':notificationId="notificationId" ' +
@ -26,13 +26,25 @@ window.addEventListener('DOMContentLoaded', function (e) {
buttonNoText: 'false' === el.dataset.buttonText, buttonNoText: 'false' === el.dataset.buttonText,
showUrl: el.dataset.showButtonUrl, showUrl: el.dataset.showButtonUrl,
isRead: 1 === +el.dataset.notificationCurrentIsRead, isRead: 1 === +el.dataset.notificationCurrentIsRead,
container: el.dataset.container
}
},
computed: {
getContainer() {
return document.querySelectorAll('div.' + this.container);
} }
}, },
methods: { methods: {
onMarkRead() { onMarkRead() {
if (typeof this.getContainer[i] !== 'undefined') {
this.getContainer[i].classList.replace('read', 'unread');
} else { throw 'data-container attribute is missing' }
this.isRead = false; this.isRead = false;
}, },
onMarkUnread() { onMarkUnread() {
if (typeof this.getContainer[i] !== 'undefined') {
this.getContainer[i].classList.replace('unread', 'read');
} else { throw 'data-container attribute is missing' }
this.isRead = true; this.isRead = true;
}, },
} }
@ -40,4 +52,4 @@ window.addEventListener('DOMContentLoaded', function (e) {
.use(i18n) .use(i18n)
.mount(el); .mount(el);
}); });
}) });

View File

@ -98,6 +98,8 @@
v-bind:defaultz="this.defaultz" v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity" v-bind:entity="this.entity"
v-bind:flag="this.flag" v-bind:flag="this.flag"
v-bind:errors="this.errors"
v-bind:checkErrors="this.checkErrors"
@getCities="getCities" @getCities="getCities"
@getReferenceAddresses="getReferenceAddresses"> @getReferenceAddresses="getReferenceAddresses">
</edit-pane> </edit-pane>
@ -123,6 +125,8 @@
v-bind:defaultz="this.defaultz" v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity" v-bind:entity="this.entity"
v-bind:flag="this.flag" v-bind:flag="this.flag"
v-bind:errors="this.errors"
v-bind:checkErrors="this.checkErrors"
v-bind:insideModal="false" v-bind:insideModal="false"
@getCities="getCities" @getCities="getCities"
@getReferenceAddresses="getReferenceAddresses"> @getReferenceAddresses="getReferenceAddresses">
@ -256,8 +260,10 @@ export default {
editPane: false, editPane: false,
datePane: false, datePane: false,
loading: false, loading: false,
success: false success: false,
dirty: false
}, },
errors: [],
defaultz: { defaultz: {
button: { button: {
text: { create: 'add_an_address_title', edit: 'edit_address' }, text: { create: 'add_an_address_title', edit: 'edit_address' },
@ -529,6 +535,23 @@ export default {
}); });
}, },
checkErrors() {
this.errors = [];
if (this.flag.dirty) {
if (this.entity.selected.country === null) {
this.errors.push("Un pays doit être sélectionné.");
}
if (Object.keys(this.entity.selected.city).length === 0) {
this.errors.push("Une ville doit être sélectionnée.");
}
if (!this.entity.selected.isNoAddress) {
if (this.entity.selected.address.street === null || this.entity.selected.address.streetNumber === null) {
this.errors.push("Une adresse doit être sélectionnée.");
}
}
}
},
/* /*
* Make form ready for new changes * Make form ready for new changes
*/ */

View File

@ -12,6 +12,7 @@
@search-change="listenInputSearch" @search-change="listenInputSearch"
ref="addressSelector" ref="addressSelector"
@select="selectAddress" @select="selectAddress"
@remove="remove"
name="field" name="field"
track-by="id" track-by="id"
label="value" label="value"
@ -56,7 +57,7 @@ import { searchReferenceAddresses, fetchReferenceAddresses } from '../../api.js'
export default { export default {
name: 'AddressSelection', name: 'AddressSelection',
components: { VueMultiselect }, components: { VueMultiselect },
props: ['entity', 'context', 'updateMapCenter'], props: ['entity', 'context', 'updateMapCenter', 'flag', 'checkErrors'],
data() { data() {
return { return {
value: this.context.edit ? this.entity.address.addressReference : null, value: this.context.edit ? this.entity.address.addressReference : null,
@ -109,6 +110,13 @@ export default {
this.entity.selected.address.streetNumber = value.streetNumber; this.entity.selected.address.streetNumber = value.streetNumber;
this.entity.selected.writeNew.address = false; this.entity.selected.writeNew.address = false;
this.updateMapCenter(value.point); this.updateMapCenter(value.point);
this.flag.dirty = true;
this.checkErrors();
},
remove() {
this.flag.dirty = true;
this.entity.selected.address = {};
this.checkErrors();
}, },
listenInputSearch(query) { listenInputSearch(query) {
//console.log('listenInputSearch', query, this.isAddressSelectorOpen); //console.log('listenInputSearch', query, this.isAddressSelectorOpen);
@ -149,6 +157,8 @@ export default {
this.entity.selected.address.street = addr.street; this.entity.selected.address.street = addr.street;
this.entity.selected.address.streetNumber = addr.number; this.entity.selected.address.streetNumber = addr.number;
this.entity.selected.writeNew.address = true; this.entity.selected.writeNew.address = true;
this.flag.dirty = true;
this.checkErrors();
} }
}, },
splitAddress(address) { splitAddress(address) {

View File

@ -7,6 +7,7 @@
@search-change="listenInputSearch" @search-change="listenInputSearch"
ref="citySelector" ref="citySelector"
@select="selectCity" @select="selectCity"
@remove="remove"
name="field" name="field"
track-by="id" track-by="id"
label="value" label="value"
@ -55,12 +56,12 @@ import { searchCities, fetchCities } from '../../api.js';
export default { export default {
name: 'CitySelection', name: 'CitySelection',
components: { VueMultiselect }, components: { VueMultiselect },
props: ['entity', 'context', 'focusOnAddress', 'updateMapCenter'], props: ['entity', 'context', 'focusOnAddress', 'updateMapCenter', 'flag', 'checkErrors'],
emits: ['getReferenceAddresses'], emits: ['getReferenceAddresses'],
data() { data() {
return { return {
value: this.context.edit ? this.entity.address.postcode : null, value: this.context.edit ? this.entity.address.postcode : null,
isLoading: false isLoading: false,
} }
}, },
computed: { computed: {
@ -123,6 +124,13 @@ export default {
if (value.center) { if (value.center) {
this.updateMapCenter(value.center); this.updateMapCenter(value.center);
} }
this.flag.dirty = true;
this.checkErrors();
},
remove() {
this.flag.dirty = true;
this.entity.selected.city = {};
this.checkErrors();
}, },
listenInputSearch(query) { listenInputSearch(query) {
if (query.length > 2) { if (query.length > 2) {

View File

@ -12,7 +12,9 @@
:select-label="$t('multiselect.select_label')" :select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')" :deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')" :selected-label="$t('multiselect.selected_label')"
@select="selectCountry"> @select="selectCountry"
@remove="remove"
>
</VueMultiselect> </VueMultiselect>
</div> </div>
</template> </template>
@ -23,7 +25,7 @@ import VueMultiselect from 'vue-multiselect';
export default { export default {
name: 'CountrySelection', name: 'CountrySelection',
components: { VueMultiselect }, components: { VueMultiselect },
props: ['context', 'entity'], props: ['context', 'entity', 'flag', 'checkErrors'],
emits: ['getCities'], emits: ['getCities'],
data() { data() {
return { return {
@ -34,14 +36,13 @@ export default {
}, },
computed: { computed: {
sortedCountries() { sortedCountries() {
//console.log('sorted countries');
const countries = this.entity.loaded.countries; const countries = this.entity.loaded.countries;
let sortedCountries = []; let sortedCountries = [];
sortedCountries.push(...countries.filter(c => c.countryCode === 'FR')) sortedCountries.push(...countries.filter(c => c.countryCode === 'FR'))
sortedCountries.push(...countries.filter(c => c.countryCode === 'BE')) sortedCountries.push(...countries.filter(c => c.countryCode === 'BE'))
sortedCountries.push(...countries.filter(c => c.countryCode !== 'FR').filter(c => c.countryCode !== 'BE')) sortedCountries.push(...countries.filter(c => c.countryCode !== 'FR').filter(c => c.countryCode !== 'BE'))
return sortedCountries; return sortedCountries;
} },
}, },
mounted() { mounted() {
this.init(); this.init();
@ -50,6 +51,7 @@ export default {
init() { init() {
if (this.value !== undefined) { if (this.value !== undefined) {
this.selectCountry(this.value); this.selectCountry(this.value);
this.flag.dirty = false;
} }
}, },
selectCountryByCode(countryCode) { selectCountryByCode(countryCode) {
@ -62,7 +64,13 @@ export default {
//console.log('select country', value); //console.log('select country', value);
this.entity.selected.country = value; this.entity.selected.country = value;
this.$emit('getCities', value); this.$emit('getCities', value);
} this.checkErrors();
},
remove() {
this.flag.dirty = true;
this.entity.selected.country = null;
this.checkErrors();
},
} }
}; };

View File

@ -7,6 +7,12 @@
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div>
<div v-if="errors.length" class="alert alert-warning" >
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
<h4 class="h3">{{ $t('select_an_address_title') }}</h4> <h4 class="h3">{{ $t('select_an_address_title') }}</h4>
<div class="row my-3"> <div class="row my-3">
<div class="col-lg-6"> <div class="col-lg-6">
@ -25,6 +31,8 @@
<country-selection <country-selection
v-bind:context="context" v-bind:context="context"
v-bind:entity="entity" v-bind:entity="entity"
v-bind:flag="flag"
v-bind:checkErrors="checkErrors"
@getCities="$emit('getCities', selected.country)"> @getCities="$emit('getCities', selected.country)">
</country-selection> </country-selection>
@ -33,13 +41,17 @@
v-bind:context="context" v-bind:context="context"
v-bind:focusOnAddress="focusOnAddress" v-bind:focusOnAddress="focusOnAddress"
v-bind:updateMapCenter="updateMapCenter" v-bind:updateMapCenter="updateMapCenter"
v-bind:flag="flag"
v-bind:checkErrors="checkErrors"
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)"> @getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
</city-selection> </city-selection>
<address-selection v-if="!isNoAddress" <address-selection v-if="!isNoAddress"
v-bind:entity="entity" v-bind:entity="entity"
v-bind:context="context" v-bind:context="context"
v-bind:updateMapCenter="updateMapCenter"> v-bind:updateMapCenter="updateMapCenter"
v-bind:flag="flag"
v-bind:checkErrors="checkErrors">
</address-selection> </address-selection>
</div> </div>
@ -99,7 +111,9 @@ export default {
'flag', 'flag',
'entity', 'entity',
'errorMsg', 'errorMsg',
'insideModal' 'insideModal',
'errors',
'checkErrors',
], ],
emits: ['getCities', 'getReferenceAddresses'], emits: ['getCities', 'getReferenceAddresses'],
data() { data() {
@ -128,7 +142,7 @@ export default {
get() { get() {
return this.entity.selected.isNoAddress; return this.entity.selected.isNoAddress;
} }
} },
}, },
methods: { methods: {
focusOnAddress() { focusOnAddress() {

View File

@ -5,6 +5,7 @@
:action="context.action" :action="context.action"
:buttonText="options.buttonText" :buttonText="options.buttonText"
:displayBadge="options.displayBadge === 'true'" :displayBadge="options.displayBadge === 'true'"
:isDead="options.isDead"
:parent="options.parent" :parent="options.parent"
@saveFormOnTheFly="saveFormOnTheFly"> @saveFormOnTheFly="saveFormOnTheFly">
</on-the-fly> </on-the-fly>

View File

@ -2,14 +2,14 @@
<a v-if="isDisplayBadge" @click="openModal"> <a v-if="isDisplayBadge" @click="openModal">
<span class="chill-entity" :class="badgeType"> <span class="chill-entity" :class="badgeType">
{{ buttonText }} {{ buttonText }}<span v-if="isDead"> ()</span>
</span> </span>
</a> </a>
<a v-else class="btn btn-sm" target="_blank" <a v-else class="btn btn-sm" target="_blank"
:class="classAction" :class="classAction"
:title="$t(titleAction)" :title="$t(titleAction)"
@click="openModal"> @click="openModal">
{{ buttonText }} {{ buttonText }}<span v-if="isDead"> ()</span>
</a> </a>
<teleport to="body"> <teleport to="body">
@ -90,7 +90,7 @@ export default {
OnTheFlyThirdparty, OnTheFlyThirdparty,
OnTheFlyCreate OnTheFlyCreate
}, },
props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'parent'], props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'isDead', 'parent', 'canCloseModal'],
emits: ['saveFormOnTheFly'], emits: ['saveFormOnTheFly'],
data() { data() {
return { return {
@ -162,7 +162,20 @@ export default {
return 'entity-' + this.type + ' badge-' + this.type; return 'entity-' + this.type + ' badge-' + this.type;
} }
}, },
watch: {
canCloseModal: {
handler: function(val, oldVal) {
if (val) {
this.closeModal();
}
},
deep: true
}
},
methods: { methods: {
closeModal() {
this.modal.showModal = false;
},
openModal() { openModal() {
//console.log('## OPEN ON THE FLY MODAL'); //console.log('## OPEN ON THE FLY MODAL');
//console.log('## type:', this.type, ', action:', this.action); //console.log('## type:', this.type, ', action:', this.action);
@ -200,8 +213,6 @@ export default {
// pass datas to parent // pass datas to parent
this.$emit('saveFormOnTheFly', { type: type, data: data }); this.$emit('saveFormOnTheFly', { type: type, data: data });
this.modal.showModal = false;
}, },
buildLocation(id, type) { buildLocation(id, type) {
if (type === 'person') { if (type === 'person') {

View File

@ -22,7 +22,8 @@ containers.forEach((container) => {
options: { options: {
buttonText: container.dataset.buttonText || null, buttonText: container.dataset.buttonText || null,
displayBadge: container.dataset.displayBadge || false, displayBadge: container.dataset.displayBadge || false,
parent: JSON.parse(container.dataset.parent) || null, isDead: container.dataset.isDead || false,
parent: container.dataset.parent ? JSON.parse(container.dataset.parent) : null,
} }
} }
} }
@ -33,4 +34,5 @@ containers.forEach((container) => {
.mount(container); .mount(container);
//console.log('container dataset', container.dataset); //console.log('container dataset', container.dataset);
//console.log('data-parent', container.dataset.parent);
}); });

View File

@ -34,7 +34,7 @@
:href="showUrl" :href="showUrl"
:title="$t('action.show')" :title="$t('action.show')"
> >
<i class="fa fa-sm fa-eye"></i> <i class="fa fa-sm fa-comment-o"></i>
</a> </a>
</div> </div>

View File

@ -23,7 +23,7 @@
<td>{{ entity.email }}</td> <td>{{ entity.email }}</td>
<td> <td>
{% if entity.address is not null %} {% if entity.address is not null %}
{{ entity.address.street}}, {{ entity.address.streetnumber }} {{ entity.address| chill_entity_render_box }}
{% endif %} {% endif %}
</td> </td>
<td style="text-align:center;"> <td style="text-align:center;">

View File

@ -1,39 +1,41 @@
<div class="item-bloc {% if not notification.isReadBy(app.user) %}unread{% else %}read{% endif %}"> {% macro title(c) %}
<div class="item-row title"> <div class="item-row title">
<h2 class="notification-title"> <h2 class="notification-title">
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}"> <a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">
{{ notification.title }} {{ 'notification.object_prefix'|trans ~ c.notification.title }}
</a> </a>
</h2> </h2>
</div> </div>
<div class="item-row mt-2 header"> {% endmacro %}
{% macro header(c) %}
<div class="item-row notification-header mt-2">
<div class="item-col"> <div class="item-col">
<ul class="small_in_title"> <ul class="small_in_title">
{% if step is not defined or step == 'inbox' %} {% if c.step is not defined or c.step == 'inbox' %}
<li class="notification-from"> <li class="notification-from">
<span class="item-key"> <span class="item-key">
<abbr title="{{ 'notification.received_from'|trans }}"> <abbr title="{{ 'notification.received_from'|trans }}">
{{ 'notification.from'|trans }} : {{ 'notification.from'|trans }} :
</abbr> </abbr>
</span> </span>
{% if not notification.isSystem %} {% if not c.notification.isSystem %}
<span class="badge-user"> <span class="badge-user">
{{ notification.sender|chill_entity_render_string }} {{ c.notification.sender|chill_entity_render_string }}
</span> </span>
{% else %} {% else %}
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span> <span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
{% endif %} {% endif %}
</li> </li>
{% endif %} {% endif %}
{% if notification.addressees|length > 0 %} {% if c.notification.addressees|length > 0 %}
<li class="notification-to"> <li class="notification-to">
<span class="item-key"> <span class="item-key">
<abbr title="{{ 'notification.sent_to'|trans }}"> <abbr title="{{ 'notification.sent_to'|trans }}">
{{ 'notification.to'|trans }} : {{ 'notification.to'|trans }} :
</abbr> </abbr>
</span> </span>
{% for a in notification.addressees %} {% for a in c.notification.addressees %}
<span class="badge-user"> <span class="badge-user">
{{ a|chill_entity_render_string }} {{ a|chill_entity_render_string }}
</span> </span>
@ -42,47 +44,76 @@
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
<div class="item-col"> <div class="item-col">
{{ notification.date|format_datetime('long', 'short') }} {{ c.notification.date|format_datetime('long', 'short') }}
</div> </div>
</div> </div>
{% endmacro %}
{% macro content(c) %}
<div class="item-row separator"> <div class="item-row separator">
<div class="mx-3 flex-grow-1"> <div class="mx-3 flex-grow-1">
{% include data.template with data.template_data %} {% include c.data.template with c.data.template_data %}
</div> </div>
</div> </div>
<div class="item-row row"> <div class="item-row">
<div> <div class="notification-content">
<blockquote class="chill-user-quote"> {% if c.full_content is defined and c.full_content == 'true' %}
{{ notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }} {{ c.notification.message|chill_markdown_to_html }}
</blockquote> {% else %}
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
{% endif %}
</div> </div>
</div> </div>
{% if action_button is not defined or action_button != 'false' %} {% if c.action_button is not defined or c.action_button != 'false' %}
<div class="item-row separator"> <div class="item-row separator">
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
{# Vue component #} {# Vue component #}
<span class="notification_toggle_read_status" <span class="notification_toggle_read_status"
data-notification-id="{{ notification.id }}" data-notification-id="{{ c.notification.id }}"
data-notification-current-is-read="{{ notification.isReadBy(app.user) }}" data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
data-container="notification-status"
></span> ></span>
</li> </li>
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', notification) %} {% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %}
<li> <li>
<a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': notification.id}) }}" <a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': c.notification.id}) }}"
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a> class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', notification) %} {% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
<li> <li>
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}" <a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
class="btn btn-show" title="{{ 'Show'|trans }}"></a> class="btn btn-show change-icon" title="{{ 'notification.see_comments_thread'|trans }}"><i class="fa fa-comment"></i></a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
{% endmacro %}
<div class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
{% if fold_item is defined and fold_item != 'false' %}
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
<button type="button" class="accordion-button collapsed"
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
aria-expanded="false" aria-controls="flush-collapse-{{ notification.id }}">
{{ _self.title(_context) }}
</button>
{{ _self.header(_context) }}
</div>
<div id="flush-collapse-{{ notification.id }}"
class="accordion-collapse collapse"
aria-labelledby="flush-heading-{{ notification.id }}"
data-bs-parent="#notification-fold">
{{ _self.content(_context) }}
</div>
{% else %}
{{ _self.title(_context) }}
{{ _self.header(_context) }}
{{ _self.content(_context) }}
{% endif %}
</div> </div>

View File

@ -4,7 +4,7 @@
</div> </div>
{# TODO pagination or limit #} {# TODO pagination or limit #}
{% for notification in notifications %} {% for notification in notifications %}
<div class="list-group-item {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}"> <div class="list-group-item notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
{% if not notification.isSystem %} {% if not notification.isSystem %}
{% if notification.sender == app.user %} {% if notification.sender == app.user %}
@ -17,6 +17,7 @@
<span class="notification_toggle_read_status" <span class="notification_toggle_read_status"
data-notification-id="{{ notification.id }}" data-notification-id="{{ notification.id }}"
data-notification-current-is-read="{{ notification.isReadBy(app.user) }}" data-notification-current-is-read="{{ notification.isReadBy(app.user) }}"
data-container="notification-status"
data-show-button-url="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}" data-show-button-url="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}"
data-button-class="btn-outline-primary" data-button-class="btn-outline-primary"
data-button-text="false" data-button-text="false"

View File

@ -46,10 +46,12 @@
<p class="chill-no-data-statement">{{ 'notification.Any notification sent'|trans }}</p> <p class="chill-no-data-statement">{{ 'notification.Any notification sent'|trans }}</p>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="flex-table"> <div class="flex-table accordion accordion-flush" id="notification-fold">
{% for data in datas %} {% for data in datas %}
{% set notification = data.notification %} {% set notification = data.notification %}
{% include 'ChillMainBundle:Notification:_list_item.html.twig' %} {% include 'ChillMainBundle:Notification:_list_item.html.twig' with {
'fold_item': 'true'
} %}
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}

View File

@ -40,7 +40,8 @@
'template': handler.getTemplate(notification), 'template': handler.getTemplate(notification),
'template_data': handler.getTemplateData(notification) 'template_data': handler.getTemplateData(notification)
}, },
'action_button': 'false' 'action_button': 'false',
'full_content': 'true'
} %} } %}
</div> </div>
@ -86,8 +87,9 @@
</div> </div>
{% endif %} {% endif %}
{% if appendCommentForm is not null %}
<div class="new-comment my-5"> <div class="new-comment my-5">
<h2 class="chill-blue">{{ 'Write a new comment'|trans }}</h2> <h2 class="chill-blue mb-4">{{ 'Write a new comment'|trans }}</h2>
{{ form_start(appendCommentForm) }} {{ form_start(appendCommentForm) }}
{{ form_errors(appendCommentForm) }} {{ form_errors(appendCommentForm) }}
@ -101,6 +103,7 @@
{{ form_end(appendCommentForm) }} {{ form_end(appendCommentForm) }}
</div> </div>
{% endif %}
</div> </div>
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
@ -114,6 +117,7 @@
<span class="notification_toggle_read_status" <span class="notification_toggle_read_status"
data-notification-id="{{ notification.id }}" data-notification-id="{{ notification.id }}"
data-notification-current-is-read="1" data-notification-current-is-read="1"
data-container="notification-status"
></span> ></span>
</li> </li>
</ul> </ul>

View File

@ -11,6 +11,7 @@
* buttonText string * buttonText string
* displayBadge boolean (default: false) replace button by badge, need to define buttonText for content * displayBadge boolean (default: false) replace button by badge, need to define buttonText for content
* parent object (optional) pass parent context of the targetEntity (used for course resource comment) * parent object (optional) pass parent context of the targetEntity (used for course resource comment)
* isDead boolean (default: false) is the person dead
#} #}
<span class="onthefly-container" <span class="onthefly-container"
@ -28,6 +29,10 @@
data-button-text="{{ buttonText|e('html_attr') }}" data-button-text="{{ buttonText|e('html_attr') }}"
{% endif %} {% endif %}
{% if isDead is defined and isDead == 1 %}
data-is-dead="true"
{% endif %}
{% if displayBadge is defined and displayBadge == 1 %} {% if displayBadge is defined and displayBadge == 1 %}
data-display-badge="true" data-display-badge="true"
{% endif %} {% endif %}

View File

@ -381,4 +381,6 @@ notification:
you were notified by %sender%: Vous avez été notifié par %sender% you were notified by %sender%: Vous avez été notifié par %sender%
you were notified by system: Vous avez été notifié automatiquement you were notified by system: Vous avez été notifié automatiquement
subject: Objet subject: Objet
see_comments_thread: Voir le fil de commentaires associé
object_prefix: "[CHILL] notification - "

View File

@ -23,3 +23,8 @@ address:
street1-should-be-set: Une ligne d'adresse doit être présente street1-should-be-set: Une ligne d'adresse doit être présente
date-should-be-set: La date de début de validité doit être présente date-should-be-set: La date de début de validité doit être présente
postcode-should-be-set: Le code postal doit être renseigné postcode-should-be-set: Le code postal doit être renseigné
notification:
At least one addressee: Indiquez au moins un destinataire
Title must be defined: Un titre doit être indiqué

View File

@ -151,7 +151,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @var DateTime * @var DateTime
* *
* @ORM\Column(type="date", nullable=true) * @ORM\Column(type="date", nullable=true)
* @Assert\Date
* @Birthdate * @Birthdate
*/ */
private $birthdate; private $birthdate;
@ -259,7 +258,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @var string * @var string
* *
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
* @Assert\NotBlank * @Assert\NotBlank(message="The firstname cannot be empty")
* @Assert\Length( * @Assert\Length(
* max=255, * max=255,
* ) * )
@ -282,7 +281,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @var string * @var string
* *
* @ORM\Column(type="string", length=9, nullable=true) * @ORM\Column(type="string", length=9, nullable=true)
* @Assert\NotNull * @Assert\NotNull(message="The gender must be set")
*/ */
private $gender; private $gender;
@ -326,7 +325,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @var string * @var string
* *
* @ORM\Column(type="string", length=255) * @ORM\Column(type="string", length=255)
* @Assert\NotBlank * @Assert\NotBlank(message="The lastname cannot be empty")
* @Assert\Length( * @Assert\Length(
* max=255, * max=255,
* ) * )

View File

@ -130,7 +130,7 @@ const store = createStore({
person.group = person.type person.group = person.type
person._id = person.id person._id = person.id
person.id = `person_${person.id}` person.id = `person_${person.id}`
person.label = `*${person.text}*\n_${getGender(person.gender)}${age}_${debug}` person.label = `*${person.text}${person.deathdate ? ' (‡)' : ''}*\n_${getGender(person.gender)}${age}_${debug}`
person.folded = false person.folded = false
// folded is used for missing persons // folded is used for missing persons
if (options.folded) { if (options.folded) {

View File

@ -66,9 +66,10 @@
<div class="create-button"> <div class="create-button">
<on-the-fly <on-the-fly
v-if="query.length >= 3" v-if="query.length >= 3"
v-bind:buttonText="$t('onthefly.create.button', {q: query})" :buttonText="$t('onthefly.create.button', {q: query})"
action="create" action="create"
@saveFormOnTheFly="saveFormOnTheFly"> @saveFormOnTheFly="saveFormOnTheFly"
:canCloseModal="canCloseOnTheFlyModal">
</on-the-fly> </on-the-fly>
</div> </div>
@ -91,8 +92,7 @@ import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue'; import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
import PersonSuggestion from './AddPersons/PersonSuggestion'; import PersonSuggestion from './AddPersons/PersonSuggestion';
import { searchEntities } from 'ChillPersonAssets/vuejs/_api/AddPersons'; import { searchEntities } from 'ChillPersonAssets/vuejs/_api/AddPersons';
import { postPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly"; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
import { postThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly";
export default { export default {
name: 'AddPersons', name: 'AddPersons',
@ -120,7 +120,8 @@ export default {
suggested: [], suggested: [],
selected: [], selected: [],
priorSuggestion: {} priorSuggestion: {}
} },
canCloseOnTheFlyModal: false
} }
}, },
computed: { computed: {
@ -267,22 +268,36 @@ export default {
saveFormOnTheFly({ type, data }) { saveFormOnTheFly({ type, data }) {
console.log('saveFormOnTheFly from addPersons, type', type, ', data', data); console.log('saveFormOnTheFly from addPersons, type', type, ', data', data);
if (type === 'person') { if (type === 'person') {
console.log('type person with', data); makeFetch('POST', '/api/1.0/person/person.json', data)
postPerson(data) .then(response => {
.then(person => new Promise((resolve, reject) => { this.newPriorSuggestion(response);
console.log('onthefly create: post person', person); this.canCloseOnTheFlyModal = true;
this.newPriorSuggestion(person); })
resolve(); .catch((error) => {
})); if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
}
})
} }
else if (type === 'thirdparty') { else if (type === 'thirdparty') {
console.log('type thirdparty with', data); makeFetch('POST', '/api/1.0/thirdparty/thirdparty.json', data)
postThirdparty(data) .then(response => {
.then(thirdparty => new Promise((resolve, reject) => { this.newPriorSuggestion(response);
console.log('onthefly create: post thirdparty', thirdparty); this.canCloseOnTheFlyModal = true;
this.newPriorSuggestion(thirdparty); })
resolve(); .catch((error) => {
})); if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
}
})
} }
} }
}, },

View File

@ -127,6 +127,7 @@
<i class="fa fa-stack-1x">T</i> <i class="fa fa-stack-1x">T</i>
</span> </span>
{{ person.text }} {{ person.text }}
<span v-if="person.deathdate" class="deathdate"> ()</span>
</a> </a>
<span v-else> <span v-else>
<span v-if="options.isHolder" class="fa-stack fa-holder" :title="$t('renderbox.holder')"> <span v-if="options.isHolder" class="fa-stack fa-holder" :title="$t('renderbox.holder')">
@ -134,6 +135,7 @@
<i class="fa fa-stack-1x">T</i> <i class="fa fa-stack-1x">T</i>
</span> </span>
{{ person.text }} {{ person.text }}
<span v-if="person.deathdate" class="deathdate"> ()</span>
</span> </span>
<slot name="post-badge"></slot> <slot name="post-badge"></slot>
</span> </span>

View File

@ -22,24 +22,45 @@
<div v-else-if="action === 'edit' || action === 'create'"> <div v-else-if="action === 'edit' || action === 'create'">
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input class="form-control form-control-lg" id="lastname" v-model="lastName" v-bind:placeholder="$t('person.lastname')" /> <input
class="form-control form-control-lg"
id="lastname"
v-model="lastName"
:placeholder="$t('person.lastname')"
@change="checkErrors"
/>
<label for="lastname">{{ $t('person.lastname') }}</label> <label for="lastname">{{ $t('person.lastname') }}</label>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input class="form-control form-control-lg" id="firstname" v-model="firstName" v-bind:placeholder="$t('person.firstname')" /> <input
class="form-control form-control-lg"
id="firstname"
v-model="firstName"
:placeholder="$t('person.firstname')"
@change="checkErrors"
/>
<label for="firstname">{{ $t('person.firstname') }}</label> <label for="firstname">{{ $t('person.firstname') }}</label>
</div> </div>
<div v-for="(a) in config.altNames" :key="a.key" class="form-floating mb-3"> <div v-for="(a) in config.altNames" :key="a.key" class="form-floating mb-3">
<input class="form-control form-control-lg" :id="a.key" @input="onAltNameInput" /> <input
class="form-control form-control-lg"
:id="a.key"
@input="onAltNameInput"
/>
<label :for="a.key">{{ a.labels.fr }}</label> <label :for="a.key">{{ a.labels.fr }}</label>
</div> </div>
<!-- TODO fix placeholder if undefined <!-- TODO fix placeholder if undefined
--> -->
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<select class="form-select form-select-lg" id="gender" v-model="gender"> <select
class="form-select form-select-lg"
id="gender"
v-model="gender"
@change="checkErrors"
>
<option selected disabled >{{ $t('person.gender.placeholder') }}</option> <option selected disabled >{{ $t('person.gender.placeholder') }}</option>
<option value="woman">{{ $t('person.gender.woman') }}</option> <option value="woman">{{ $t('person.gender.woman') }}</option>
<option value="man">{{ $t('person.gender.man') }}</option> <option value="man">{{ $t('person.gender.man') }}</option>
@ -62,8 +83,8 @@
<span class="input-group-text" id="phonenumber"><i class="fa fa-fw fa-phone"></i></span> <span class="input-group-text" id="phonenumber"><i class="fa fa-fw fa-phone"></i></span>
<input class="form-control form-control-lg" <input class="form-control form-control-lg"
v-model="phonenumber" v-model="phonenumber"
v-bind:placeholder="$t('person.phonenumber')" :placeholder="$t('person.phonenumber')"
v-bind:aria-label="$t('person.phonenumber')" :aria-label="$t('person.phonenumber')"
aria-describedby="phonenumber" /> aria-describedby="phonenumber" />
</div> </div>
@ -71,8 +92,8 @@
<span class="input-group-text" id="mobilenumber"><i class="fa fa-fw fa-mobile"></i></span> <span class="input-group-text" id="mobilenumber"><i class="fa fa-fw fa-mobile"></i></span>
<input class="form-control form-control-lg" <input class="form-control form-control-lg"
v-model="mobilenumber" v-model="mobilenumber"
v-bind:placeholder="$t('person.mobilenumber')" :placeholder="$t('person.mobilenumber')"
v-bind:aria-label="$t('person.mobilenumber')" :aria-label="$t('person.mobilenumber')"
aria-describedby="mobilenumber" /> aria-describedby="mobilenumber" />
</div> </div>
@ -80,11 +101,17 @@
<span class="input-group-text" id="email"><i class="fa fa-fw fa-at"></i></span> <span class="input-group-text" id="email"><i class="fa fa-fw fa-at"></i></span>
<input class="form-control form-control-lg" <input class="form-control form-control-lg"
v-model="email" v-model="email"
v-bind:placeholder="$t('person.email')" :placeholder="$t('person.email')"
v-bind:aria-label="$t('person.email')" :aria-label="$t('person.email')"
aria-describedby="email" /> aria-describedby="email" />
</div> </div>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
</div> </div>
</template> </template>
@ -108,6 +135,7 @@ export default {
config: { config: {
altNames: [] altNames: []
}, },
errors: []
} }
}, },
computed: { computed: {
@ -183,6 +211,18 @@ export default {
} }
}, },
methods: { methods: {
checkErrors(e) {
this.errors = [];
if (!this.person.lastName) {
this.errors.push("Le nom ne doit pas être vide.");
}
if (!this.person.firstName) {
this.errors.push("Le prénom ne doit pas être vide.");
}
if (!this.person.gender) {
this.errors.push("Le genre doit être renseigné");
}
},
loadData() { loadData() {
getPerson(this.id) getPerson(this.id)
.then(person => new Promise((resolve, reject) => { .then(person => new Promise((resolve, reject) => {

View File

@ -3,7 +3,8 @@
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: type, id: entity.id }, targetEntity: { name: type, id: entity.id },
buttonText: entity|chill_entity_render_string buttonText: entity|chill_entity_render_string,
isDead: entity.deathdate is not null
} %} } %}
{% endmacro %} {% endmacro %}

View File

@ -9,6 +9,7 @@
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: type, id: entity.id }, targetEntity: { name: type, id: entity.id },
buttonText: entity|chill_entity_render_string, buttonText: entity|chill_entity_render_string,
isDead: entity.deathdate is not null,
parent: parent parent: parent
} %} } %}
{% endmacro %} {% endmacro %}

View File

@ -69,7 +69,8 @@
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: p.id }, targetEntity: { name: 'person', id: p.id },
buttonText: p|chill_entity_render_string buttonText: p|chill_entity_render_string,
isDead: entity.deathdate is not null
} %} } %}
</span> </span>
{% endfor %} {% endfor %}

View File

@ -65,7 +65,8 @@
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: period.requestorPerson.id }, targetEntity: { name: 'person', id: period.requestorPerson.id },
buttonText: period.requestorPerson|chill_entity_render_string buttonText: period.requestorPerson|chill_entity_render_string,
isDead: period.requestorPerson.deathdate is not null
} %} } %}
</span> </span>
{% endif %} {% endif %}
@ -90,7 +91,8 @@
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: p.person.id }, targetEntity: { name: 'person', id: p.person.id },
buttonText: p.person|chill_entity_render_string buttonText: p.person|chill_entity_render_string,
isDead: p.person.deathdate is not null
} %} } %}
</span> </span>
{% endfor %} {% endfor %}

View File

@ -90,7 +90,7 @@
{#- must be on one line to avoid spaces with dash -#} {#- must be on one line to avoid spaces with dash -#}
<time datetime="{{ person.deathdate|date('Y-m-d') }}" title="{{ 'deathdate'|trans }}">{{ person.deathdate|format_date("medium") }}</time> <time datetime="{{ person.deathdate|date('Y-m-d') }}" title="{{ 'deathdate'|trans }}">{{ person.deathdate|format_date("medium") }}</time>
{%- if options['addAge'] -%} {%- if options['addAge'] -%}
<span class="age">{{ 'years_old'|trans({ 'age': person.age }) }}</span> <span class="age">&nbsp;{{ 'years_old'|trans({ 'age': person.age }) }}</span>
{%- endif -%} {%- endif -%}
{%- elseif person.birthdate is not null -%} {%- elseif person.birthdate is not null -%}
<time datetime="{{ person.birthdate|date('Y-m-d') }}" title="{{ 'Birthdate'|trans }}"> <time datetime="{{ person.birthdate|date('Y-m-d') }}" title="{{ 'Birthdate'|trans }}">

View File

@ -59,7 +59,8 @@
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: m.person.id }, targetEntity: { name: 'person', id: m.person.id },
buttonText: m.person|chill_entity_render_string buttonText: m.person|chill_entity_render_string,
isDead: m.person.deathdate is not null
} %} } %}
{%- endfor -%} {%- endfor -%}
{% endif %} {% endif %}

View File

@ -154,7 +154,8 @@
targetEntity: { name: 'person', id: part.person.id }, targetEntity: { name: 'person', id: part.person.id },
action: 'show', action: 'show',
displayBadge: true, displayBadge: true,
buttonText: part.person|chill_entity_render_string buttonText: part.person|chill_entity_render_string,
isDead: part.person.deathdate is not null
} %} } %}
{% else %} {% else %}
{% set participating = true %} {% set participating = true %}
@ -190,7 +191,8 @@
targetEntity: { name: 'person', id: acp.requestorPerson.id }, targetEntity: { name: 'person', id: acp.requestorPerson.id },
action: 'show', action: 'show',
displayBadge: true, displayBadge: true,
buttonText: acp.requestorPerson|chill_entity_render_string buttonText: acp.requestorPerson|chill_entity_render_string,
isDead: acp.requestorPerson.deathdate is not null
} %} } %}
{% endif %} {% endif %}
</div> </div>

View File

@ -11,9 +11,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Service\DocGenerator; namespace Chill\PersonBundle\Service\DocGenerator;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;

View File

@ -16,6 +16,9 @@ The birthdate must be before %date%: La date de naissance doit être avant le %d
'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33623456789': 'Numéro de téléphone invalide: il doit commencer par le préfixe international précédé de "+", ne comporter que des chiffres et faire moins de 20 caractères. Ex: +33623456789' 'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33623456789': 'Numéro de téléphone invalide: il doit commencer par le préfixe international précédé de "+", ne comporter que des chiffres et faire moins de 20 caractères. Ex: +33623456789'
'The email is not valid': 'Le courriel n''est pas valide' 'The email is not valid': 'Le courriel n''est pas valide'
Two addresses has the same validFrom date: La date de validité est identique à celle d'une autre adresse Two addresses has the same validFrom date: La date de validité est identique à celle d'une autre adresse
The firstname cannot be empty: Le prénom ne peut pas être vide
The lastname cannot be empty: Le nom de famille ne peut pas être vide
The gender must be set: Le genre doit être renseigné
#export list #export list
You must select at least one element: Vous devez sélectionner au moins un élément You must select at least one element: Vous devez sélectionner au moins un élément

View File

@ -22,7 +22,8 @@
targetEntity: { name: 'person', id: task.person.id }, targetEntity: { name: 'person', id: task.person.id },
action: 'show', action: 'show',
displayBadge: true, displayBadge: true,
buttonText: task.person|chill_entity_render_string buttonText: task.person|chill_entity_render_string,
isDead: task.person.deathdate is not null
} %} } %}
</span> </span>
{% elseif task.course is not null %} {% elseif task.course is not null %}
@ -36,7 +37,8 @@
targetEntity: { name: 'person', id: part.person.id }, targetEntity: { name: 'person', id: part.person.id },
action: 'show', action: 'show',
displayBadge: true, displayBadge: true,
buttonText: part.person|chill_entity_render_string buttonText: part.person|chill_entity_render_string,
isDead: part.person.deathdate is not null
} %} } %}
{% endfor %} {% endfor %}

View File

@ -50,7 +50,7 @@
<dt>{{ 'thirdparty.Categories'|trans }}</dt> <dt>{{ 'thirdparty.Categories'|trans }}</dt>
{% set types = [] %} {% set types = [] %}
{% for t in thirdParty.types %} {% for t in thirdParty.thirdPartyTypes %}
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %} {% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
{% endfor %} {% endfor %}
{% for c in thirdParty.categories %} {% for c in thirdParty.categories %}