diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c85f102b..a308e03cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,45 @@ and this project adheres to +* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167) +* remove "search by phone configuration option": search by phone is now executed by default +* remplacer le classement par ordre alphabétique par un classement par ordre de pertinence, qui tient compte: + * de la présence d'une string avec le nom de la ville; + * de la similarité; + * du fait que la recherche commence par une partie du mot recherché +* ajouter la recherche par numéro de téléphone directement dans la barre de recherche et dans le formulaire recherche avancée; +* ajouter la recherche par date de naissance directement dans la barre de recherche; +* ajouter la recherche par ville dans la recherche avancée +* ajouter un lien vers le ménage dans les résultats de recherche +* ajouter l'id du parcours dans les résultats de recherche +* ajouter le demandeur dans les résultats de recherche +* ajout d'un bouton "recherche avancée" sur la page d'accueil +* [person] create an accompanying course: add client-side validation if no origin (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/210) +* [person] fix bounds for computing current person address: the new address appears immediatly +* [docgen] create a normalizer and serializer for normalization on doc format ## Test releases +### Test release 2021-11-15 + +* [main] fix adding multiple AddresseDeRelais (combine PickAddressType with ChillCollection) +* [person]: do not suggest the current household of the person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/51) +* [person]: display other phone numbers in view + add message in case no others phone numbers (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/184) +* unnecessary whitespace removed from person banner after person-id + double parentheses removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/290) +* [person]: delete accompanying period work, including related objects (cascade) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/36) +* [address]: Display of incomplete address adjusted. +* [household]: improve relationship graph + * add form to create/edit/delete relationship link, + * improve graph refresh mechanism + * add feature to export canvas as image (png) +* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern; +* [person] do not ask for center any more on person creation +* [3party] do not ask for center any more on 3party creation + ### Test release 2021-11-08 +* [person]: Display the name of a user when searching after a User (TMS) * [person]: Add civility to the person * [person]: Various improvements on the edit person form * [person]: Set available_languages and available_countries as parameters for use in the edit person form @@ -42,10 +75,9 @@ and this project adheres to * [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning * [admin]: links to activity admin section added again. * [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top. -* [socialWorkAction]: display of social issue and parent issues + banner context added. +* [socialWorkAction]: display of social issue and parent issues + banner context added. * [DBAL dependencies] Upgrade to DBAL 3.1 - ### Test release 2021-10-27 * [person]: delete double actions buttons on search person page @@ -63,7 +95,10 @@ and this project adheres to * [3party]: fix address creation * [household members editor] finalisation of editor * [AccompanyingCourse banner]: replace translation referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/70) -* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location. +* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location. +* [household]: add relationship page with dynamic data visualisation graph + +## Test releases ### Test release 2021-10-11 @@ -130,7 +165,7 @@ and this project adheres to ## Test released - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js index 52f3f6c36..c372ac7a7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js @@ -86,7 +86,8 @@ const postParticipation = (id, payload, method) => { }) .then(response => { if (response.ok) { return response.json(); } - throw { msg: 'Error while sending AccompanyingPeriod Course participation.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; + // TODO: adjust message according to status code? Or how to access the message from the violation array? + throw { msg: 'Error while sending AccompanyingPeriod Course participation', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; }); }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue index 84e7fb2c6..7cce4d900 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue @@ -22,20 +22,22 @@ {{ $t('course.open_at') }}{{ $d(accompanyingCourse.openingDate.datetime, 'text') }} - {{ $t('course.referrer') }}: {{ accompanyingCourse.user.username }} + {{ $t('course.referrer') }}: {{ accompanyingCourse.user.username }} -
- - -
+ + +
+ + + @@ -43,12 +45,14 @@ + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue index f23a7c9e5..4142b4f50 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue @@ -10,7 +10,7 @@
{{ $t('confirm.alert_validation') }}
+ +
+ {{ $t('origin.not_valid') }} +
+ + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/api.js new file mode 100644 index 000000000..448ff6633 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/api.js @@ -0,0 +1,195 @@ +import { splitId } from './vis-network' + +/** + * @function makeFetch + * @param method + * @param url + * @param body + * @returns {Promise} + */ +const makeFetch = (method, url, body) => { + return fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: (body !== null) ? JSON.stringify(body) : null + }) + .then(response => { + + if (response.ok) { + return response.json(); + } + + if (response.status === 422) { + return response.json().then(violations => { + throw ValidationException(violations) + }); + } + + throw { + msg: 'Error while updating AccompanyingPeriod Course.', + sta: response.status, + txt: response.statusText, + err: new Error(), + body: response.body + }; + }); +} + +/** + * @param violations + * @constructor + */ +const ValidationException = (violations) => { + this.violations = violations + this.name = 'ValidationException' +} + +/** + * @function getFetch + * @param url + * @returns {Promise} + */ +const getFetch = (url) => { + return makeFetch('GET', url, null) +} + +/** + * @function postFetch + * @param url + * @param body + * @returns {Promise} + */ +const postFetch = (url, body) => { + return makeFetch('POST', url, body) +} + +/** + * @function patchFetch + * @param url + * @param body + * @returns {Promise} + */ +const patchFetch = (url, body) => { + return makeFetch('PATCH', url, body) +} + +/** + * @function deleteFetch + * @param url + * @param body + * @returns {Promise} + */ +const deleteFetch = (url, body) => { + return makeFetch('DELETE', url, null) +} + + +/** + * @function getHouseholdByPerson + * @param person + * @returns {Promise} + */ +const getHouseholdByPerson = (person) => { + //console.log('getHouseholdByPerson', person.id) + if (person.current_household_id === null) { + throw 'Currently the person has not household!' + } + return getFetch( + `/api/1.0/person/household/${person.current_household_id}.json`) +} + +/** + * @function getCoursesByPerson + * @param person + * @returns {Promise} + */ +const getCoursesByPerson = (person) => { + //console.log('getCoursesByPerson', person._id) + return getFetch( + `/api/1.0/person/accompanying-course/by-person/${person._id}.json`) +} + +/** + * @function getRelationshipsByPerson + * @param person + * @returns {Promise} + */ +const getRelationshipsByPerson = (person) => { + //console.log('getRelationshipsByPerson', person.id) + return getFetch( + `/api/1.0/relations/relationship/by-person/${person._id}.json`) +} + +/** + * Return list of relations + * @returns {Promise} + */ +const getRelationsList = () => { + return getFetch(`/api/1.0/relations/relation.json`) +} + +/** + * @function postRelationship + * @param relationship + * @returns {Promise} + */ +const postRelationship = (relationship) => { + //console.log(relationship) + return postFetch( + `/api/1.0/relations/relationship.json`, + { + type: 'relationship', + fromPerson: { type: 'person', id: splitId(relationship.from, 'id') }, + toPerson: { type: 'person', id: splitId(relationship.to, 'id') }, + relation: { type: 'relation', id: relationship.relation.id }, + reverse: relationship.reverse + } + ) +} + +/** + * @function patchRelationship + * @param relationship + * @returns {Promise} + */ +const patchRelationship = (relationship) => { + //console.log(relationship) + let linkType = splitId(relationship.id, 'link') + let id = splitId(linkType, 'id') + return patchFetch( + `/api/1.0/relations/relationship/${id}.json`, + { + type: 'relationship', + fromPerson: { type: 'person', id: splitId(relationship.from, 'id') }, + toPerson: { type: 'person', id: splitId(relationship.to, 'id') }, + relation: { type: 'relation', id: relationship.relation.id }, + reverse: relationship.reverse + } + ) +} + +/** + * @function deleteRelationship + * @param relationship + * @returns {Promise} + */ +const deleteRelationship = (relationship) => { + //console.log(relationship) + let linkType = splitId(relationship.id, 'link') + let id = splitId(linkType, 'id') + return deleteFetch( + `/api/1.0/relations/relationship/${id}.json` + ) +} + +export { + getHouseholdByPerson, + getCoursesByPerson, + getRelationshipsByPerson, + getRelationsList, + postRelationship, + patchRelationship, + deleteRelationship +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js new file mode 100644 index 000000000..c2ee09960 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js @@ -0,0 +1,62 @@ +const visMessages = { + fr: { + visgraph: { + Course: 'Parcours', + Household: 'Ménage', + Holder: 'Titulaire', + Legend: 'Calques', + concerned: 'concerné', + both: 'neutre, non binaire', + woman: 'féminin', + man: 'masculin', + years: 'ans', + click_to_expand: 'cliquez pour étendre', + add_relationship_link: "Créer un lien de filiation", + edit_relationship_link: "Modifier le lien de filiation", + delete_relationship_link: "Êtes-vous sûr ?", + delete_confirmation_text: "Vous allez supprimer le lien entre ces 2 usagers.", + reverse_relation: "Inverser la relation", + relation_from_to_like: "{2} de {1}", // disable {0} + between: "entre", + and: "et", + add_link: "Créer un lien de filiation", + create_link_help: "Pour créer un lien de filiation, cliquez d'abord sur un usager, puis sur un second ; précisez ensuite la nature du lien dans le formulaire d'édition.", + refresh: "Rafraîchir", + screenshot: "Prendre une photo", + choose_relation: "Choisissez le lien de parenté", + }, + edit: 'Éditer', + del: 'Supprimer', + back: 'Revenir en arrière', + addNode: 'Ajouter un noeuds', + addEdge: 'Ajouter un lien de filiation', + editNode: 'Éditer le noeuds', + editEdge: 'Éditer le lien', + addDescription: 'Cliquez dans un espace vide pour créer un nouveau nœud.', + edgeDescription: 'Cliquez sur un usager et faites glisser le lien vers un autre usager pour les connecter.', + editEdgeDescription: 'Cliquez sur les points de contrôle et faites-les glisser vers un nœud pour les relier.', + createEdgeError: 'Il est impossible de relier des arêtes à un cluster.', + deleteClusterError: 'Les clusters ne peuvent pas être supprimés.', + editClusterError: 'Les clusters ne peuvent pas être modifiés.' + }, + en: { + edit: 'Edit', + del: 'Delete selected', + back: 'Back', + addNode: 'Add Node', + addEdge: 'Add Link', + editNode: 'Edit Switch', + editEdge: 'Edit Link', + addDescription: 'Click in an empty space to place a new node.', + edgeDescription: 'Click on a node and drag the link to another node to connect them.', + editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.', + createEdgeError: 'Cannot link edges to a cluster.', + deleteClusterError: 'Clusters cannot be deleted.', + editClusterError: 'Clusters cannot be edited.' + + } +} + +export { + visMessages +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js new file mode 100644 index 000000000..ca76f283b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js @@ -0,0 +1,24 @@ +import { createApp } from "vue" +import { store } from "./store.js" +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' +import { visMessages } from './i18n' +import App from './App.vue' + +import './vis-network' + +const i18n = _createI18n(visMessages) +const container = document.getElementById('relationship-graph') +const persons = JSON.parse(container.dataset.persons) + +persons.forEach(person => { + store.dispatch('addPerson', person) + store.commit('markInWhitelist', person) +}) + +const app = createApp({ + template: `` +}) +.use(store) +.use(i18n) +.component('app', App) +.mount('#relationship-graph') diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js new file mode 100644 index 000000000..119a7b29c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js @@ -0,0 +1,534 @@ +import { createStore } from 'vuex' +import { getHouseholdByPerson, getCoursesByPerson, getRelationshipsByPerson } from './api' +import { getHouseholdLabel, getHouseholdWidth, getRelationshipLabel, getRelationshipTitle, getRelationshipDirection, splitId, getGender, getAge } from './vis-network' +import {visMessages} from "./i18n"; + +const debug = process.env.NODE_ENV !== 'production' + +const store = createStore({ + strict: debug, + state: { + persons: [], + households: [], + courses: [], + relationships: [], + links: [], + whitelistIds: [], + personLoadedIds: [], + householdLoadingIds: [], + courseLoadedIds: [], + relationshipLoadedIds: [], + excludedNodesIds: [], + updateHack: 0 + }, + getters: { + nodes(state) { + let nodes = [] + state.persons.forEach(p => { + nodes.push(p) + }) + state.households.forEach(h => { + nodes.push(h) + }) + state.courses.forEach(c => { + nodes.push(c) + }) + // except excluded nodes (unchecked layers) + state.excludedNodesIds.forEach(excluded => { + nodes = nodes.filter(n => n.id !== excluded) + }) + return nodes + }, + edges(state) { + return state.links + }, + isInWhitelist: (state) => (person_id) => { + return state.whitelistIds.includes(person_id) + }, + isHouseholdLoading: (state) => (household_id) => { + return state.householdLoadingIds.includes(household_id) + }, + isCourseLoaded: (state) => (course_id) => { + return state.courseLoadedIds.includes(course_id) + }, + isRelationshipLoaded: (state) => (relationship_id) => { + return state.relationshipLoadedIds.includes(relationship_id) + }, + isPersonLoaded: (state) => (person_id) => { + return state.personLoadedIds.includes(person_id) + }, + isExcludedNode: (state) => (id) => { + return state.excludedNodesIds.includes(id) + }, + + countLinksByNode: (state) => (node_id) => { + let array = [] + state.links.filter(link => ! link.id.startsWith('relationship')) + .forEach(link => { + if (link.from === node_id || link.to === node_id) { + if (state.excludedNodesIds.indexOf(splitId(link.id, 'link')) === -1) { + array.push(link) + } + //console.log(link.id, state.excludedNodesIds.indexOf(splitId(link.id, 'link'))) + } + }) + //console.log('count links', array.length, array.map(i => i.id)) + return array.length + }, + + getParticipationsByCourse: (state) => (course_id) => { + const course = state.courses.filter(c => c.id === course_id)[0] + const currentParticipations = course.participations.filter(p => p.endDate === null) + //console.log('get persons in', course_id, currentParticipations.map(p => p.person.id), + // 'with folded', currentParticipations.filter(p => p.person.folded === true).map(p => p.person.id)) + return currentParticipations + }, + + getMembersByHousehold: (state) => (household_id) => { + const household = state.households.filter(h => h.id === household_id)[0] + const currentMembers = household.members.filter(m => household.current_members_id.includes(m.id)) + //console.log('get persons in', household_id, currentMembers.map(m => m.person.id), + // 'with folded', currentMembers.filter(m => m.person.folded === true).map(m => m.person.id)) + return currentMembers + }, + + /** + * This getter is a little bit mysterious : + * The 2 previous getters return complete array, but folded (missing) persons are not taken into consideration and are not displayed (!?!) + * This getter compare input array (participations|members) to personLoadedIds array + * and return complete array with folded persons taken into consideration + * + * @param state + * @param array - An array of persons from course or household. + * This array is dirty, melting persons adapted (or not) to vis, with _id and _label. + * @return array - An array of persons mapped and taken in state.persons + */ + getPersonsGroup: (state) => (array) => { + let group = [] + array.forEach(item => { + let id = splitId(item.person.id, 'id') + if (state.personLoadedIds.includes(id)) { + group.push(state.persons.filter(person => person._id === id)[0]) + } + }) + //console.log('array', array.map(item => item.person.id)) + console.log('get persons group', group.map(f => f.id)) + return group + }, + + + }, + mutations: { + addPerson(state, [person, options]) { + let debug = '' + /// Debug mode: uncomment to display person_id on visgraph + //debug = `\nid ${person.id}` + person.group = person.type + person._id = person.id + person.id = `person_${person.id}` + person.label = `*${person.text}*\n_${getGender(person.gender)} - ${getAge(person.birthdate)}_${debug}` // + person.folded = false + // folded is used for missing persons + if (options.folded) { + person.title = visMessages.fr.visgraph.click_to_expand + person._label = person.label // keep label + person.label = null + person.folded = true + } + state.persons.push(person) + }, + addHousehold(state, household) { + household.group = household.type + household._id = household.id + household.label = `${visMessages.fr.visgraph.Household} n° ${household.id}` + household.id = `household_${household.id}` + state.households.push(household) + }, + addCourse(state, course) { + course.group = course.type + course._id = course.id + course.label = `${visMessages.fr.visgraph.Course} n° ${course.id}` + course.id = `accompanying_period_${course.id}` + state.courses.push(course) + }, + addRelationship(state, relationship) { + relationship.group = relationship.type + relationship._id = relationship.id + relationship.id = `relationship_${relationship.id}` + state.relationships.push(relationship) + }, + addLink(state, link) { + state.links.push(link) + }, + updateLink(state, link) { + console.log('updateLink', link) + let link_ = { + from: `person_${link.fromPerson.id}`, + to: `person_${link.toPerson.id}`, + id: 'relationship_' + splitId(link.id,'id') + + '-person_' + link.fromPerson.id + '-person_' + link.toPerson.id, + arrows: getRelationshipDirection(link), + color: 'lightblue', + font: { color: '#33839d' }, + dashes: true, + label: getRelationshipLabel(link), + title: getRelationshipTitle(link), + relation: link.relation, + reverse: link.reverse + } + // find row position and replace by updatedLink + state.links.splice( + state.links.findIndex(item => item.id === link_.id), 1, link_ + ) + }, + removeLink(state, link_id) { + state.links = state.links.filter(l => l.id !== link_id) + }, + + //// id markers + markInWhitelist(state, person) { + state.whitelistIds.push(person.id) + }, + markPersonLoaded(state, id) { + state.personLoadedIds.push(id) + }, + unmarkPersonLoaded(state, id) { + state.personLoadedIds = state.personLoadedIds.filter(i => i !== id) + }, + markHouseholdLoading(state, id) { + //console.log('..loading household', id) + state.householdLoadingIds.push(id) + }, + unmarkHouseholdLoading(state, id) { + state.householdLoadingIds = state.householdLoadingIds.filter(i => i !== id) + }, + markCourseLoaded(state, id) { + state.courseLoadedIds.push(id) + }, + unmarkCourseLoaded(state, id) { + state.courseLoadedIds = state.courseLoadedIds.filter(i => i !== id) + }, + markRelationshipLoaded(state, id) { + state.relationshipLoadedIds.push(id) + }, + unmarkRelationshipLoaded(state, id) { + state.relationshipLoadedIds = state.relationshipLoadedIds.filter(i => i !== id) + }, + + //// excluded + addExcludedNode(state, id) { + //console.log('==> exclude list: +', id) + state.excludedNodesIds.push(id) + }, + removeExcludedNode(state, id) { + //console.log('<== exclude list: -', id) + state.excludedNodesIds = state.excludedNodesIds.filter(e => e !== id) + }, + + //// unfold + unfoldPerson(state, person) { + //console.log('unfoldPerson', person) + person.label = person._label + delete person._label + delete person.title + person.folded = false + }, + + //// force update hack + updateHack(state) { + state.updateHack = state.updateHack + 1 + } + }, + actions: { + /** + * Expand loop (steps 1->10), always start from a person. + * Fetch household, courses, relationships, and others persons. + * These persons are "missing" and will be first display in fold mode. + * + * 1) Add a new person + * @param object + * @param person + */ + addPerson({ commit, dispatch }, person) { + commit('markPersonLoaded', person.id) + commit('addPerson', [person, { folded: false }]) + commit('updateHack') + dispatch('fetchInfoForPerson', person) + }, + + /** + * 2) Fetch infos for this person (hub) + * @param object + * @param person + */ + fetchInfoForPerson({ dispatch }, person) { + // TODO enfants hors ménages + // example: household 61 + // console.log(person.text, 'household', person.current_household_id) + if (null !== person.current_household_id) { + dispatch('fetchHouseholdForPerson', person) + } + dispatch('fetchCoursesByPerson', person) + dispatch('fetchRelationshipByPerson', person) + }, + + /** + * 3) Fetch person current household (if it is not already loading) + * check first isHouseholdLoading to fetch household once + * @param object + * @param person + */ + fetchHouseholdForPerson({ commit, getters, dispatch }, person) { + //console.log(' isHouseholdLoading ?', getters.isHouseholdLoading(person.current_household_id)) + if (! getters.isHouseholdLoading(person.current_household_id)) { + commit('markHouseholdLoading', person.current_household_id) + getHouseholdByPerson(person) + .then(household => new Promise(resolve => { + commit('addHousehold', household) + // DISABLED: in init or expand loop, layer is uncheck when added + //commit('addExcludedNode', household.id) + //commit('updateHack') + dispatch('addLinkFromPersonsToHousehold', household) + commit('updateHack') + resolve() + }) + ).catch( () => { + commit('unmarkHouseholdLoading', person.current_household_id) + }) + } + }, + + /** + * 4) Add an edge for each household member (household -> person) + * @param object + * @param household + */ + addLinkFromPersonsToHousehold({ commit, getters, dispatch }, household) { + let members = getters.getMembersByHousehold(household.id) + console.log('add link for', members.length, 'members') + members.forEach(m => { + commit('addLink', { + from: `${m.person.type}_${m.person.id}`, + to: `household_${m.person.current_household_id}`, + id: `household_${m.person.current_household_id}-person_${m.person.id}`, + arrows: 'from', + color: 'pink', + font: { color: '#D04A60' }, + label: getHouseholdLabel(m), + width: getHouseholdWidth(m), + }) + if (!getters.isPersonLoaded(m.person.id)) { + dispatch('addMissingPerson', [m.person, household]) + } + }) + }, + + /** + * 5) Fetch AccompanyingCourses for the person + * @param object + * @param person + */ + fetchCoursesByPerson({ commit, dispatch }, person) { + getCoursesByPerson(person) + .then(courses => new Promise(resolve => { + dispatch('addCourses', courses) + resolve() + })) + }, + + /** + * 6) Add each distinct course (a person can have multiple courses) + * @param object + * @param courses + */ + addCourses({ commit, getters, dispatch }, courses) { + let currentCourses = courses.filter(c => c.closingDate === null) + currentCourses.forEach(course => { + //console.log(' isCourseLoaded ?', getters.isCourseLoaded(course.id)) + if (! getters.isCourseLoaded(course.id)) { + commit('markCourseLoaded', course.id) + commit('addCourse', course) + commit('addExcludedNode', course.id) // in init or expand loop, layer is uncheck when added + dispatch('addLinkFromPersonsToCourse', course) + commit('updateHack') + } + }) + }, + + /** + * 7) Add an edge for each course participation (course <- person) + * @param object + * @param course + */ + addLinkFromPersonsToCourse({ commit, getters, dispatch }, course) { + const participations = getters.getParticipationsByCourse(course.id) + console.log('add link for', participations.length, 'participations') + participations.forEach(p => { + //console.log(p.person.id) + commit('addLink', { + from: `${p.person.type}_${p.person.id}`, + to: `${course.id}`, + id: `accompanying_period_${splitId(course.id,'id')}-person_${p.person.id}`, + arrows: 'from', + color: 'orange', + font: { color: 'darkorange' }, + }) + if (!getters.isPersonLoaded(p.person.id)) { + dispatch('addMissingPerson', [p.person, course]) + } + }) + }, + + /** + * 8) Fetch Relationship + * @param object + * @param person + */ + fetchRelationshipByPerson({ dispatch }, person) { + //console.log('fetchRelationshipByPerson', person) + getRelationshipsByPerson(person) + .then(relationships => new Promise(resolve => { + dispatch('addRelationships', relationships) + resolve() + })) + }, + + /** + * 9) Add each distinct relationship + * @param object + * @param relationships + */ + addRelationships({ commit, getters, dispatch }, relationships) { + relationships.forEach(relationship => { + //console.log(' isRelationshipLoaded ?', getters.isRelationshipLoaded(relationship.id)) + if (! getters.isRelationshipLoaded(relationship.id)) { + commit('markRelationshipLoaded', relationship.id) + commit('addRelationship', relationship) + dispatch('addLinkFromRelationship', relationship) + commit('updateHack') + } + }) + }, + + /** + * 10) Add an edge for each relationship (person -> person) + * @param object + * @param relationship + */ + addLinkFromRelationship({ commit, getters, dispatch }, relationship) { + //console.log('-> addLink from person', relationship.fromPerson.id, 'to person', relationship.toPerson.id) + commit('addLink', { + from: `person_${relationship.fromPerson.id}`, + to: `person_${relationship.toPerson.id}`, + id: 'relationship_' + splitId(relationship.id,'id') + + '-person_' + relationship.fromPerson.id + '-person_' + relationship.toPerson.id, + arrows: getRelationshipDirection(relationship), + color: 'lightblue', + font: { color: '#33839d' }, + dashes: true, + label: getRelationshipLabel(relationship), + title: getRelationshipTitle(relationship), + relation: relationship.relation, + reverse: relationship.reverse + }) + for (let person of [relationship.fromPerson, relationship.toPerson]) { + if (!getters.isPersonLoaded(person.id)) { + dispatch('addMissingPerson', [person, relationship]) + } + } + }, + + /** + * Add missing person. node is displayed without label (folded). + * We stop here and listen on events to unfold person and expand its fetch infos + * @param object + * @param array + */ + addMissingPerson({ commit, getters, dispatch }, [person, parent]) { + console.log('! add missing Person', person.id) + commit('markPersonLoaded', person.id) + commit('addPerson', [person, { folded: true }]) + if (getters.isExcludedNode(parent.id)) { + // in init or expand loop, exclude too missing persons if parent have been excluded + commit('addExcludedNode', person.id) + } + commit('updateHack') + }, + + /** + * ================================================================== + * Triggered by a vis-network event when clicking on a Course Node. + * Each folded node is unfold, then expanded with fetch infos + * @param object + * @param course + */ + unfoldPersonsByCourse({ getters, commit, dispatch }, course) { + const participations = getters.getParticipationsByCourse(course.id) + getters.getPersonsGroup(participations) + .forEach(person => { + if (person.folded === true) { + console.log('-=. unfold and expand person', person.id) + commit('unfoldPerson', person) + dispatch('fetchInfoForPerson', person) + } + }) + }, + + /** + * Triggered by a vis-network event when clicking on a Household Node. + * Each folded node is unfold, then expanded with fetch infos + * @param object + * @param household + */ + unfoldPersonsByHousehold({ getters, commit, dispatch }, household) { + const members = getters.getMembersByHousehold(household.id) + getters.getPersonsGroup(members) + .forEach(person => { + if (person.folded === true) { + console.log('-=. unfold and expand person', person.id) + commit('unfoldPerson', person) + dispatch('fetchInfoForPerson', person) + } + }) + }, + + /** + * ================================================================== + * For an excluded node, add|remove relative persons excluded too + * @param object + * @param array (add|remove action, id) + */ + excludedNode({ getters, commit }, [action, id]) { + const personGroup = () => { + switch (splitId(id, 'type')) { + case 'accompanying_period': + return getters.getParticipationsByCourse(id) + case 'household': + return getters.getMembersByHousehold(id) + default: + throw 'undefined case with this id' + } + } + let group = getters.getPersonsGroup(personGroup()) + if (action === 'add') { + commit('addExcludedNode', id) + group.forEach(person => { + // countLinks < 2 but parent has just already been added ! + if (!getters.isInWhitelist(person.id) && getters.countLinksByNode(person.id) < 1) { + commit('addExcludedNode', person.id) + } + }) + } + if (action === 'remove') { + commit('removeExcludedNode', id) + group.forEach(person => { + commit('removeExcludedNode', person.id) + }) + } + commit('updateHack') + }, + + } +}) + +export { store } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js new file mode 100644 index 000000000..e95bc0d0b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js @@ -0,0 +1,262 @@ +import { visMessages } from './i18n' + +/** + * Vis-network initial data/configuration script + * Notes: + * Use window.network and window.options to avoid conflict between vue and vis + * cfr. https://github.com/almende/vis/issues/2524#issuecomment-307108271 + */ + +window.network = {} + +window.options = { + locale: 'fr', + locales: visMessages, + /* + configure: { + enabled: true, + filter: 'nodes,edges', + //container: undefined, + showButton: true + }, + */ + physics: { + enabled: true, + barnesHut: { + theta: 0.5, + gravitationalConstant: -2000, + centralGravity: 0.08, //// 0.3 + springLength: 220, //// 95 + springConstant: 0.04, + damping: 0.09, + avoidOverlap: 0 + }, + forceAtlas2Based: { + theta: 0.5, + gravitationalConstant: -50, + centralGravity: 0.01, + springLength: 100, + springConstant: 0.08, + damping: 0.4, + avoidOverlap: 0 + }, + repulsion: { + centralGravity: 0.2, + springLength: 200, + springConstant: 0.05, + nodeDistance: 100, + damping: 0.09 + }, + hierarchicalRepulsion: { + centralGravity: 0.0, + springLength: 100, + springConstant: 0.01, + nodeDistance: 120, + damping: 0.09, + avoidOverlap: 0 + }, + maxVelocity: 50, + minVelocity: 0.1, + solver: 'forceAtlas2Based', //'barnesHut', // + stabilization: { + enabled: true, + iterations: 1000, + updateInterval: 100, + onlyDynamicEdges: false, + fit: true + }, + timestep: 0.5, + adaptiveTimestep: true, + wind: { x: 0, y: 0 } + }, + interaction: { + hover: true, + multiselect: true, + navigationButtons: false, + }, + manipulation: { + enabled: false, + initiallyActive: false, + addNode: false, + deleteNode: false + }, + nodes: { + borderWidth: 1, + borderWidthSelected: 3, + font: { + multi: 'md' + } + }, + edges: { + font: { + color: '#b0b0b0', + size: 9, + face: 'arial', + background: 'none', + strokeWidth: 2, // px + strokeColor: '#ffffff', + align: 'middle', + multi: false, + vadjust: 0, + }, + scaling:{ + label: true, + }, + smooth: true, + }, + groups: { + person: { + shape: 'box', + shapeProperties: { + borderDashes: false, + borderRadius: 3, + }, + color: { + border: '#b0b0b0', + background: 'rgb(193,229,222)', + highlight: { + border: '#89c9a9', + background: 'rgb(156,213,203)' + }, + hover: { + border: '#89c9a9', + background: 'rgb(156,213,203)' + } + }, + opacity: 0.85, + shadow:{ + enabled: true, + color: 'rgba(0,0,0,0.5)', + size:10, + x:5, + y:5 + }, + }, + household: { + color: 'pink' + }, + accompanying_period: { + color: 'orange', + }, + } +} + +/** + * @param gender + * @returns {string} + */ +const getGender = (gender) => { + switch (gender) { + case 'both': + return visMessages.fr.visgraph.both + case 'woman': + return visMessages.fr.visgraph.woman + case 'man': + return visMessages.fr.visgraph.man + default: + throw 'gender undefined' + } +} + +/** + * TODO Repeat getAge() in PersonRenderBox.vue + * @param birthdate + * @returns {string|null} + */ +const getAge = (birthdate) => { + if (null === birthdate) { + return null + } + const birthday = new Date(birthdate.datetime) + const now = new Date() + return (now.getFullYear() - birthday.getFullYear()) + ' '+ visMessages.fr.visgraph.years +} + +/** + * Return member position in household + * @param member + * @returns string + */ +const getHouseholdLabel = (member) => { + let position = member.position.label.fr + let holder = member.holder ? ` ${visMessages.fr.visgraph.Holder}` : '' + return position + holder +} + +/** + * Return edge width for member (depends of position in household) + * @param member + * @returns integer (width) + */ +const getHouseholdWidth = (member) => { + if (member.holder) { + return 5 + } + if (member.shareHousehold) { + return 2 + } + return 1 +} + +/** + * Return direction edge + * @param relationship + * @returns string + */ +const getRelationshipDirection = (relationship) => { + return (!relationship.reverse) ? 'to' : 'from' +} + +/** + * Return label edge + * !! always set label in title direction (arrow is reversed, see in previous method) !! + * @param relationship + * @returns string + */ +const getRelationshipLabel = (relationship) => { + return relationship.relation.title.fr +} + +/** + * Return title edge + * @param relationship + * @returns string + */ +const getRelationshipTitle = (relationship) => { + return (!relationship.reverse) ? + relationship.relation.title.fr + ': ' + relationship.fromPerson.text + '\n' + relationship.relation.reverseTitle.fr + ': ' + relationship.toPerson.text : + relationship.relation.title.fr + ': ' + relationship.toPerson.text + '\n' + relationship.relation.reverseTitle.fr + ': ' + relationship.fromPerson.text +} + +/** + * Split string id and return type|id substring + * @param id + * @param position + * @returns string|integer + */ +const splitId = (id, position) => { + //console.log(id, position) + switch (position) { + case 'type': // return 'accompanying_period' + return /(.+)_/.exec(id)[1] + case 'id': // return 124 + return parseInt(id.toString() + .split("_") + .pop()) + case 'link': + return id.split("-")[0] // return first segment + default: + throw 'position undefined' + } +} + +export { + getGender, + getAge, + getHouseholdLabel, + getHouseholdWidth, + getRelationshipDirection, + getRelationshipLabel, + getRelationshipTitle, + splitId +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue index 931830fd3..e3b4901d7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue @@ -20,18 +20,25 @@ v-bind:item="item"> + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig index 7ef2de466..fda36da85 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig @@ -6,7 +6,7 @@
- Corriger + {{ 'fix it'|trans }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig index f04a8a376..2febc7967 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_warning_address.html.twig @@ -8,7 +8,7 @@ - Corriger + {{ 'fix it'|trans }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig index e55b39f64..ded5590a2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/banner.html.twig @@ -23,11 +23,28 @@
-
+
- {# vue teleport fragment here #} - +
+ +
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig index 612c3a46b..434a87759 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig @@ -23,32 +23,6 @@ {% block content %}
-
- {% for h in participationsByHousehold %} - {% set householdClass = (h.household is not null) ? 'household-' ~ h.household.id : 'no-household alert alert-warning' %} - {% set householdTitle = (h.household is not null) ? - 'household.Household number'|trans({'household_num': h.household.id }) : 'household.Never in any household'|trans %} - - {% if h.household is not null %} - - {% endif %} - {% for p in h.members %} - - {# include vue_onthefly component #} - {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { - targetEntity: { name: 'person', id: p.person.id }, - action: 'show', - displayBadge: true, - buttonText: p.person|chill_entity_render_string - } %} - - {% endfor %} - - {% endfor %} -
- {% if 'DRAFT' == accompanyingCourse.step %}
{% include '@ChillPerson/AccompanyingCourse/_still_draft.html.twig' %} @@ -83,7 +57,7 @@
@@ -101,8 +75,7 @@ {% set accompanying_course_id = accompanyingCourse.id %} {% endif %} -

{{ 'Last activities' |trans }}

- +

{{ 'Last activities' |trans }}

{% include 'ChillActivityBundle:Activity:list_recent.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %}
{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig new file mode 100644 index 000000000..c66e5aa3d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig @@ -0,0 +1,34 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% set activeRouteKey = 'chill_person_accompanying_period_work_list' %} + +{% block title 'accompanying_course_work.remove'|trans %} + +{% block content %} + +
+

+ {{ 'accompanying_course_work.action'|trans }} + {{ work.socialAction|chill_entity_render_string }} +

+ +
+

{{ "Associated peoples"|trans }}

+
    + {% for p in work.persons %} + {{ p|chill_entity_render_box }} + {% endfor %} +
+
+
+ + + {{ include('@ChillMain/Util/confirmation_template.html.twig', + { + 'title' : 'accompanying_course_work.remove'|trans, + 'confirm_question' : 'Are you sure you want to remove this work of the accompanying period %name% ?'|trans({ '%name%' : accompanyingCourse.id } ), + 'cancel_route' : 'chill_person_accompanying_period_work_list', + 'cancel_parameters' : {'id' : accompanyingCourse.id}, + 'form' : delete_form + } ) }} +{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig index dd8beded5..797f1bcc8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig @@ -103,6 +103,11 @@ href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}" >{% if buttonText is not defined or buttonText == true %}{{ 'Edit'|trans }}{% endif %} +
  • + {% if buttonText is not defined or buttonText == true %}{{ 'Delete'|trans }}{% endif %} +
  • diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig index 61f48aa76..ec782615a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig @@ -1,29 +1,33 @@ - {% if works|length == 0 %} -

    {{ 'accompanying_course_work.Any work'|trans }} - {# TODO link #} -

    - {% endif %} -
    {% for w in works | slice(0,5) %}

    - {{ 'accompanying_course_work.action'|trans }} - {{ w.socialAction|chill_entity_render_string }} + + {{ 'accompanying_course_work.action'|trans }} + + + {{ w.socialAction|chill_entity_render_string }}
    • - {{ 'accompanying_course_work.start_date'|trans ~ ' : ' }} - {{ w.startDate|format_date('short') }} + {{ 'accompanying_course_work.start_date'|trans ~ ' : ' }} + {{ w.startDate|format_date('short') }}
    • + {% if w.endDate %}
    • - {{ 'Last updated by'|trans ~ ' : ' }} - {{ w.updatedBy|chill_entity_render_box }}, {{ w.updatedAt|format_datetime('short', 'short') }} + {{ 'accompanying_course_work.end_date'|trans ~ ' : ' }} + {{ w.endDate|format_date('short') }}
    • + {% endif %}
    + +

    {% endfor %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig index 1bf08602a..c28504042 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig @@ -62,7 +62,7 @@ {%- endif -%} {%- if options['addId'] -%} - {{ person.id|upper }} + {{ person.id|upper -}} {%- endif -%}
    @@ -95,7 +95,7 @@ {%- if options['addAge'] -%} - ({{ 'years_old'|trans({ 'age': person.age }) }}) + {{- 'years_old'|trans({ 'age': person.age }) -}} {%- endif -%} {%- endif -%} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig index 5d5fc77af..56fcce85c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig @@ -2,26 +2,34 @@ {% block title 'household.Relationship'|trans %} -{% block content %} -

    {{ block('title') }}

    -
    +{# + Give more space to graph: + * use parent twig block (layout_wvm_content) + * hide title (d-none) + * apply negative margin-top +#} +{% block layout_wvm_content %} +
    - {% for m in household.members %} - {% if m.endDate is null %} - {{ dump(m) }} - {% endif %} - {% endfor %} +
    +

    {{ block('title') }}

    +
    +
    +
    +
    +{% endblock %} + +{% block block_post_menu %} +
    {% endblock %} {% block js %} - {{ parent() }} - {{ encore_entry_script_tags('page_vis') }} + {{ encore_entry_script_tags('vue_visgraph') }} {% endblock %} {% block css %} - {{ parent() }} - {{ encore_entry_link_tags('page_vis') }} + {{ encore_entry_link_tags('vue_visgraph') }} {% endblock %} - -{% block block_post_menu %}{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig index 98dff821a..f28115d8d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig @@ -1,7 +1,12 @@ -{% macro button_person(person) %} +{% macro button_person_after(person) %} + {% set household = person.getCurrentHousehold %} + {% if household is not null %} +
  • + +
  • + {% endif %}
  • - +
  • {% endmacro %} @@ -56,7 +61,7 @@ 'addAltNames': true, 'addCenter': true, 'address_multiline': false, - 'customButtons': { 'after': _self.button_person(person) } + 'customButtons': { 'after': _self.button_person_after(person) } }) }} {#- 'acps' is for AcCompanyingPeriodS #} @@ -76,12 +81,20 @@
    -
    - {% if acp.requestorPerson == person %} - + {% if acp.step == 'DRAFT' %} +
    + {{ 'course.draft'|trans }} +
    + {% endif %} + {% if acp.requestorPerson == person %} +
    + {{ 'Requestor'|trans({'gender': person.gender}) }} - - {% endif %} + +
    + {% endif %} + +
    {% if app != null %} {{ 'Since %date%'|trans({'%date%': app.startDate|format_date('medium') }) }} {% endif %} @@ -94,6 +107,11 @@
    {% endif %} +
    + {{ 'File number'|trans }} {{ acp.id }} +
    + +
    @@ -101,17 +119,76 @@ {{ issue|chill_entity_render_box }} {% endfor %} -
      +
      • +
      • +
    + {% if acp.currentParticipations|length > 1 %} +
    +
    +
    + {{ 'Participants'|trans }} +
    +
    +
    + {% set participating = false %} + {% for part in acp.currentParticipations %} + {% if part.person.id != person.id %} + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'person', id: part.person.id }, + action: 'show', + displayBadge: true, + buttonText: part.person|chill_entity_render_string + } %} + {% else %} + {% set participating = true %} + {% endif %} + {% endfor %} + {% if participating %} + {{ 'person.and_himself'|trans({'gender': person.gender}) }} + {% endif %} +
    +
    + {% endif %} + {% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %} +
    +
    +
    + {% if acp.requestorPerson is not null %} + {{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }} + {% else %} + {{ 'Requestor'|trans({'gender': 'other'})}} + {% endif %} +
    +
    +
    + {% if acp.requestorThirdParty is not null %} + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id }, + action: 'show', + displayBadge: true, + buttonText: acp.requestorThirdParty|chill_entity_render_string + } %} + {% else %} + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'person', id: acp.requestorPerson.id }, + action: 'show', + displayBadge: true, + buttonText: acp.requestorPerson|chill_entity_render_string + } %} + {% endif %} +
    +
    + {% endif %} {% endfor %}
    diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig index 0351d0ba1..fb06c4d5e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig @@ -216,13 +216,23 @@ This view should receive those arguments: {%- if chill_person.fields.mobilenumber == 'visible' -%}
    {{ 'Mobilenumber'|trans }} :
    -
    {% if person.mobilenumber is not empty %}
    {{ person.mobilenumber|chill_format_phonenumber }}
    {% else %}{{ 'No data given'|trans }}{% endif %}
    +
    {% if person.mobilenumber is not empty %}{{ person.mobilenumber|chill_format_phonenumber }}{% else %}{{ 'No data given'|trans }}{% endif %}
    {% endif %} - {# TODO - display collection of others phonenumbers - #} + {%- if chill_person.fields.mobilenumber == 'visible' -%} + {% if person.otherPhoneNumbers is not empty %} +
    +
    {{ 'Others phone numbers'|trans }} :
    + {% for el in person.otherPhoneNumbers %} + {% if el.phonenumber is not empty %} +
    {% if el.description is not empty %}{{ el.description }} : {% endif %}{{ el.phonenumber|chill_format_phonenumber }}
    + {% endif %} + {% endfor %} + +
    + {% endif %} + {% endif %} {%- if chill_person.fields.contact_info == 'visible' -%}
    @@ -259,6 +269,21 @@ This view should receive those arguments: {% endif %} +
    + {% if person.createdBy %} +
    + {{ 'Created by'|trans}}: {{ person.createdBy|chill_entity_render_box }},
    + {{ 'on'|trans ~ person.createdAt|format_datetime('long', 'short') }} +
    + {% endif %} + {% if person.updatedBy %} +
    + {{ 'Last updated by'|trans}}: {{ person.updatedBy|chill_entity_render_box }},
    + {{ 'on'|trans ~ person.updatedAt|format_datetime('long', 'short') }} +
    + {% endif %} +
    + {% if is_granted('CHILL_PERSON_UPDATE', person) %}