Merge remote-tracking branch 'origin/features/edit-accompanying-period-social-work' into _vue_echanges

This commit is contained in:
2021-06-26 12:14:32 +02:00
99 changed files with 4656 additions and 375 deletions

View File

@@ -1,2 +1,3 @@
require('./sass/chillperson.scss');
require('./sass/person_with_period.scss');
require('./sass/household_banner.scss');

View File

@@ -0,0 +1,57 @@
import { ShowHide } from 'ShowHide/show_hide.js';
const maritalStatus = document.getElementById("maritalStatus");
const maritalStatusDate = document.getElementById("maritalStatusDate");
const personEmail = document.getElementById("personEmail");
const personAcceptEmail = document.getElementById("personAcceptEmail");
const personPhoneNumber = document.getElementById("personPhoneNumber");
const personAcceptSMS = document.getElementById("personAcceptSMS");
new ShowHide({
froms: [maritalStatus],
container: [maritalStatusDate],
test: function(froms) {
for (let f of froms.values()) {
for (let input of f.querySelectorAll('select').values()) {
if (input.value) {
return true
}
}
}
return false;
},
event_name: 'change'
});
new ShowHide({
froms: [personEmail],
container: [personAcceptEmail],
test: function(froms) {
for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) {
if (input.value) {
return true
}
}
}
return false;
},
event_name: 'input'
});
new ShowHide({
froms: [personPhoneNumber],
container: [personAcceptSMS],
test: function(froms) {
for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) {
if (input.value) {
return true
}
}
}
return false;
},
event_name: 'input'
});

View File

@@ -0,0 +1 @@
require('./index.scss');

View File

@@ -0,0 +1,96 @@
#accompanying_course_work_list {
.item {
margin-bottom: 1.5rem;
padding: 1rem;
border: 1px solid gray;
.title_label {
display: block;
margin: 0;
font-variant-caps: small-caps;
+ * {
margin-top: 0;
}
}
.objective_results {
display: grid;
grid-template-areas:
"obj res"
;
grid-template-columns: 50%;
column-gap: 1rem;
padding: 0.3rem;
.obj {
grid-area: obj;
}
.res {
grid-area: res;
ul.result_list {
list-style-type: none;
padding: 0;
}
}
}
.objective_results:nth-child(2n+2) {
background-color: var(--chill-llight-gray);
}
}
.updatedBy {
margin-top: 1rem;
text-align: right;
font-size: 0.8rem;
font-style: italic;
}
}
ul.timeline {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
list-style-type: none;
> li {
min-width: 210px;
div.date {
margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
div.label {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 40px;
border-top: 2px solid var(--chill-green);
&:before {
content: '';
display: inline-block;
position: relative;
width: 25px;
height: 25px;
background-color: white;
border-radius: 25px;
border: 1px solid #ddd;
top: -15px;
}
}
}
}

View File

@@ -0,0 +1,5 @@
.banner-household {
.current-members-explain {
font-weight: 700;
}
}

View File

@@ -1,7 +1,6 @@
/// complete and overwrite flex-table in chillmain.scss
div.list-with-period,
div.list-household-members,
div.list-household-members--summary {
div.list-household-members {
.chill-entity__person {
.chill-entity__person__first-name,

View File

@@ -24,7 +24,6 @@ if (root === 'app') {
.use(i18n)
.component('app', App)
.mount('#accompanying-course');
});
}
@@ -43,8 +42,7 @@ if (root === 'banner') {
.use(store)
.use(i18n)
.component('banner', Banner)
.mount('#accompanying-course');
.mount('#banner-accompanying-course');
});
}

View File

@@ -0,0 +1,224 @@
<template>
<h2>{{ $t('pick_social_issue') }}</h2>
<div id="awc_create_form">
<div id="picking">
<p>{{ $t('pick_social_issue_linked_with_action') }}</p>
<div v-for="si in socialIssues">
<input type="radio" v-bind:value="si.id" name="socialIssue" v-model="socialIssuePicked"> {{ si.title.fr }}
</div>
<div v-if="hasSocialIssuePicked">
<h2>{{ $t('pick_an_action') }}</h2>
<vue-multiselect
v-model="socialActionPicked"
label="text"
:options="socialActionsReachables"
:searchable="true"
:close-on-select="true"
:show-labels="true"
track-by="id"
></vue-multiselect>
</div>
<div v-if="isLoadingSocialActions">
<p>spinner</p>
</div>
<div v-if="hasSocialActionPicked" id="persons">
<h2>{{ $t('persons_involved') }}</h2>
<ul>
<li v-for="p in personsReachables" :key="p.id">
<input type="checkbox" :value="p.id" v-model="personsPicked">
<person :person="p"></person>
</li>
</ul>
</div>
</div>
<div v-if="hasSocialActionPicked" id="start_date">
<p><label>{{ $t('startDate') }}</label> <input type="date" v-model="startDate" /></p>
</div>
<div v-if="hasSocialActionPicked" id="end_date">
<p><label>{{ $t('endDate') }}</label> <input type="date" v-model="endDate" /></p>
</div>
<div id="confirm">
<div v-if="hasErrors">
<p>{{ $t('form_has_errors') }}</p>
<ul>
<li v-for="e in errors">
{{ e }}
</li>
</ul>
</div>
<div>
<ul class="record_actions">
<li class="cancel">
<a href="#" class="sc-button bt-cancel">
{{ $t('action.cancel') }}
</a>
</li>
<li v-if="hasSocialActionPicked">
<button class="sc-button bt-save" v-show="!isPostingWork" @click="submit">
{{ $t('action.save') }}
</button>
<button class="sc-button bt-save" v-show="isPostingWork" disabled>
{{ $t('Save') }}
</button>
</li>
</ul>
</div>
</div>
</div>
</template>
<style lang="scss">
#awc_create_form {
display: grid;
grid-template-areas:
"picking picking"
"start_date end_date"
"confirm confirm"
;
grid-template-columns: 50% 50%;
column-gap: 1.5rem;
#picking {
grid-area: picking;
#persons {
ul {
padding: 0;
list-style-type: none;
}
}
}
#start_date {
grid-area: start_date;
}
#end_date {
grid-area: end_date;
}
#confirm {
grid-area: confirm;
}
}
</style>
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import VueMultiselect from 'vue-multiselect';
import { dateToISO, ISOToDate } from 'ChillMainAssets/js/date.js';
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
const i18n = {
messages: {
fr: {
startDate: "Date de début",
endDate: "Date de fin",
form_has_errors: "Le formulaire comporte des erreurs",
pick_social_issue: "Choisir une problématique sociale",
pick_an_action: "Choisir une action d'accompagnement",
pick_social_issue_linked_with_action: "Indiquez la problématique sociale liée à l'action d'accompagnement",
persons_involved: "Usagers concernés",
}
}
}
export default {
name: 'App',
components: {
VueMultiselect,
Person,
},
methods: {
submit() {
this.$store.dispatch('submit');
}
},
i18n,
computed: {
...mapState([
'socialIssues',
'socialActionsReachables',
'errors',
'personsReachables',
]),
...mapGetters([
'hasSocialIssuePicked',
'hasSocialActionPicked',
'isLoadingSocialActions',
'isPostingWork',
'hasErrors',
]),
personsPicked: {
get() {
let s = this.$store.state.personsPicked.map(p => p.id);
console.log('persons picked', s);
return s;
},
set(v) {
console.log('persons picked', v);
this.$store.commit('setPersonsPickedIds', v);
}
},
socialIssuePicked: {
get() {
let s = this.$store.state.socialIssuePicked;
if (s === null) {
return null;
}
return s.id;
},
set(value) {
this.$store.dispatch('pickSocialIssue', value);
}
},
socialActionPicked: {
get() {
return this.$store.state.socialActionPicked;
},
set(value) {
this.$store.commit('setSocialAction', value);
}
},
startDate: {
get() {
let d = this.$store.state.startDate;
return dateToISO(d);
},
set(value) {
this.$store.commit('setStartDate', ISOToDate(value));
}
},
endDate: {
get() {
return dateToISO(this.$store.state.endDate);
},
set(value) {
this.$store.commit('setEndDate', ISOToDate(value));
}
},
}
}
</script>

