Merge branch '139_demandeur' into 'master'

139 suite et fin

See merge request Chill-Projet/chill-bundles!62
This commit is contained in:
Mathieu Jaumotte 2021-05-25 12:39:00 +00:00
commit 0dbd42ba5b
19 changed files with 493 additions and 95 deletions

View File

@ -13,9 +13,9 @@
div#header-accompanying_course-name { div#header-accompanying_course-name {
background: none repeat scroll 0 0 #718596; background: none repeat scroll 0 0 #718596;
color: #FFF; color: #FFF;
padding-top: 1em; h1 {
padding-bottom: 1em; margin: 0.4em 0;
}
span { span {
a { a {
color: white; color: white;
@ -54,3 +54,10 @@ div.subheader {
color: grey; color: grey;
margin-right: 1em; margin-right: 1em;
} }
table {
ul.record_actions {
margin: 0;
padding: 0.5em;
}
}

View File

@ -37,12 +37,16 @@ const messages = {
ok: "OK", ok: "OK",
cancel: "Annuler", cancel: "Annuler",
close: "Fermer", close: "Fermer",
next: "Suivant",
previous: "Précédent",
back: "Retour", back: "Retour",
check_all: "cocher tout", check_all: "cocher tout",
reset: "réinitialiser" reset: "réinitialiser"
}, },
nav: {
next: "Suivant",
previous: "Précédent",
top: "Haut",
bottom: "Bas",
}
} }
}; };

View File

