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

This commit is contained in:
Julien Fastré 2022-03-14 13:15:05 +01:00
commit a49c53772e
11 changed files with 283 additions and 55 deletions

View File

@ -11,6 +11,8 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477)
* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472) * [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
* [person] Add url in accompanying period work evaluations entity and form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/476) * [person] Add url in accompanying period work evaluations entity and form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/476)
* [person] Add document generation in admin and in person/{id}/document (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/464) * [person] Add document generation in admin and in person/{id}/document (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/464)
@ -30,9 +32,11 @@ and this project adheres to
* [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue) * [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue)
* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474) * [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474)
* [template] do not list inactive templates (for doc generator) * [template] do not list inactive templates (for doc generator)
* [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480)
* [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478) * [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478)
* [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490) * [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490)
* [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484) * [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484)
* [address] person badge in address history changed to open OnTheFly with all person info (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/489)
* [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499) * [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499)
* [thirdparty] Add parameter condition to display centers or not (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/500) * [thirdparty] Add parameter condition to display centers or not (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/500)
* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496) * [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496)

View File

@ -1,17 +1,22 @@
/** /**
* Generic api method that can be adapted to any fetch request * Generic api method that can be adapted to any fetch request
*/ */
const makeFetch = (method, url, body) => { const makeFetch = (method, url, body, options) => {
return fetch(url, { let opts = {
method: method, method: method,
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8' 'Content-Type': 'application/json;charset=utf-8'
}, },
body: (body !== null) ? JSON.stringify(body) : null body: (body !== null) ? JSON.stringify(body) : null
}) };
if (typeof options !== 'undefined') {
opts = Object.assign(opts, options);
}
return fetch(url, opts)
.then(response => { .then(response => {
if (response.ok) { if (response.ok) {
console.log('200 error')
return response.json(); return response.json();
} }

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\DependencyInjection\MissingBundleException;
use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController; use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
use Chill\PersonBundle\Doctrine\DQL\AddressPart; use Chill\PersonBundle\Doctrine\DQL\AddressPart;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
@ -415,6 +416,25 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
], ],
], ],
], ],
[
'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Comment::class,
'name' => 'accompanying_period_comment',
'base_path' => '/api/1.0/person/accompanying-period/comment',
'base_role' => 'ROLE_USER',
'actions' => [
'_entity' => [
'methods' => [
Request::METHOD_GET => false,
Request::METHOD_PATCH => true,
Request::METHOD_HEAD => false,
Request::METHOD_DELETE => false,
],
'roles' => [
Request::METHOD_PATCH => AccompanyingPeriodCommentVoter::EDIT,
],
],
],
],
[ [
'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Resource::class, 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Resource::class,
'name' => 'accompanying_period_resource', 'name' => 'accompanying_period_resource',

View File

@ -257,9 +257,11 @@ class AccompanyingPeriod implements
/** /**
* @ORM\ManyToOne( * @ORM\ManyToOne(
* targetEntity=Comment::class * targetEntity=Comment::class,
* cascade={"persist"},
* ) * )
* @Groups({"read"}) * @Groups({"read"})
* @ORM\JoinColumn(onDelete="SET NULL")
*/ */
private ?Comment $pinnedComment = null; private ?Comment $pinnedComment = null;

View File

@ -14,24 +14,27 @@
<ckeditor <ckeditor
name="content" name="content"
v-bind:placeholder="$t('comment.content')" :placeholder="$t('comment.content')"
:editor="editor" :editor="editor"
v-model="content" v-model="content"
tag-name="textarea"> tag-name="textarea">
</ckeditor> </ckeditor>
<div v-if="pinnedComment" class="metadata"> <div class="sub-comment">
<div v-if="pinnedComment !== null && typeof pinnedComment.creator !== 'undefined'" class="metadata">
{{ $t('comment.created_by', [ {{ $t('comment.created_by', [
pinnedComment.creator.text, pinnedComment.creator.text,
$d(pinnedComment.createdAt.datetime, 'long') $d(pinnedComment.updatedAt.datetime, 'long')
]) }} ])
}}
</div>
<div class="loading">
<i v-if="loading" class="fa fa-circle-o-notch fa-spin" :title="$t('loading')"></i>
</div>
</div> </div>
<div> <div>
<ul class="record_actions"> <ul class="record_actions">
<li>
<button type="submit" class="btn btn-save">{{ $t('action.save') }}</button>
</li>
<li v-if="pinnedComment !== null"> <li v-if="pinnedComment !== null">
<a class="btn btn-delete" <a class="btn btn-delete"
@click="removeComment"> @click="removeComment">
@ -50,6 +53,7 @@
<script> <script>
import CKEditor from '@ckeditor/ckeditor5-vue'; import CKEditor from '@ckeditor/ckeditor5-vue';
import ClassicEditor from "ChillMainAssets/module/ckeditor5"; import ClassicEditor from "ChillMainAssets/module/ckeditor5";
import { mapState } from "vuex";
export default { export default {
name: "Comment", name: "Comment",
@ -59,22 +63,58 @@ export default {
data() { data() {
return { return {
editor: ClassicEditor, editor: ClassicEditor,
formdata: { loading: false,
type: "accompanying_period_comment", lastRecordedContent: null,
content: ''
}
} }
}, },
computed: { computed: {
pinnedComment() { ...mapState({
return this.$store.state.accompanyingCourse.pinnedComment; pinnedComment: state => state.accompanyingCourse.pinnedComment,
}, }),
content: { content: {
set(value) { set(value) {
this.formdata.content = value; console.log('new comment value', value);
console.log('previous value', this.lastRecordedContent);
this.lastRecordedContent = value;
setTimeout(() => {
console.log('performing test on ', value);
if (this.lastRecordedContent === value) {
this.loading = true;
if (value !== '') {
this.$store.dispatch('updatePinnedComment', value)
.then(() => {
this.loading = false;
})
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} else {
if (this.$store.state.accompanyingCourse.pinnedComment !== null) {
this.$store.dispatch('removePinnedComment', {id: this.pinnedComment.id})
.then(() => {
this.loading = false;
this.lastRecoredContent = null;
})
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}
}
}
}, 3000);
}, },
get() { get() {
return (this.pinnedComment)? this.pinnedComment.content : {}; return this.pinnedComment ? this.pinnedComment.content : '';
} }
}, },
errors() { errors() {
@ -82,18 +122,11 @@ export default {
} }
}, },
methods: { methods: {
submitform() { onContentChange() {
this.$store.dispatch('postFirstComment', this.formdata) let lastRecordedContent = this.formData.content;
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
removeComment() { removeComment() {
this.$store.dispatch('postFirstComment', {}) this.$store.dispatch('removePinnedComment', {id: this.pinnedComment.id})
.catch(({name, violations}) => { .catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') { if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation})); violations.forEach((violation) => this.$toast.open({message: violation}));
@ -104,15 +137,18 @@ export default {
} }
} }
} }
/*
* TODO
* - [x] delete button in ul record_actions, but not in form
* - [ ] display updatedAt => pinnedComment fetch PATCH content changes MUST NOT change object id !!
*/
</script> </script>
<style lang="scss"> <style lang="scss">
div.ck-editor.ck-reset { div.ck-editor.ck-reset {
margin: 0.6em 0; margin: 0.6em 0;
} }
div.sub-comment {
display: flex;
justify-content: space-between;
div.loading {
margin-right: 6px;
margin-left: 6px;
}
}
</style> </style>

View File

@ -122,7 +122,7 @@ const appMessages = {
title: "Observations", title: "Observations",
label: "Ajout d'une note", label: "Ajout d'une note",
content: "Rédigez une première note…", content: "Rédigez une première note…",
created_by: "créé par {0}, le {1}" created_by: "créé par {0}, mis à jour le {1}"
}, },
confirm: { confirm: {
title: "Confirmation", title: "Confirmation",

View File

@ -42,7 +42,11 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
referrersSuggested: [], referrersSuggested: [],
// all the users available // all the users available
users: [], users: [],
permissions: {} permissions: {},
// controller for updating comment
updateCommentAbortController: null,
// waiting response for inserting first comment
postFirstPinnedCommentResponse: null,
}, },
getters: { getters: {
isParticipationValid(state) { isParticipationValid(state) {
@ -203,9 +207,35 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
//console.log('### mutation: toggleConfidential'); //console.log('### mutation: toggleConfidential');
state.accompanyingCourse.confidential = value; state.accompanyingCourse.confidential = value;
}, },
postFirstComment(state, comment) { setPinnedComment(state, content) {
//console.log('### mutation: postFirstComment', comment); if (null === state.accompanyingCourse.pinnedComment) {
state.accompanyingCourse.pinnedComment = comment; state.accompanyingCourse.pinnedComment = {
id: -1,
content,
type: "accompanying_period_comment",
};
} else {
state.accompanyingCourse.pinnedComment.content = content;
}
},
setPinnedCommentDetails(state, value) {
state.accompanyingCourse.pinnedComment.id = value.id;
if (typeof value.creator !== 'undefined') {
state.accompanyingCourse.pinnedComment.creator = value.creator;
state.accompanyingCourse.pinnedComment.updatedBy = value.updatedBy;
state.accompanyingCourse.pinnedComment.updatedAt = value.updatedAt;
state.accompanyingCourse.pinnedComment.createdAt = value.createdAt;
}
},
removePinnedComment(state, value) {
state.accompanyingCourse.pinnedComment = null;
},
setPinCommentAbortController(state, value) {
state.updateCommentAbortController = value;
},
setPostFirstPinnedCommentResponse(state, value) {
state.postFirstPinnedCommentResponse = value;
}, },
updateSocialIssues(state, value) { updateSocialIssues(state, value) {
console.log('updateSocialIssues', value); console.log('updateSocialIssues', value);
@ -337,6 +367,87 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
throw error; throw error;
}) })
}, },
/**
* Add/remove pinnedComment
*/
removePinnedComment({ commit }, payload) {
const body = {type: "accompanying_period_comment", id: payload.id};
const url = `/api/1.0/person/accompanying-course/${id}/comment.json`;
return makeFetch('DELETE', url, body)
.then((response) => {
commit('removePinnedComment');
})
.catch((error) => {
commit('catchError', error);
throw error;
})
},
/**
* internal method to insert a new comment
*
* @param commit
* @param dispatch
* @param content
* @returns {*}
*/
addPinnedComment({ commit, dispatch }, content) {
const url = `/api/1.0/person/accompanying-course/${id}/comment.json`;
return makeFetch('POST', url, {type: "accompanying_period_comment", content})
.then((response) => {
commit('setPinnedCommentDetails', response);
return dispatch('patchFirstComment', response);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
},
updatePinnedComment({ commit, state, dispatch }, content) {
commit('setPinnedComment', content);
if (state.accompanyingCourse.pinnedComment.id === -1 && state.postFirstPinnedCommentResponse === null) {
let r = dispatch('addPinnedComment', content).then(() => {
commit('setPostFirstPinnedCommentResponse', null);
});
commit('setPostFirstPinnedCommentResponse', r);
} else {
(state.postFirstPinnedCommentResponse === null ? Promise.resolve() : state.postFirstPinnedCommentResponse).then(() => {
dispatch('updateExistingPinnedComment', content);
});
}
},
/**
* internal method to patch an existing comment
*
* @param commit
* @param state
* @param comment
* @returns {*}
*/
updateExistingPinnedComment({ commit, state }, content) {
const payload = {type: "accompanying_period_comment", content, id: state.accompanyingCourse.pinnedComment.id};
const url = `/api/1.0/person/accompanying-period/comment/${payload.id}.json`;
if (state.updateCommentAbortController !== null) {
state.updateCommentAbortController.abort();
commit('setPinCommentAbortController', null);
}
let controller = new AbortController();
commit('setPinCommentAbortController', controller);
return makeFetch('PATCH', url, payload, { signal: controller.signal })
.then((response) => {
commit('setPinCommentAbortController', null);
commit('setPinnedCommentDetails', response);
})
.catch((error) => {
commit('catchError', error);
commit('setPinCommentAbortController', null);
throw error;
})
},
/** /**
* Add/remove/display anonymous requestor * Add/remove/display anonymous requestor
*/ */
@ -606,18 +717,20 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
return Promise.all(promises); return Promise.all(promises);
}, },
postFirstComment({ commit }, payload) { patchFirstComment({ commit }, payload) {
const url = `/api/1.0/person/accompanying-course/${id}.json` const url = `/api/1.0/person/accompanying-course/${id}.json`;
const body = { type: "accompanying_period", pinnedComment: payload } const body = {
type: "accompanying_period",
pinnedComment: {
type: "accompanying_period_comment",
id: payload.id
}
};
return makeFetch('PATCH', url, body) return makeFetch('PATCH', url, body)
.then((response) => {
commit('postFirstComment', response.pinnedComment);
})
.catch((error) => { .catch((error) => {
commit('catchError', error); commit('catchError', error);
throw error; throw error;
}) });
}, },
updateSocialIssues({ state, commit, dispatch }, { payload, body, method }) { updateSocialIssues({ state, commit, dispatch }, { payload, body, method }) {
const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`; const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`;

View File

@ -87,10 +87,14 @@ export default {
currentMembers() { currentMembers() {
let members = this.household.members.filter(m => this.household.current_members_id.includes(m.id)) let members = this.household.members.filter(m => this.household.current_members_id.includes(m.id))
.sort((a, b) => { .sort((a, b) => {
if (a.position.ordering < b.position.ordering) {
const orderA = a.position ? a.position.ordering : 0;
const orderB = b.position ? b.position.ordering : 0;
if (orderA < orderB) {
return -1; return -1;
} }
if (a.position.ordering > b.position.ordering) { if (orderA > orderB) {
return 1; return 1;
} }
if (a.holder && !b.holder) { if (a.holder && !b.holder) {

View File

@ -49,7 +49,12 @@
<li> <li>
<i class="fa fa-li fa-home"></i> <i class="fa fa-li fa-home"></i>
<span class="item-key">{{ "Address of"|trans}} </span> <span class="item-key">{{ "Address of"|trans}} </span>
<span class="chill-entity entity-person badge-person">{{ a.hostPerson|chill_entity_render_box }}</span> {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: a.hostPerson.id },
buttonText: a.hostPerson|chill_entity_render_string,
isDead: a.hostPerson.deathdate is not null
} %}
</li> </li>
<li> <li>
{% set address_date = date(a.startDate|date("m/d/Y")) %} {% set address_date = date(a.startDate|date("m/d/Y")) %}

View File

@ -0,0 +1,38 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Change constraint on pinnedComment in Accompanying period.
*/
final class Version20220310124318 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT fk_e260a868b0804e90');
$this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT fk_e260a868b0804e90 FOREIGN KEY (pinnedcomment_id) REFERENCES chill_person_accompanying_period_comment (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function getDescription(): string
{
return 'Change constraint on pinnedComment in Accompanying period';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT FK_E260A868B0804E90');
$this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT FK_E260A868B0804E90 FOREIGN KEY (pinnedComment_id) REFERENCES chill_person_accompanying_period_comment (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE');
}
}

View File

@ -46,6 +46,7 @@ User: Utilisateur
"Associated tasks": "Tâches associées" "Associated tasks": "Tâches associées"
"My tasks": "Mes tâches" "My tasks": "Mes tâches"
"Tasks for this accompanying period": "Tâches pour ce parcours d'accompagnement" "Tasks for this accompanying period": "Tâches pour ce parcours d'accompagnement"
"Tasks for {{ name }}": "Tâches pour {{ name }}"
"No description": "Pas de description" "No description": "Pas de description"
"No dates specified": "Dates non spécifiées" "No dates specified": "Dates non spécifiées"
"No one assignee": "Aucune personne assignée" "No one assignee": "Aucune personne assignée"