View File

@@ -0,0 +1,15 @@
import { createApp } from 'vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { store } from './store';
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
import App from './App.vue';
const i18n = _createI18n(personMessages);
const app = createApp({
template: `<app></app>`,
})
.use(store)
.use(i18n)
.component('app', App)
.mount('#accompanying_course_work_create');

View File

@@ -0,0 +1,175 @@
import { createStore } from 'vuex';
import { datetimeToISO } from 'ChillMainAssets/js/date.js';
import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js';
import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js';
const debug = process.env.NODE_ENV !== 'production';
const store = createStore({
strict: debug,
state: {
accompanyingCourse: window.accompanyingCourse,
socialIssues: window.accompanyingCourse.socialIssues,
socialIssuePicked: null,
socialActionsReachables: [],
socialActionPicked: null,
personsPicked: window.accompanyingCourse.participations.filter(p => p.endDate == null)
.map(p => p.person),
personsReachables: window.accompanyingCourse.participations.filter(p => p.endDate == null)
.map(p => p.person),
startDate: new Date(),
endDate: null,
isLoadingSocialActions: false,
isPostingWork: false,
errors: [],
},
getters: {
hasSocialActionPicked(state) {
console.log(state.socialActionPicked);
return null !== state.socialActionPicked;
},
hasSocialIssuePicked(state) {
console.log(state.socialIssuePicked);
return null !== state.socialIssuePicked;
},
isLoadingSocialActions(state) {
return state.isLoadingSocialActions;
},
isPostingWork(state) {
return state.isPostingWork;
},
buildPayloadCreate(state) {
let payload = {
type: 'accompanying_period_work',
socialAction: {
type: 'social_work_social_action',
id: state.socialActionPicked.id
},
startDate: {
datetime: datetimeToISO(state.startDate)
},
persons: []
};
for (let i in state.personsPicked) {
payload.persons.push({
id: state.personsPicked[i].id,
type: 'person'
});
}
if (null !== state.endDate) {
payload.endDate = {
datetime: datetimeToISO(state.endDate)
};
}
return payload;
},
hasErrors(state) {
return state.errors.length > 0;
},
},
mutations: {
setSocialActionsReachables(state, actions) {
console.log('set social action reachables');
console.log(actions);
state.socialActionsReachables = actions;
},
setSocialAction(state, socialAction) {
console.log('socialAction', socialAction);
state.socialActionPicked = socialAction;
},
setSocialIssue(state, socialIssueId) {
console.log('set social issue', socialIssueId);
if (socialIssueId === null) {
state.socialIssuePicked = null;
} else {
let mapped = state.socialIssues
.find(e => e.id === socialIssueId);
console.log('mapped', mapped);
state.socialIssuePicked = mapped;
console.log('social issue setted', state.socialIssuePicked);
}
},
setIsLoadingSocialActions(state, s) {
state.isLoadingSocialActions = s;
},
setPostingWork(state) {
state.isPostingWork = true;
},
setStartDate(state, date) {
state.startDate = date;
},
setEndDate(state, date) {
state.endDate = date;
},
setPersonsPickedIds(state, ids) {
console.log('persons ids', ids);
state.personsPicked = state.personsReachables
.filter(p => ids.includes(p.id))
},
addErrors(state, { errors, cancel_posting }) {
console.log('add errors', errors);
state.errors = errors;
if (cancel_posting) {
state.isPostingWork = false;
}
},
},
actions: {
pickSocialIssue({ commit }, socialIssueId) {
console.log('pick social issue');
commit('setIsLoadingSocialActions', true);
commit('setSocialAction', null);
commit('setSocialActionsReachables', []);
commit('setSocialIssue', null);
findSocialActionsBySocialIssue(socialIssueId).then(
(response) => {
console.log(response);
console.log(response.results);
commit('setSocialIssue', socialIssueId);
commit('setSocialActionsReachables', response.results);
commit('setIsLoadingSocialActions', false);
})
.catch(err => {
console.error(err);
});
},
submit({ commit, getters, state }) {
console.log('submit');
let
payload = getters.buildPayloadCreate,
errors = [];
commit('setPostingWork');
create(state.accompanyingCourse.id, payload)
.then( ({status, data}) => {
console.log('created return', { status, data});
if (status === 200) {
console.log('created, nothing to do here any more. Bye-bye!');
window.location.assign(`/fr/person/accompanying-period/work/${data.id}/edit`);
} else if (status === 422) {
console.log(data);
for (let i in data.violations) {
console.log(i);
console.log(data.violations[i].title);
errors.push(data.violations[i].title);
}
console.log('errors after reseponse handling', errors);
console.log({errors, cancel_posting: true});
commit('addErrors', { errors, cancel_posting: true });
}
});
},
},
});
export { store };