@ -1,5 +1,6 @@
<template> <template>
<banner></banner> <banner></banner>
<sticky-nav></sticky-nav>
<h1 v-if="accompanyingCourse.step === 'DRAFT'">{{ $t('course.title.draft') }}</h1> <h1 v-if="accompanyingCourse.step === 'DRAFT'">{{ $t('course.title.draft') }}</h1>
<h1 v-else>{{ $t('course.title.active') }}</h1> <h1 v-else>{{ $t('course.title.active') }}</h1>
@ -17,6 +18,7 @@
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import Banner from './components/Banner.vue'; import Banner from './components/Banner.vue';
import StickyNav from './components/StickyNav.vue';
import PersonsAssociated from './components/PersonsAssociated.vue'; import PersonsAssociated from './components/PersonsAssociated.vue';
import Requestor from './components/Requestor.vue'; import Requestor from './components/Requestor.vue';
import SocialIssue from './components/SocialIssue.vue'; import SocialIssue from './components/SocialIssue.vue';
@ -29,6 +31,7 @@ export default {
name: 'App', name: 'App',
components: { components: {
Banner, Banner,
StickyNav,
PersonsAssociated, PersonsAssociated,
Requestor, Requestor,
SocialIssue, SocialIssue,
@ -44,51 +47,60 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
h1 { div#accompanying-course {
margin: 1.5em 0; h1 {
} margin: 1.5em 0;
div.vue-component { }
h2 { div.vue-component {
margin-left: 0.7em; h2 {
margin-left: 0.7em;
position: relative;
&:before {
position: absolute;
/*
content: "\f192"; //circle-dot
content: "\f1dd"; //paragraph
content: "\f292"; //hashtag
content: "\f069"; //asterisk
*/
content: "\f142"; //ellipsis-v
font-family: "ForkAwesome";
color: #718596ab;
left: -22px;
top: 4px;
}
a[name^="section"] {
position: absolute;
top: -3.5em; // ref. stickNav
}
}
padding: 0.8em 0em;
margin: 2em 0;
border: 1px dotted #718596ab;
border-radius: 5px;
border-left: 1px dotted #718596ab;
border-right: 1px dotted #718596ab;
/* debug components
position: relative; position: relative;
&:before { &:before {
content: "vuejs component";
position: absolute; position: absolute;
content: "\f192"; left: 1.5em;
font-family: "ForkAwesome"; top: -0.9em;
color: #718596ab; background-color: white;
left: -28px; color: grey;
top: 4px; padding: 0 0.3em;
} }
a[name^="section"] { */
position: absolute; dd {
top: -2em; margin-left: 1em;
}
& > div {
margin: 1em 3em 0;
}
table {
} }
} }
padding: 0.8em 0em;
margin: 2em 0; }
border: 1px dotted #718596ab;
border-radius: 5px;
border-left: 1px dotted #718596ab;
border-right: 1px dotted #718596ab;
/*
position: relative;
&:before {
content: "vuejs component";
position: absolute;
left: 1.5em;
top: -0.9em;
background-color: white;
color: grey;
padding: 0 0.3em;
}
*/
dd {
margin-left: 1em;
}
& > div {
margin: 1em 3em 0;
}
table {
}
}
</style> </style>

View File

@ -146,6 +146,25 @@ const postResource = (id, payload, method) => {
}); });
}; };
/*
* Endpoint to Add/remove SocialIssue
*/
const postSocialIssue = (id, body, method) => {
//console.log('api body and method', body, method);
const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`;
return fetch(url, {
method: method,
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 { export {
getAccompanyingCourse, getAccompanyingCourse,
patchAccompanyingCourse, patchAccompanyingCourse,
@ -154,4 +173,5 @@ export {
postParticipation, postParticipation,
postRequestor, postRequestor,
postResource, postResource,
postSocialIssue
}; };

View File

@ -43,20 +43,26 @@
</teleport> </teleport>
<teleport to="#header-accompanying_course-details #banner-social-issues"> <teleport to="#header-accompanying_course-details #banner-social-issues">
<div class="grid-4">{{ $t('social_issue.title') }}</div> <div class="grid-12">
<div class="grid-4">_</div> <social-issue
<div class="grid-4">_</div> v-for="issue in accompanyingCourse.socialIssues"
v-bind:key="issue.id"
v-bind:issue="issue">
</social-issue>
</div>
</teleport> </teleport>
</template> </template>
<script> <script>
import ToggleFlags from './ToggleFlags'; import ToggleFlags from './Banner/ToggleFlags';
import SocialIssue from './Banner/SocialIssue.vue';
export default { export default {
name: 'Banner', name: 'Banner',
components: { components: {
ToggleFlags ToggleFlags,
SocialIssue
}, },
computed: { computed: {
accompanyingCourse() { accompanyingCourse() {

View File

@ -0,0 +1,20 @@
<template>
<span class="badge badge-secondary">{{ issue.text }}</span>
</template>
<script>
export default {
name: "SocialIssues",
props: ['issue']
}
</script>
<style lang="scss" scoped>
span.badge {
font-size: 95%;
text-transform: capitalize !important;
font-weight: 500 !important;
margin-bottom: 5px;
margin-right: 1em;
}
</style>

View File

@ -8,17 +8,17 @@
</div--> </div-->
<div> <div>
<div v-if="initialComment">
créé par {{ initialComment.creator.text }}
le {{ $d(initialComment.createdAt.datetime, 'long') }}
<div v-if="initialComment.updatedAt.datetime !== initialComment.createdAt.datetime">
modifié par {{ initialComment.updatedBy.text }}
le {{ $d(initialComment.updatedAt.datetime, 'long') }}
</div>
</div>
<form @submit.prevent="submitform"> <form @submit.prevent="submitform">
<label for="content">{{ $t('comment.label') }}</label> <label for="content">{{ $t('comment.label') }}</label>
<div v-if="initialComment">
{{ $t('comment.created_by', [
initialComment.creator.text,
$d(initialComment.createdAt.datetime, 'long')
]) }}
</div>
<textarea <textarea
name="content" name="content"
v-bind:placeholder="$t('comment.content')" v-bind:placeholder="$t('comment.content')"
@ -27,6 +27,7 @@
ckeditor="ckeditor" ckeditor="ckeditor"
v-model="content"> v-model="content">
</textarea> </textarea>
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
<button type="submit" class="sc-button bt-save">{{ $t('action.save') }}</button> <button type="submit" class="sc-button bt-save">{{ $t('action.save') }}</button>
@ -38,6 +39,7 @@
</a> </a>
</li> </li>
</ul> </ul>
</form> </form>
</div> </div>

View File

@ -43,7 +43,7 @@
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import PersonItem from "./PersonItem.vue" import PersonItem from "./PersonsAssociated/PersonItem.vue"
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue' import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'
export default { export default {

View File

@ -1,11 +1,77 @@
<template> <template>
<div class="vue-component"> <div class="vue-component">
<h2><a name="section-40"></a>{{ $t('referrer.title') }}</h2> <h2><a name="section-40"></a>{{ $t('referrer.title') }}</h2>
<div class="my-4">
<label for="" class="">
{{ $t('referrer.label') }}
</label>
<VueMultiselect
track-by="id"
label="text"
:multiple="false"
:searchable="true"
:placeholder="$t('referrer.placeholder')"
@update:model-value="updateReferrer"
:model-value="value"
:options="options">
</VueMultiselect>
<ul class="record_actions">
<li>
<button
class="sc-button bt-create"
type="button"
name="button"
@click="assignMe">
{{ $t('referrer.assign_me') }}
</button>
</li>
</ul>
</div>
</div> </div>
</template> </template>
<script> <script>
import VueMultiselect from 'vue-multiselect';
//import { getUsers } from '../api';
import { mapState } from 'vuex';
export default { export default {
name: "Referrer", name: "Referrer",
components: { VueMultiselect },
data() {
return {
options: []
}
},
computed: {
...mapState({
value: state => state.accompanyingCourse.user,
}),
},
mounted() {
this.getOptions();
},
methods: {
getOptions() {
//getUsers().then(response => new Promise((resolve, reject) => {
// console.log(response);
// resolve();
//})).catch(er => this.$store.commit('catchError'), error));
},
updateReferrer(value) {
//this.$store.dispatch('updateReferrer', this.transformValue(value));
},
transformValue(value) {
let payload = value;
return { payload, body, method };
},
assignMe() {
console.log('assign me');
}
}
} }
</script> </script>

View File

@ -54,6 +54,9 @@
</li> </li>
</ul> </ul>
</div> </div>
<div v-else>
<label>{{ $t('requestor.counter') }}</label>
</div>
<div> <div>
<add-persons v-if="accompanyingCourse.requestor === null" <add-persons v-if="accompanyingCourse.requestor === null"

View File

@ -42,7 +42,7 @@
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'; import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
import ResourceItem from './ResourceItem.vue'; import ResourceItem from './Resources/ResourceItem.vue';
export default { export default {
name: 'Resources', name: 'Resources',

View File

@ -3,19 +3,21 @@
<h2><a name="section-30"></a>{{ $t('social_issue.title') }}</h2> <h2><a name="section-30"></a>{{ $t('social_issue.title') }}</h2>
<div class="my-4"> <div class="my-4">
<!--label for="selectIssues">{{ $t('social_issue.label') }}</label--> <!--label for="field">{{ $t('social_issue.label') }}</label
-->
<VueMultiselect <VueMultiselect
name="selectIssues" name="field"
v-model="selected" :close-on-select="false"
track-by="id" :allow-empty="true"
label="text" :show-labels="false"
:placeholder="$t('social_issue.label')" track-by="id"
:multiple="true" label="text"
:searchable="true" :multiple="true"
:close-on-select="false" :searchable="false"
:allow-empty="true" :placeholder="$t('social_issue.label')"
:show-labels="false" @update:model-value="updateSocialIssues"
:options="options"> :model-value="value"
:options="options">
</VueMultiselect> </VueMultiselect>
</div> </div>
@ -23,38 +25,56 @@
</template> </template>
<script> <script>
import VueMultiselect from 'vue-multiselect' import VueMultiselect from 'vue-multiselect';
import { getSocialIssues } from '../api' import { getSocialIssues } from '../api';
import { mapState } from 'vuex';
export default { export default {
name: "SocialIssue", name: "SocialIssue",
components: { VueMultiselect }, components: { VueMultiselect },
data () { data() {
return { return {
selected: null,
options: [] options: []
} }
}, },
computed: { computed: {
...mapState({
value: state => state.accompanyingCourse.socialIssues,
}),
}, },
mounted() { mounted() {
this.getOptions(); this.getOptions();
}, },
methods: { methods: {
getOptions() { getOptions() {
getSocialIssues().then(socialIssues => new Promise((resolve, reject) => { getSocialIssues().then(response => new Promise((resolve, reject) => {
console.log('socialIssues', socialIssues); //console.log('get socialIssues', response.results);
this.options = socialIssues.results; this.options = response.results;
resolve(); resolve();
})).catch(error => this.$store.commit('catchError', error)); })).catch(error => this.$store.commit('catchError', error));
}, },
updateSocialIssues(value) {
this.$store.dispatch('updateSocialIssues', this.transformValue(value));
},
transformValue(updated) {
let stored = this.value;
let added = updated.filter(x => stored.indexOf(x) === -1).shift();
let removed = stored.filter(x => updated.indexOf(x) === -1).shift();
let method = (typeof removed === 'undefined') ? 'POST' : 'DELETE';
let changed = (typeof removed === 'undefined') ? added : removed;
let body = { type: "social_issue", id: changed.id };
//console.log('body', body);
//console.log('@@@', method, changed.text);
let payload = updated;
return { payload, body, method };
}
} }
} }
</script> </script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style> <style src="vue-multiselect/dist/vue-multiselect.css"></style>
<style lang="scss" scoped> <style lang="scss">
div.vue-component > div { span.multiselect__tag {
//margin: 3em; background: #e2793d;
} }
</style> </style>

View File

@ -0,0 +1,191 @@
<template>
<teleport to="#content_conainter .container.content .container">
<div id="navmap">
<nav>
<a class="top" href="#top">
<i class="fa fa-fw fa-square"></i>
<span>{{ $t('nav.top') }}</span>
</a>
<item
v-for="item of items"
:key="item.key"
:item="item"
:step="step">
</item>
</nav>
</div>
</teleport>
</template>
<script>
import Item from './StickyNav/Item.vue';
export default {
name: "StickyNav",
components: {
Item
},
data() {
return {
header: document.querySelector("header.navigation.container"),
bannerName: document.querySelector("#header-accompanying_course-name"),
bannerDetails: document.querySelector("#header-accompanying_course-details"),
container: null,
heightSum: null,
stickyNav: null,
limit: 25,
anchors: null,
items: [],
}
},
computed: {
accompanyingCourse() {
return this.$store.state.accompanyingCourse;
},
step() {
return this.accompanyingCourse.step;
},
top() {
return parseInt(window.getComputedStyle(this.stickyNav).getPropertyValue('top').slice(0, -2));
},
},
mounted() {
this.ready();
window.addEventListener('scroll', this.handleScroll);
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll);
},
methods: {
ready() {
// load datas DOM when mounted ready
this.container = document.querySelector("#content_conainter .container.content .container");
this.stickyNav = document.querySelector('#navmap');
this.anchors = document.querySelectorAll("h2 a[name^='section']");
this.initItemsMap();
// TODO resizeObserver not supports IE !
// Listen when elements change size, then recalculate heightSum and initItemsMap
const resizeObserver = new ResizeObserver(entries => {
this.refreshPos();
});
resizeObserver.observe(this.header);
resizeObserver.observe(this.bannerName);
resizeObserver.observe(this.bannerDetails);
resizeObserver.observe(this.container);
},
initItemsMap() {
this.anchors.forEach(anchor => {
this.items.push({
pos: null,
active: false,
key: parseInt(anchor.name.slice(8).slice(0, -1)),
name: '#' + anchor.name
})
});
},
refreshPos() {
//console.log('refreshPos');
this.heightSum = this.header.offsetHeight + this.bannerName.offsetHeight + this.bannerDetails.offsetHeight;
this.anchors.forEach((anchor, i) => {
this.items[i].pos = this.findPos(anchor)['y'];
});
},
findPos(element) {
let posX = 0, posY = 0;
do {
posX += element.offsetLeft;
posY += element.offsetTop;
element = element.offsetParent;
}
while( element != null );
let pos = [];
pos['x'] = posX;
pos['y'] = posY;
return pos;
},
handleScroll(event) {
let pos = this.findPos(this.stickyNav);
let top = this.heightSum + this.top - window.scrollY;
//console.log(window.scrollY);
if (top > this.limit) {
this.stickyNav.style.position = 'absolute';
this.stickyNav.style.left = '-60px';
} else {
this.stickyNav.style.position = 'fixed';
this.stickyNav.style.left = pos['x'] + 'px';
}
this.switchActive();
},
switchActive() {
this.items.forEach((item, i) => {
let next = (this.items[i+1]) ? this.items[i+1].pos : '100000';
item.active =
(window.scrollY >= item.pos & window.scrollY < next) ? true : false;
}, this);
// last item never switch active because scroll reach bottom of page
if (document.body.scrollHeight == window.scrollY + window.innerHeight) {
this.items[this.items.length-1].active = true;
this.items[this.items.length-2].active = false;
} else {
this.items[this.items.length-1].active = false;
}
}
}
}
</script>
<style lang="scss">
div#navmap {
position: absolute;
top: 30px;
left: -60px; //-10%;
nav {
font-size: small;
a {
display: block;
box-sizing: border-box;
margin-bottom: -3px;
color: #71859669;
&.top {
color: #718596;
}
span {
display: none;
}
&:hover,
&.active {
span {
display: inline;
padding-left: 8px;
}
}
&:hover {
color: #718596b5;
}
&.active {
color: #e2793d; //orange
//color: #eec84a; //jaune
}
}
}
}
@media only screen and (max-width: 768px) {
div.sticky-section {
display: none;
}
}
</style>

View File

@ -0,0 +1,30 @@
<template>
<a
v-if="item.key <= 5"
:href="item.name"
:class="{ 'active': isActive }"
>
<i class="fa fa-fw fa-square"></i>
<span>{{ item.key }}</span>
</a>
<a
v-else-if="step === 'DRAFT'"
:href="item.name"
:class="{ 'active': isActive }"
>
<i class="fa fa-fw fa-square"></i>
<span>{{ item.key }}</span>
</a>
</template>
<script>
export default {
name: "Item",
props: ['item', 'step'],
computed: {
isActive() {
return this.item.active;
}
}
}
</script>

View File

@ -39,6 +39,7 @@ const appMessages = {
title: "Demandeur", title: "Demandeur",
add_requestor: "Ajouter un demandeur", add_requestor: "Ajouter un demandeur",
is_anonymous: "Le demandeur est anonyme", is_anonymous: "Le demandeur est anonyme",
counter: "Il n'y a pas encore de demandeur",
type: "Type", type: "Type",
person_id: "id", person_id: "id",
text: "Dénomination", text: "Dénomination",
@ -57,7 +58,10 @@ const appMessages = {
label: "Choisir les problématiques sociales", label: "Choisir les problématiques sociales",
}, },
referrer: { referrer: {
title: "Référent", title: "Référent du parcours",
label: "Vous pouvez choisir un TMS ou vous assigner directement comme référent",
placeholder: "Choisir un TMS",
assign_me: "M'assigner comme référent",
}, },
resources: { resources: {
title: "Interlocuteurs privilégiés", title: "Interlocuteurs privilégiés",
@ -69,7 +73,8 @@ const appMessages = {
comment: { comment: {
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}"
}, },
confirm: { confirm: {
title: "Confirmation", title: "Confirmation",

View File

@ -5,7 +5,8 @@ import { getAccompanyingCourse,
confirmAccompanyingCourse, confirmAccompanyingCourse,
postParticipation, postParticipation,
postRequestor, postRequestor,
postResource } from '../api'; postResource,
postSocialIssue } from '../api';
const debug = process.env.NODE_ENV !== 'production'; const debug = process.env.NODE_ENV !== 'production';
const id = window.accompanyingCourseId; const id = window.accompanyingCourseId;
@ -74,9 +75,12 @@ let initPromise = getAccompanyingCourse(id)
state.accompanyingCourse.confidential = value; state.accompanyingCourse.confidential = value;
}, },
postFirstComment(state, comment) { postFirstComment(state, comment) {
console.log('### mutation: postFirstComment', comment); //console.log('### mutation: postFirstComment', comment);
state.accompanyingCourse.initialComment = comment; state.accompanyingCourse.initialComment = comment;
}, },
updateSocialIssues(state, value) {
state.accompanyingCourse.socialIssues = value;
},
confirmAccompanyingCourse(state, response) { confirmAccompanyingCourse(state, response) {
//console.log('### mutation: confirmAccompanyingCourse: response', response); //console.log('### mutation: confirmAccompanyingCourse: response', response);
state.accompanyingCourse.step = response.step; state.accompanyingCourse.step = response.step;
@ -145,7 +149,7 @@ let initPromise = getAccompanyingCourse(id)
})).catch((error) => { commit('catchError', error) }); })).catch((error) => { commit('catchError', error) });
}, },
toggleIntensity({ commit }, payload) { toggleIntensity({ commit }, payload) {
console.log(payload); //console.log(payload);
patchAccompanyingCourse(id, { type: "accompanying_period", intensity: payload }) patchAccompanyingCourse(id, { type: "accompanying_period", intensity: payload })
.then(course => new Promise((resolve, reject) => { .then(course => new Promise((resolve, reject) => {
commit('toggleIntensity', course.intensity); commit('toggleIntensity', course.intensity);
@ -167,16 +171,24 @@ let initPromise = getAccompanyingCourse(id)
})).catch((error) => { commit('catchError', error) }); })).catch((error) => { commit('catchError', error) });
}, },
postFirstComment({ commit }, payload) { postFirstComment({ commit }, payload) {
console.log('## action: postFirstComment: payload', payload); //console.log('## action: postFirstComment: payload', payload);
patchAccompanyingCourse(id, { type: "accompanying_period", initialComment: payload }) patchAccompanyingCourse(id, { type: "accompanying_period", initialComment: payload })
.then(course => new Promise((resolve, reject) => { .then(course => new Promise((resolve, reject) => {
commit('postFirstComment', course.initialComment); commit('postFirstComment', course.initialComment);
resolve(); resolve();
})).catch((error) => { commit('catchError', error) }); })).catch((error) => { commit('catchError', error) });
}, },
updateSocialIssues({ commit }, { payload, body, method }) {
//console.log('## action: payload', { payload, body, method });
postSocialIssue(id, body, method)
.then(response => new Promise((resolve, reject) => {
//console.log('response', response);
commit('updateSocialIssues', payload);
resolve();
})).catch((error) => { commit('catchError', error) });
},
confirmAccompanyingCourse({ commit }) { confirmAccompanyingCourse({ commit }) {
console.log('## action: confirmAccompanyingCourse'); //console.log('## action: confirmAccompanyingCourse');
confirmAccompanyingCourse(id) confirmAccompanyingCourse(id)
.then(response => new Promise((resolve, reject) => { .then(response => new Promise((resolve, reject) => {
commit('confirmAccompanyingCourse', response); commit('confirmAccompanyingCourse', response);