View File

@@ -0,0 +1,486 @@
<template>
<div id="workEditor">
<div id="title">
<label class="action_title_label">{{ $t('action_title') }}</label>
<p class="action_title">{{ work.socialAction.text }}</p>
</div>
<div id="startDate">
<label>{{ $t('startDate') }}</label>
<input type="date" v-model="startDate" />
</div>
<div id="endDate">
<label>{{ $t('endDate') }}</label>
<input type="date" v-model="endDate" />
</div>
<div id="comment">
<label>Commentaire</label>
<ckeditor
:editor="editor"
v-model="note"
tag-name="textarea"
></ckeditor>
</div>
<div id="objectives" class="objectives_list">
<div class="title" aria="hidden">
<div><h3>Motifs - objectifs - dispositifs</h3></div>
<div><h3>Orientations - résultats</h3></div>
</div>
<!-- results which are not attached to an objective -->
<div v-if="hasResultsForAction">
<div class="results_without_objective">
{{ $t('results_without_objective') }}
</div>
<div>
<add-result :availableResults="resultsForAction" destination="action"></add-result>
</div>
</div>
<!-- results which **are** attached to an objective -->
<div v-for="g in goalsPicked">
<div>
<div @click="removeGoal(g)" class="objective-title">
<i class="fa fa-times"></i>
{{ g.goal.title.fr }}
</div>
</div>
<div>
<add-result destination="goal" :goal="g.goal"></add-result>
</div>
</div>
<!-- box to add goal -->
<div class="add_goal">
<div>
<div v-if="showAddObjective">
<p>Motifs, objectifs et dispositifs disponibles pour ajout&nbsp;:</p>
<ul class="list-objectives">
<li v-for="g in availableForCheckGoal" @click="addGoal(g)" class="badge badge-primary">
<i class="fa fa-plus"></i>
{{ g.title.fr }}
</li>
</ul>
</div>
<ul class="record_actions">
<li>
<button @click="toggleAddObjective" class="sc-button bt-create">
Ajouter un objectif
</button>
</li>
</ul>
</div>
<div><!-- empty for results --></div>
</div>
</div>
<div id="persons">
<h2>{{ $t('persons_involved') }}</h2>
<ul>
<li v-for="p in personsReachables" :key="p.id">
<input type="checkbox" :value="p.id" v-model="personsPicked">
<person :person="p"></person>
</li>
</ul>
</div>
<div id="handlingThirdParty">
<h2>Tiers traitant</h2>
<div v-if="!hasHandlingThirdParty">
<p class="chill-no-data-statement">
Aucun tiers traitant
</p>
<ul class="record_actions">
<li>
<add-persons
buttonTitle="Indiquer un tiers traitant"
modalTitle="Choisir un tiers"
v-bind:key="handlingThirdPartyPicker.key"
v-bind:options="handlingThirdPartyPicker.options"
@addNewPersons="setHandlingThirdParty"
ref="handlingThirdPartyPicker"> <!-- to cast child method -->
</add-persons>
</li>
</ul>
</div>
<div v-else>
<p>{{ handlingThirdParty.text }}</p>
<show-address :address="handlingThirdParty.address"></show-address>
<ul class="record_actions">
<li>
<button class="sc-button bt-delete" @click="removeHandlingThirdParty">
Supprimer le tiers traitant
</button>
</li>
</ul>
</div>
</div>
<div id="thirdParties">
<h2>Tiers intervenants</h2>
<div v-if="!hasThirdParties">
<p class="chill-no-data-statement">Aucun tiers intervenant</p>
</div>
<div v-else>
<ul>
<li v-for="t in thirdParties">
<p>{{ t.text }}</p>
<show-address :address="t.address"></show-address>
<ul class="record_actions">
<button class="sc-button bt-delete" @click="removeThirdParty(t)"></button>
</ul>
</li>
</ul>
</div>
<ul class="record_actions">
<li>
<add-persons
buttonTitle="Ajouter des tiers"
modalTitle="Choisir des tiers"
v-bind:key="thirdPartyPicker.key"
v-bind:options="thirdPartyPicker.options"
@addNewPersons="addThirdParties"
ref="thirdPartyPicker"> <!-- to cast child method -->
</add-persons>
</li>
</ul>
</div>
<div id="errors" v-if="errors.length > 0">
<p>Veuillez corriger les erreurs suivantes:</p>
<ul>
<li v-for="e in errors">{{ e }}</li>
</ul>
</div>
</div>
<ul class="record_actions sticky-form-buttons">
<li v-if="!isPosting">
<button
class="sc-button bt-save"
@click="submit"
>
{{ $t('action.save') }}
</button>
</li>
<li v-if="isPosting">
<button
class="sc-button bt-save"
disabled
>
{{ $t('action.save') }}
</button>
</li>
</ul>
</template>
<style lang="scss">
#workEditor {
display: grid;
grid-template-areas:
"title title"
"startDate endDate"
"comment comment"
"objectives objectives"
"persons persons"
"handling handling"
"tparties tparties"
"errors errors"
;
grid-template-columns: 50%;
column-gap: 1rem;
#title {
grid-area: title;
.action_title_label {
margin-bottom: 0;
}
.action_title {
margin-top: 0;
font-weight: bold;
font-size: 1.5rem;
}
}
#startDate {
grid-area: startDate;
}
#endDate {
grid-area: endDate;
}
#comment {
grid-area: comment;
}
#objectives {
grid-area: objectives;
> div.title {
background-color: var(--chill-light-gray);
color: white;
h3 {
text-align: center;
}
}
> div {
display: grid;
grid-template-areas: "obj res";
grid-template-columns: 50%;
column-gap: 1rem;
> div {
&:nth-child(1) {
grid-area: obj;
}
&:nth-child(2) {
grid-area: res;
}
}
> div.results_without_objective {
background: repeating-linear-gradient(
45deg,
var(--chill-light-gray),
var(--chill-light-gray) 10px,
var(--chill-llight-gray) 10px,
var(--chill-llight-gray) 20px
);
text-align: center;
font-weight: 700;
padding-top: 1.5rem;
}
}
ul.list-objectives {
list-style-type: none;
padding: 0;
li {
margin: 0.5rem;
}
}
div.objective-title {
margin-top: 1rem;
font-size: 1.5rem;
font-weight: bold;
text-align: center;
i.fa {
padding: 0.25rem;
}
i.fa-times {
background-color: red;
color: white;
}
}
li.badge, div.badge {
padding-bottom: 0;
padding-top: 0;
padding-left: 0;
i.fa {
padding: 0.25rem;
}
i.fa-plus {
background-color: green;
}
}
}
#persons {
grid-area: persons;
}
#handlingThirdParty {
grid-area: handling;
}
#thirdParties {
grid-area: tparties;
}
#errors {
grid-area: errors;
}
}
</style>
<script>
import { mapState, mapGetters, } from 'vuex';
import { dateToISO, ISOToDate, ISOToDatetime } from 'ChillMainAssets/js/date.js';
import CKEditor from '@ckeditor/ckeditor5-vue';
import ClassicEditor from 'ChillMainAssets/modules/ckeditor5/index.js';
import AddResult from './_components/AddResult.vue';
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
import ShowAddress from 'ChillMainAssets/vuejs/_components/ShowAddress.vue';
const i18n = {
messages: {
fr: {
action_title: "Action d'accompagnement",
startDate: "Date de début",
endDate: "Date de fin",
results_without_objective: "Résultats - orientations sans objectifs",
add_objectif: "Ajouter un motif - objectif - dispositif",
persons_involved: "Usagers concernés",
}
}
};
export default {
name: 'App',
components: {
ckeditor: CKEditor.component,
AddResult,
AddPersons,
Person,
ShowAddress,
},
i18n,
data() {
return {
editor: ClassicEditor,
showAddObjective: false,
handlingThirdPartyPicker: {
key: 'handling-third-party',
options: {
type: [ 'thirdparty' ],
priority: null,
uniq: true
},
},
thirdPartyPicker: {
key: 'third-party',
options: {
type: [ 'thirdparty' ],
priority: null,
uniq: false,
},
}
};
},
computed: {
...mapState([
'work',
'resultsForAction',
'goalsPicked',
'personsReachables',
'handlingThirdParty',
'thirdParties',
'isPosting',
'errors',
]),
...mapGetters([
'hasResultsForAction',
'hasHandlingThirdParty',
'hasThirdParties',
]),
startDate: {
get() {
console.log('get start date', this.$store.state.startDate);
return dateToISO(this.$store.state.startDate);
},
set(v) {
this.$store.commit('setStartDate', ISOToDate(v));
}
},
endDate: {
get() {
console.log('get end date', this.$store.state.endDate);
return dateToISO(this.$store.state.endDate);
},
set(v) {
this.$store.commit('setEndDate', ISOToDate(v));
}
},
note: {
get() {
return this.$store.state.note;
},
set(v) {
this.$store.commit('setNote', v);
}
},
availableForCheckGoal() {
let pickedIds = this.$store.state.goalsPicked.map(g => g.goal.id);
console.log('pickeds goals id', pickedIds);
console.log(this.$store.state.goalsForAction);
return this.$store.state.goalsForAction.filter(g => !pickedIds.includes(g.id));
},
personsPicked: {
get() {
let s = this.$store.state.personsPicked.map(p => p.id);
console.log('persons picked', s);
return s;
},
set(v) {
console.log('persons picked', v);
this.$store.commit('setPersonsPickedIds', v);
}
},
},
methods: {
toggleAddObjective() {
this.showAddObjective = !this.showAddObjective;
},
addGoal(g) {
console.log('add Goal', g);
this.$store.commit('addGoal', g);
},
removeGoal(g) {
console.log('remove goal', g);
this.$store.commit('removeGoal', g);
},
setHandlingThirdParty({ selected, modal }) {
console.log('setHandlingThirdParty', selected);
this.$store.commit('setHandlingThirdParty', selected.shift().result);
this.$refs.handlingThirdPartyPicker.resetSearch();
modal.showModal = false;
},
removeHandlingThirdParty() {
console.log('removeHandlingThirdParty');
this.$store.commit('setHandlingThirdParty', null);
},
addThirdParties({ selected, modal}) {
console.log('addThirdParties', selected);
this.$store.commit('addThirdParties', selected.map(r => r.result));
this.$refs.thirdPartyPicker.resetSearch();
modal.showModal = false;
},
removeThirdParty(t) {
console.log('remove third party', t);
this.$store.commit('removeThirdParty', t);
},
submit() {
this.$store.dispatch('submit');
},
}
};
</script>

View File

@@ -0,0 +1,166 @@
<template>
<div class="addResult" v-if="hasResult">
<p class="chill-no-data-statement" v-if="pickedResults.length ===0">
Aucun résultat associé
</p>
<ul class="list-results">
<li v-for="r in pickedResults" @click="removeResult(r)" class="badge badge-primary">
<i class="fa fa-times"></i>
{{ r.title.fr }}
</li>
<template v-if="isExpanded">
<li v-for="r in availableForCheckResults" @click="addResult(r)" class="badge badge-primary">
<i class="fa fa-plus"></i>
{{ r.title.fr }}
</li>
</template>
</ul>
<ul class="record_actions">
<li v-if="isExpanded">
<button @click="toggleSelect" class="sc-button bt-hide" >
<i class="fa fa-eye-slash"></i>
Masquer résultats et orientations disponibles
</button>
</li>
<li v-else>
<button @click="toggleSelect" class="sc-button bt-show">
Afficher résultats et orientations disponibles
</button>
</li>
</ul>
</div>
<div class="noResult" v-if="!hasResult">
<div class="chill-no-data-statement">
{{ $t('goal_has_no_result') }}
</div>
</div>
</template>
<style lang="scss">
button.hide {
background-color: rgb(51, 77, 92);
}
ul.list-results {
list-style-type: none;
padding: 0;
li {
margin: 0.5rem;
}
li.badge {
padding-bottom: 0;
padding-top: 0;
padding-left: 0;
i.fa {
/*border-radius: 0.25rem; */
padding: 0.25rem;
}
i.fa-plus {
background-color: green;
}
i.fa-times {
background-color: red;
color: white;
}
}
}
</style>
<script>
const i18n = {
messages: {
fr: {
add_a_result: "Résultat - orientation disponibles",
goal_has_no_result: "Aucun résultat - orientation disponible",
}
}
};
export default {
name: "AddResult",
props: [ 'destination', 'goal' ],
i18n,
data() {
return {
isExpanded: false,
};
},
computed: {
hasResult() {
if (this.destination === 'action') {
return this.$store.state.resultsForAction.length > 0;
} else if (this.destination === 'goal') {
return this.$store.getters.resultsForGoal(this.goal).length > 0;
}
throw Error(`this.destination is not implemented: ${this.destination}`);
},
pickedResults() {
console.log('get checked');
console.log('this.destination', this.destination);
console.log('this.goal', this.goal);
if (this.destination === 'action') {
return this.$store.state.resultsPicked;
} else if (this.destination === 'goal') {
return this.$store.getters.resultsPickedForGoal(this.goal);
}
throw Error(`this.destination is not implemented: ${this.destination}`);
},
availableForCheckResults() {
console.log('availableForCheckResults');
console.log('this.destination', this.destination);
console.log('this.goal', this.goal);
if (this.destination === 'action') {
let pickedIds = this.$store.state.resultsPicked.map(r => r.id);
console.log('picked ids', pickedIds);
return this.$store.state.resultsForAction.filter(r => !pickedIds.includes(r.id));
} else if (this.destination === 'goal') {
console.log('results picked for goal', this.$store.getters.resultsPickedForGoal(this.goal));
let pickedIds = this.$store.getters.resultsPickedForGoal(this.goal).map(r => r.id);
return this.$store.getters.resultsForGoal(this.goal).filter(r => !pickedIds.includes(r.id));
}
throw Error(`this.destination is not implemented: ${this.destination}`);
}
},
methods: {
toggleSelect() {
this.isExpanded = !this.isExpanded;
},
addResult(r) {
console.log('addResult', r);
if (this.destination === 'action') {
this.$store.commit('addResultPicked', r);
return;
} else if (this.destination === 'goal') {
this.$store.commit('addResultForGoalPicked', { goal: this.goal, result: r });
return;
}
throw Error(`this.destination is not implemented: ${this.destination}`);
},
removeResult(r) {
console.log('removeresult', r);
if (this.destination === 'action') {
this.$store.commit('removeResultPicked', r);
return;
} else if (this.destination === 'goal') {
this.$store.commit('removeResultForGoalPicked', { goal: this.goal, result: r });
return;
}
throw Error(`this.destination is not implemented: ${this.destination}`);
}
}
}
</script>

View File

@@ -0,0 +1,15 @@
import { createApp } from 'vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { store } from './store';
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
import App from './App.vue';
const i18n = _createI18n(personMessages);
const app = createApp({
template: `<app></app>`,
})
.use(store)
.use(i18n)
.component('app', App)
.mount('#accompanying_course_work_edit');

View File

@@ -0,0 +1,304 @@
import { createStore } from 'vuex';
import { datetimeToISO, ISOToDatetime } from 'ChillMainAssets/js/date.js';
import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js';
import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js';
const debug = process.env.NODE_ENV !== 'production';
console.log(window.accompanyingCourseWork);
const store = createStore({
strict: debug,
state: {
work: window.accompanyingCourseWork,
startDate: ISOToDatetime(window.accompanyingCourseWork.startDate.datetime),
endDate: (window.accompanyingCourseWork.endDate !== null ?
ISOToDatetime(window.accompanyingCourseWork.endDate.datetime) : null),
note: window.accompanyingCourseWork.note,
goalsPicked: window.accompanyingCourseWork.goals,
resultsPicked: window.accompanyingCourseWork.results,
resultsForAction: [],
goalsForAction: [],
resultsForGoal: [],
personsPicked: window.accompanyingCourseWork.persons,
personsReachables: window.accompanyingCourseWork.accompanyingPeriod.participations.filter(p => p.endDate == null)
.map(p => p.person),
handlingThirdParty: window.accompanyingCourseWork.handlingThierParty,
thirdParties: window.accompanyingCourseWork.thirdParties,
isPosting: false,
errors: [],
},
getters: {
socialAction(state) {
return state.work.socialAction;
},
hasResultsForAction(state) {
return state.resultsForAction.length > 0;
},
resultsForGoal: (state) => (goal) => {
let founds = state.resultsForGoal.filter(r => r.goalId === goal.id);
return founds === undefined ? [] : founds;
},
resultsPickedForGoal: (state) => (goal) => {
let found = state.goalsPicked.find(g => g.goal.id === goal.id);
return found === undefined ? [] : found.results;
},
hasHandlingThirdParty(state) {
return state.handlingThirdParty !== null;
},
hasThirdParties(state) {
return state.thirdParties.length > 0;
},
buildPayload(state) {
return {
type: 'accompanying_period_work',
id: state.work.id,
startDate: {
datetime: datetimeToISO(state.startDate)
},
endDate: state.endDate === null ? null : {
datetime: datetimeToISO(state.endDate)
},
note: state.note,
persons: state.personsPicked.map(p => ({id: p.id, type: p.type})),
handlingThierParty: state.handlingThirdParty === null ? null : {
id: state.handlingThirdParty.id,
type: state.handlingThirdParty.type
},
results: state.resultsPicked.map(r => ({id: r.id, type: r.type})),
thirdParties: state.thirdParties.map(t => ({id: t.id, type: t.type})),
goals: state.goalsPicked.map(g => {
let o = {
type: g.type,
note: g.note,
goal: {
type: g.goal.type,
id: g.goal.id,
},
results: g.results.map(r => ({id: r.id, type: r.type})),
};
if (g.id !== undefined) {
o.id = g.id;
}
return o;
})
};
}
},
mutations: {
setStartDate(state, date) {
state.startDate = date;
},
setEndDate(state, date) {
state.endDate = date;
},
setResultsForAction(state, results) {
console.log('set results for action', results);
state.resultsForAction = results;
},
setResultsForGoal(state, { goal, results }) {
console.log('set results for goal', results);
state.goalsForAction.push(goal);
for (let i in results) {
let r = results[i];
r.goalId = goal.id;
console.log('adding result', r);
state.resultsForGoal.push(r);
}
},
addResultPicked(state, result) {
state.resultsPicked.push(result);
},
removeResultPicked(state, result) {
state.resultsPicked = state.resultsPicked.filter(r => r.id !== result.id);
},
addGoal(state, goal) {
let g = {
type: "accompanying_period_work_goal",
goal: goal,
note: '',
results: []
}
state.goalsPicked.push(g);
},
removeGoal(state, goal) {
state.goalsPicked = state.goalsPicked.filter(g => g.id !== goal.id);
},
addResultForGoalPicked(state, { goal, result}) {
let found = state.goalsPicked.find(g => g.goal.id === goal.id);
console.log('adResultForGoalPicked');
console.log('found', found);
console.log('goal', goal);
console.log('result', result);
if (found === undefined) {
return;
}
found.results.push(result);
},
removeResultForGoalPicked(state, { goal, result}) {
let found = state.goalsPicked.find(g => g.goal.id === goal.id);
if (found === undefined) {
return;
}
found.results = found.results.filter(r => r.id !== result.id);
},
setPersonsPickedIds(state, ids) {
console.log('persons ids', ids);
state.personsPicked = state.personsReachables
.filter(p => ids.includes(p.id))
},
setNote(state, note) {
state.note = note;
},
setHandlingThirdParty(state, thirdParty) {
state.handlingThirdParty = thirdParty;
},
addThirdParties(state, thirdParties) {
console.log('addThirdParties', thirdParties);
// filter to remove existing thirdparties
let ids = state.thirdParties.map(t => t.id);
let unexistings = thirdParties.filter(t => !ids.includes(t.id));
console.log('unexisting third parties', unexistings);
for (let i in unexistings) {
state.thirdParties.push(unexistings[i]);
}
},
removeThirdParty(state, thirdParty) {
state.thirdParties = state.thirdParties
.filter(t => t.id !== thirdParty.id);
},
setErrors(state, errors) {
console.log('handling errors', errors);
state.errors = errors;
},
setIsPosting(state, st) {
state.isPosting = st;
},
},
actions: {
getReachablesGoalsForAction({ getters, commit, dispatch }) {
console.log('getReachablesGoalsForAction');
let
socialActionId = getters.socialAction.id,
url = `/api/1.0/person/social-work/goal/by-social-action/${socialActionId}.json`
;
console.log(url);
window
.fetch(
url
).then( response => {
if (response.ok) {
return response.json();
}
throw { m: 'Error while retriving goal for social action', s: response.status, b: response.body };
}).then( data => {
for (let i in data.results) {
dispatch('getReachablesResultsForGoal', data.results[i]);
}
}).catch( errors => {
commit('addErrors', errors);
});
},
getReachablesResultsForGoal({ commit }, goal) {
console.log('getReachablesResultsForGoal');
let
url = `/api/1.0/person/social-work/result/by-goal/${goal.id}.json`
;
console.log(url);
window.fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw { m: 'Error while retriving results for goal', s: response.status, b: response.body };
})
.then(data => {
console.log('data');
commit('setResultsForGoal', { goal, results: data.results });
});
},
getReachablesResultsForAction({ getters, commit }) {
console.log('getReachablesResultsForAction');
let
socialActionId = getters.socialAction.id,
url = `/api/1.0/person/social-work/result/by-social-action/${socialActionId}.json`
;
console.log(url);
window.fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw { m: 'Error while retriving results for social action', s: response.status, b: response.body };
})
.then(data => {
console.log('data retrived', data);
commit('setResultsForAction', data.results);
});
},
submit({ getters, state, commit }) {
let
payload = getters.buildPayload,
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`,
errors = []
;
console.log('action subitting', payload, url);
commit('setIsPosting', true);
window.fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}).then(response => {
if (response.ok || response.status === 422) {
return response.json().then(data => ({data, status: response.status}));
}
throw new Error(response.status);
}).then(({data, status}) => {
if (status === 422) {
for (let i in data.violations) {
errors.push(data.violations[i].title);
}
commit('setErrors', errors);
commit('setIsPosting', false);
} else {
console.info('nothing to do here, bye bye');
window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`);
}
}).catch(e => {
commit('setErrors', [
'Erreur serveur ou réseau: veuillez ré-essayer. Code erreur: ' + e
]);
commit('setIsPosting', false);
});
},
initAsync({ dispatch }) {
dispatch('getReachablesResultsForAction');
dispatch('getReachablesGoalsForAction');
},
}
});
store.dispatch('initAsync');
export { store };

View File

@@ -24,6 +24,12 @@
<div v-if="errors.length > 0">
{{ errors }}
</div>
<div v-if="loading">
{{ $t('loading') }}
</div>
<div v-if="success">
{{ $t('household_address_move_success') }}
</div>
</div>
<div>
<ul class="record_actions sticky-form-buttons">
@@ -65,6 +71,12 @@ export default {
},
errors() {
return this.$store.state.errorMsg;
},
loading() {
return this.$store.state.loading;
},
success() {
return this.$store.state.success;
}
},
methods: {

View File

@@ -7,7 +7,9 @@ const appMessages = {
add_an_address_to_household: 'Déménager le ménage',
validFrom: 'Date du déménagement',
move_date: 'Date du déménagement',
back_to_the_list: 'Retour à la liste'
back_to_the_list: 'Retour à la liste',
household_address_move_success: 'La nouvelle adresse du ménage est enregistrée',
loading: 'chargement en cours...'
}
};

View File

@@ -12,7 +12,9 @@ const store = createStore({
newAddress: {},
household: {},
validFrom: {},
errorMsg: []
errorMsg: [],
loading: false,
success: false
},
getters: {
},
@@ -31,12 +33,18 @@ const store = createStore({
addDateToAddress(state, validFrom) {
console.log('@M addDateToAddress address.validFrom', validFrom);
state.validFrom = validFrom;
},
setLoading(state, b) {
state.loading = b;
},
setSuccess(state, b) {
state.success = b;
}
},
actions: {
addAddress({ commit }, payload) {
console.log('@A addAddress payload', payload);
commit('setLoading', true);
if('newPostalCode' in payload){
postPostalCode(payload.newPostalCode)
.then(postalCode => {
@@ -46,9 +54,11 @@ const store = createStore({
.then(address => new Promise((resolve, reject) => {
commit('addAddress', address);
resolve();
commit('setLoading', false);
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
})
@@ -57,15 +67,17 @@ const store = createStore({
.then(address => new Promise((resolve, reject) => {
commit('addAddress', address);
resolve();
commit('setLoading', false);
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
}
},
addDateToAddressAndAddressToHousehold({ commit }, payload) {
console.log('@A addDateToAddressAndAddressToHousehold payload', payload);
commit('setLoading', true);
patchAddress(payload.addressId, payload.body)
.then(address => new Promise((resolve, reject) => {
commit('addDateToAddress', address.validFrom);
@@ -74,14 +86,18 @@ const store = createStore({
postAddressToHousehold(payload.householdId, payload.addressId)
.then(household => new Promise((resolve, reject) => {
commit('addAddressToHousehold', household);
commit('setSuccess', true);
commit('setLoading', false);
resolve();
}))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
})
))
.catch((error) => {
commit('catchError', error);
commit('setLoading', false);
});
},
}

View File

@@ -1,13 +1,13 @@
<template>
<household></household>
<concerned></concerned>
<dates></dates>
<confirmation></confirmation>
<concerned v-if="hasHouseholdOrLeave"></concerned>
<dates v-if="showConfirm"></dates>
<confirmation v-if="showConfirm"></confirmation>
</template>
<script>
import { mapState } from 'vuex';
import { mapGetters } from 'vuex';
import Concerned from './components/Concerned.vue';
import Household from './components/Household.vue';
import Dates from './components/Dates.vue';
@@ -22,11 +22,14 @@ export default {
Confirmation,
},
computed: {
// for debugging purpose
// (not working)
//...mapState({
// 'concerned', 'household', 'positions'
// })
...mapGetters([
'hasHouseholdOrLeave',
'hasPersonsWellPositionnated',
]),
showConfirm () {
return this.$store.getters.hasHouseholdOrLeave
&& this.$store.getters.hasPersonsWellPositionnated;
},
}
}

View File

@@ -21,13 +21,10 @@
<div v-for="conc in concUnpositionned"
class="item-bloc"
v-bind:key="conc.person.id"
draggable="true"
@dragstart="onStartDragConcern($event, conc.person.id)"
>
<div class="item-row person">
<div class="item-col box-person">
<div>
<img src="~ChillMainAssets/img/draggable.svg" class="drag-icon" />
<person :person="conc.person"></person>
</div>
<div v-if="conc.person.birthdate !== null">
@@ -89,21 +86,18 @@
v-for="position in positions"
>
<h3>{{ position.label.fr }}</h3>
<div class="flex-table list-household-members">
<div v-if="concByPosition(position.id).length > 0" class="flex-table list-household-members">
<member-details
v-for="conc in concByPosition(position.id)"
v-bind:key="conc.person.id"
v-bind:conc="conc"
>
</member-details>
<div
class="droppable_zone"
@drop="onDropConcern($event, position.id)"
@dragover.prevent
@dragenter.prevent
>
{{ $t('household_members_editor.drop_persons_here', {'position': position.label.fr }) }}
</div>
</div>
<div v-else>
<p class="chill-no-data-statement">{{ $t('household_members_editor.concerned.no_person_in_position') }}</p>
</div>
</div>
</div>
@@ -120,22 +114,6 @@ div.person {
}
}
.drag-icon {
height: 1.1em;
margin-right: 0.5em;
}
.droppable_zone {
background-color: var(--chill-llight-gray);
color: white;
font-size: large;
text-align: center;
display: table-cell;
vertical-align: middle;
padding: 1em;
background: linear-gradient(to top, var(--chill-light-gray), 30%, var(--chill-llight-gray));
}
.move_to {
.move_hint {
text-align: center;
@@ -194,18 +172,8 @@ export default {
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
},
onStartDragConcern(evt, person_id) {
evt.dataTransfer.dropEffect = 'move'
evt.dataTransfer.effectAllowed = 'move'
evt.dataTransfer.setData('application/x.person', person_id)
},
onDropConcern(evt, position_id) {
const person_id = Number(evt.dataTransfer.getData('application/x.person'));
this.moveToPosition(person_id, position_id);
},
moveToPosition(person_id, position_id) {
this.$store.dispatch('markPosition', { person_id, position_id });
},
removeConcerned(conc) {
this.$store.dispatch('removeConcerned', conc);

View File

@@ -2,11 +2,8 @@
<h2>{{ $t('household_members_editor.household_part') }}</h2>
<div v-if="hasHousehold">
<span v-if="isHouseholdNew">
{{ $t('household_members_editor.household.new_household') }}
</span>
<div v-else>
Ménage existant
<div>
<household-viewer :household="household"></household-viewer>
</div>
</div>
<div v-else-if="isForceLeaveWithoutHousehold">
@@ -39,16 +36,20 @@
<script>
import { mapGetters } from 'vuex';
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
export default {
name: 'Household',
components: {
HouseholdViewer,
},
computed: {
...mapGetters([
'hasHousehold',
'isHouseholdNew',
]),
household() {
return this.$store.household;
return this.$store.state.household;
},
allowHouseholdCreate() {
return this.$store.state.allowHouseholdCreate;

View File

@@ -19,12 +19,13 @@ const appMessages = {
move_to: "Déplacer vers",
persons_to_positionnate: 'Usagers à positionner',
persons_leaving: "Usagers quittant leurs ménages",
no_person_in_position: "Aucun usager ne sera ajouté à cette position",
},
drop_persons_here: "Glissez-déposez ici les usagers pour la position \"{position}\"",
all_positionnated: "Tous les usagers sont positionnés",
holder: "Titulaire",
is_holder: "Sera titulaire",
is_not_holder: "Ne sera pas titulaire",
is_holder: "Est titulaire",
is_not_holder: "N'est pas titulaire",
remove_position: "Retirer des {position}",
remove_concerned: "Ne plus transférer",
household_part: "Ménage de destination",

View File

@@ -19,7 +19,15 @@ const store = createStore({
state: {
concerned,
household: window.household_members_editor_data.household,
positions: window.household_members_editor_data.positions,
positions: window.household_members_editor_data.positions.sort((a, b) => {
if (a.ordering < b.ordering) {
return -1;
}
if (a.ordering > b.ordering) {
return 1;
}
return 0;
}),
startDate: new Date(),
allowHouseholdCreate: window.household_members_editor_data.allowHouseholdCreate,
allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch,
@@ -35,6 +43,13 @@ const store = createStore({
hasHousehold(state) {
return state.household !== null;
},
hasHouseholdOrLeave(state) {
return state.household !== null || state.forceLeaveWithoutHousehold;
},
hasPersonsWellPositionnated(state, getters) {
return getters.needsPositionning === false
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
},
persons(state) {
return state.concerned.map(conc => conc.person);
},
@@ -150,7 +165,7 @@ const store = createStore({
)
},
createHousehold(state) {
state.household = { type: 'household', members: [], address: null }
state.household = { type: 'household', members: [], current_address: null }
state.forceLeaveWithoutHousehold = false;
},
forceLeaveWithoutHousehold(state) {

View File

@@ -0,0 +1,30 @@
const create = (accompanying_period_id, payload) => {
const url = `/api/1.0/person/accompanying-course/${accompanying_period_id}/work.json`;
let status;
console.log('create', payload);
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
.then(response => {
if (response.ok || response.status === 422) {
status = response.status;
return response.json();
}
throw new Error("Error while retrieving social actions: " + response.status +
" " + response.statusText);
})
.then(data => {
return new Promise((resolve, reject) => {
resolve({ status, data });
});
});
};
export { create };

View File

@@ -0,0 +1,21 @@
const findSocialActionsBySocialIssue = (id) => {
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error("Error while retrieving social actions " + response.status
+ " " + response.statusText);
}
return response.json();
})
.catch(err => {
throw err
})
;
};
export {
findSocialActionsBySocialIssue
};

View File

@@ -0,0 +1,139 @@
<template>
<div class="chill-entity chill-entity__household">
<!-- identifier -->
<div v-if="isHouseholdNew()" class="identifier">
<i class="fa fa-home"></i>
{{ $t('new_household') }}
</div>
<div v-else class="identifier">
<i class="fa fa-home"></i>
{{ $t('household_number', { number: household.id } ) }}
</div>
<!-- member part -->
<div v-if="hasCurrentMembers" class="members">
<span class="current-members">{{ $t('current_members') }}: </span>
<template v-for="(m, index) in currentMembers()" :key="m.id">
<person :person="m.person"></person>
<span v-if="m.holder">
&nbsp;<span class="badge badge-primary">{{ $t('holder') }}</span>
</span>
<span v-if="index != (currentMembersLength() - 1)">, </span>
</template>
</div>
<div v-else class="members">
<p class="chill-no-data-statement">{{ $t('no_members_yet') }}</p>
</div>
<!-- address part -->
<div v-if="hasAddress()" class="where">
<i class="fa fa-where"></i>
<show-address :address="household.current_address"></show-address>
</div>
<div v-else class="where">
<i class="fa fa-where"></i>
<p class="chill-no-data-statement">{{ $t('no_current_address') }}</p>
</div>
</div>
</template>
<style lang="scss">
.chill-entity__household {
display: grid;
grid-template-areas:
"identifier identifier where"
"who who where"
;
grid-template-columns:
auto auto 30%
;
.identifier {
grid-area: identifier;
font-size: 1.3em;
font-weight: 700;
color: var(--chill-blue);
}
.members {
grid-area: who;
.current-members {
font-weight: 700;
}
}
.where {
grid-area: where
}
}
</style>
<script>
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
import ShowAddress from 'ChillMainAssets/vuejs/_components/ShowAddress.vue';
const i18n = {
"messages":
{
"fr":
{
"household_number": "Ménage #{number}",
"current_members": "Membres actuels",
"no_current_address": "Sans adresse actuellement",
"new_household": "Nouveau ménage",
"no_members_yet": "Aucun membre actuellement",
"holder": "titulaire",
}
}
}
;
export default {
name: 'Household',
props: ['household'],
components: {
Person,
ShowAddress,
},
i18n,
methods: {
hasCurrentMembers() {
return this.household.current_members_id.length > 0;
},
currentMembers() {
return this.household.members.filter(m => this.household.current_members_id.includes(m.id))
.sort((a, b) => {
if (a.position.ordering < b.position.ordering) {
return -1;
}
if (a.position.ordering > b.position.ordering) {
return 1;
}
if (a.holder && !b.holder) {
return -1;
}
if (!a.holder && b.holder) {
return 1;
}
return 0;
});
},
currentMembersLength() {
return this.household.current_members_id.length;
},
isHouseholdNew() {
return !Number.isInteger(this.household.id);
},
hasAddress() {
return this.household.current_address !== null;
}
}
};
</script>