diff --git a/CHANGELOG.md b/CHANGELOG.md index 5107184b0..abc7a219b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,11 @@ and this project adheres to * [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) * [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) - * [person search] fix bug when using birthdate after and birthdate before * [person search] increase pertinence when lastname begins with search pattern * [activity] create work if a work with same social action is not associated to the activity +* [visgraph] improve and fix bugs on vis-network relationship graph +* [bugfix] posting of birth- and deathdate through api fixed. ## Test releases @@ -27,6 +28,7 @@ and this project adheres to * [activity] layout for issues / actions * [activity][bugfix] in edit mode, the form will now load the social action list + ### Test release 2021-11-29 * [person] suggest entities (person | thirdparty) when creating/editing the accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/119) @@ -53,7 +55,9 @@ and this project adheres to * add an endpoint for checking permissions. See https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/232 * [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties * [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties -* [bugfix] posting of birth- and deathdate through api fixed. + + +## Test releases ### Test release 2021-11-22 diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue index 8696041f9..5550c3e54 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue @@ -53,6 +53,7 @@
{{ $t('visgraph.between') }}
{{ $t('visgraph.and') }}
+ {{ getPersonAge(modal.data.from) }}

{{ getPerson(modal.data.from).text }}

@@ -64,6 +65,7 @@

+ {{ getPersonAge(modal.data.to) }}

{{ getPerson(modal.data.to).text }}

@@ -119,8 +121,9 @@ import vis from 'vis-network/dist/vis-network' import { mapState, mapGetters } from "vuex" import Modal from 'ChillMainAssets/vuejs/_components/Modal' import VueMultiselect from 'vue-multiselect' -import { getRelationsList, postRelationship, patchRelationship, deleteRelationship } from "./api"; -import { splitId } from "./vis-network"; +import { getRelationsList, postRelationship, patchRelationship, deleteRelationship } from "./api" +import { splitId, getAge } from "./vis-network" +import { visMessages } from "./i18n"; export default { name: "App", @@ -128,6 +131,7 @@ export default { Modal, VueMultiselect }, + props: ['household_id'], data() { return { container: '', @@ -152,7 +156,9 @@ export default { class: null, text: null }, - } + }, + canvas: null, + link: null, } }, computed: { @@ -164,7 +170,7 @@ export default { ]), visgraph_data() { - console.log('::: visgraph_data :::', this.nodes.length, 'nodes,', this.edges.length, 'edges') + //console.log('::: visgraph_data :::', this.nodes.length, 'nodes,', this.edges.length, 'edges') return { nodes: this.nodes, edges: this.edges @@ -172,12 +178,12 @@ export default { }, refreshNetwork() { - console.log('--- refresh network') + //console.log('--- refresh network') window.network.setData(this.visgraph_data) }, legendLayers() { - console.log('--- refresh legend and rebuild checked Layers') + //console.log('--- refresh legend and rebuild checked Layers') this.checkedLayers = [] let layersDisplayed = [ ...this.nodes.filter(n => n.id.startsWith('household')), @@ -193,7 +199,7 @@ export default { }, checkedLayers() { // required to refresh data checkedLayers - console.log('--- checkedLayers') + //console.log('--- checkedLayers') return this.checkedLayers }, @@ -218,7 +224,7 @@ export default { }, watch: { updateHack(newValue, oldValue) { - console.log(`--- updateHack ${oldValue} <> ${newValue}`) + //console.log(`--- updateHack ${oldValue} <> ${newValue}`) if (oldValue !== newValue) { this.forceUpdateComponent() } @@ -229,6 +235,9 @@ export default { this.initGraph() this.listenOnGraph() this.getRelationsList() + + this.canvas = document.getElementById('visgraph').querySelector('canvas') + this.link = document.getElementById('exportCanvasBtn') }, methods: { @@ -255,27 +264,27 @@ export default { case 'person': let person = this.nodes.filter(n => n.id === node)[0] - console.log('@@@@@@ event on selected Node', person.id) + //console.log('@@@@@@ event on selected Node', person.id) if (this.listenPersonFlag === 'normal') { if (person.folded === true) { - console.log(' @@> expand mode event') + //console.log(' @@> expand mode event') this.$store.commit('unfoldPerson', person) } } else { - console.log(' @@> create link mode event') + //console.log(' @@> create link mode event') this.listenStepsToAddRelationship(person) } break case 'household': let household = this.nodes.filter(n => n.id === node)[0] - console.log('@@@@@@ event on selected Node', household.id) + //console.log('@@@@@@ event on selected Node', household.id) this.$store.dispatch('unfoldPersonsByHousehold', household) break case 'accompanying_period': let course = this.nodes.filter(n => n.id === node)[0] - console.log('@@@@@@ event on selected Node', course.id) + //console.log('@@@@@@ event on selected Node', course.id) this.$store.dispatch('unfoldPersonsByCourse', course) break @@ -290,7 +299,7 @@ export default { } let link = data.edges[0] let linkType = splitId(link, 'link') - console.log('@@@@@ event on selected Edge', data.edges.length, linkType, data) + //console.log('@@@@@ event on selected Edge', data.edges.length, linkType, data) if (linkType.startsWith('relationship')) { //console.log('linkType relationship') @@ -314,7 +323,7 @@ export default { }) }, listenStepsToAddRelationship(person) { - console.log(' @@> listenStep', this.listenPersonFlag) + //console.log(' @@> listenStep', this.listenPersonFlag) if (this.listenPersonFlag === 'step2') { //console.log(' @@> person 2', person) this.newEdgeData.to = person.id @@ -333,7 +342,7 @@ export default { /// control Layers toggleLayer(value) { let id = value.target.value - console.log('@@@@@@ toggle Layer', id) + //console.log('@@@@@@ toggle Layer', id) this.forceUpdateComponent() if (this.checkedLayers.includes(id)) { this.removeLayer(id) @@ -382,7 +391,7 @@ export default { title: null, button: { class: null, text: null, } } - console.log('==- reset Form', this.modal.data) + //console.log('==- reset Form', this.modal.data) }, getRelationsList() { //console.log('fetch relationsList') @@ -400,12 +409,16 @@ export default { let person = this.persons.filter(p => p.id === id) return person[0] }, + getPersonAge(id) { + let person = this.getPerson(id) + return getAge(person) + }, // actions createRelationship() { this.displayHelpMessage = true this.listenPersonFlag = 'step1' // toggle listener in create link mode - console.log(' @@> switch listener to create link mode:', this.listenPersonFlag) + //console.log(' @@> switch listener to create link mode:', this.listenPersonFlag) }, dropRelationship() { //console.log('delete', this.modal.data) @@ -417,13 +430,13 @@ export default { this.forceUpdateComponent() }, submitRelationship() { - console.log('submitRelationship', this.modal.action) + //console.log('submitRelationship', this.modal.action) switch (this.modal.action) { case 'create': return postRelationship(this.modal.data) .then(relationship => new Promise(resolve => { - console.log('post relationship response', relationship) + //console.log('post relationship response', relationship) this.$store.dispatch('addLinkFromRelationship', relationship) this.modal.showModal = false this.resetForm() @@ -435,7 +448,7 @@ export default { case 'edit': return patchRelationship(this.modal.data) .then(relationship => new Promise(resolve => { - console.log('patch relationship response', relationship) + //console.log('patch relationship response', relationship) this.$store.commit('updateLink', relationship) this.modal.showModal = false this.resetForm() @@ -450,39 +463,44 @@ export default { }, // export image - exportCanvasAsImage() { - const canvas = document.getElementById('visgraph') - .querySelector('canvas') - console.log(canvas) + async exportCanvasAsImage() { - let link = document.getElementById('exportCanvasBtn') - link.download = "filiation.png" + let + filename = `filiation_${this.household_id}.jpg`, + mime = 'image/jpeg', + quality = 0.85, + footer = `© Chill ${new Date().getFullYear()}`, + timestamp = `${visMessages.fr.visgraph.relationship_household} n° ${this.household_id} — ${new Date().toLocaleString()}` - canvas.toBlob(blob => { - console.log(blob) - link.href = URL.createObjectURL(blob) - }, 'image/png') + // resolve toBlob in a Promise + const getCanvasBlob = canvas => new Promise(resolve => { + canvas.toBlob(blob => resolve(blob), mime, quality) + }) - /* - TODO improve feature - - // 1. fonctionne, mais pas de contrôle sur le nom - if (canvas && canvas.getContext('2d')) { - let img = canvas.toDataURL('image/png;base64;') - img = img.replace('image/png','image/octet-stream') - window.open(img, '', 'width=1000, height=1000') - } - - // 2. fonctionne, mais 2 click et pas compatible avec tous les browsers - let link = document.getElementById('exportCanvasBtn') - link.download = "image.png" - canvas.toBlob(blob => { - link.href = URL.createObjectURL(blob) - }, 'image/png') - */ + // build image from new temporary canvas + let tmpCanvas = document.createElement('canvas') + tmpCanvas.width = this.canvas.width + tmpCanvas.height = this.canvas.height + let ctx = tmpCanvas.getContext('2d') + ctx.beginPath() + ctx.fillStyle = '#fff' + ctx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height); + ctx.fillStyle = '#9d4600' + ctx.fillText(footer +' — '+ timestamp, 5, tmpCanvas.height - 10) + ctx.drawImage(this.canvas, 0, 0) + return await getCanvasBlob(tmpCanvas) + .then(blob => { + let url = document.createElement("a") + url.download = filename + url.href = window.URL.createObjectURL(blob) + url.click() + console.log('url', url.href) + URL.revokeObjectURL(url.href) + }) } + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js index c2ee09960..8047aea11 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js @@ -24,6 +24,7 @@ const visMessages = { refresh: "Rafraîchir", screenshot: "Prendre une photo", choose_relation: "Choisissez le lien de parenté", + relationship_household: "Filiation du ménage", }, edit: 'Éditer', del: 'Supprimer', diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js index ca76f283b..5e8989e49 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js @@ -16,7 +16,12 @@ persons.forEach(person => { }) const app = createApp({ - template: `` + template: ``, + data() { + return { + household_id: JSON.parse(container.dataset.householdId) + } + } }) .use(store) .use(i18n) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js index 119a7b29c..10269c6c8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js @@ -112,7 +112,7 @@ const store = createStore({ } }) //console.log('array', array.map(item => item.person.id)) - console.log('get persons group', group.map(f => f.id)) + //console.log('get persons group', group.map(f => f.id)) return group }, @@ -120,13 +120,17 @@ const store = createStore({ }, mutations: { addPerson(state, [person, options]) { + let age = getAge(person) + age = (age === '')? '' : ' - ' + age + 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.label = `*${person.text}*\n_${getGender(person.gender)}${age}_${debug}` person.folded = false // folded is used for missing persons if (options.folded) { @@ -161,7 +165,7 @@ const store = createStore({ state.links.push(link) }, updateLink(state, link) { - console.log('updateLink', link) + //console.log('updateLink', link) let link_ = { from: `person_${link.fromPerson.id}`, to: `person_${link.toPerson.id}`, @@ -264,7 +268,7 @@ const store = createStore({ fetchInfoForPerson({ dispatch }, person) { // TODO enfants hors ménages // example: household 61 - // console.log(person.text, 'household', person.current_household_id) + //console.log(person.text, 'household', person.current_household_id) if (null !== person.current_household_id) { dispatch('fetchHouseholdForPerson', person) } @@ -305,15 +309,16 @@ const store = createStore({ */ addLinkFromPersonsToHousehold({ commit, getters, dispatch }, household) { let members = getters.getMembersByHousehold(household.id) - console.log('add link for', members.length, 'members') + //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}`, + to: `${household.id}`, + id: `${household.id}-person_${m.person.id}`, arrows: 'from', color: 'pink', font: { color: '#D04A60' }, + dashes: (getHouseholdWidth(m) === 1)? [0,4] : false, //edge style: [dash, gap, dash, gap] label: getHouseholdLabel(m), width: getHouseholdWidth(m), }) @@ -362,7 +367,7 @@ const store = createStore({ */ addLinkFromPersonsToCourse({ commit, getters, dispatch }, course) { const participations = getters.getParticipationsByCourse(course.id) - console.log('add link for', participations.length, 'participations') + //console.log('add link for', participations.length, 'participations') participations.forEach(p => { //console.log(p.person.id) commit('addLink', { @@ -445,7 +450,7 @@ const store = createStore({ * @param array */ addMissingPerson({ commit, getters, dispatch }, [person, parent]) { - console.log('! add missing Person', person.id) + //console.log('! add missing Person', person.id) commit('markPersonLoaded', person.id) commit('addPerson', [person, { folded: true }]) if (getters.isExcludedNode(parent.id)) { @@ -467,7 +472,7 @@ const store = createStore({ getters.getPersonsGroup(participations) .forEach(person => { if (person.folded === true) { - console.log('-=. unfold and expand person', person.id) + //console.log('-=. unfold and expand person', person.id) commit('unfoldPerson', person) dispatch('fetchInfoForPerson', person) } @@ -485,7 +490,7 @@ const store = createStore({ getters.getPersonsGroup(members) .forEach(person => { if (person.folded === true) { - console.log('-=. unfold and expand person', person.id) + //console.log('-=. unfold and expand person', person.id) commit('unfoldPerson', person) dispatch('fetchInfoForPerson', person) } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js index e95bc0d0b..3e00db883 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js @@ -13,13 +13,12 @@ window.options = { locale: 'fr', locales: visMessages, /* + */ configure: { enabled: true, - filter: 'nodes,edges', - //container: undefined, + filter: 'physics', showButton: true }, - */ physics: { enabled: true, barnesHut: { @@ -37,8 +36,8 @@ window.options = { centralGravity: 0.01, springLength: 100, springConstant: 0.08, - damping: 0.4, - avoidOverlap: 0 + damping: 0.75, + avoidOverlap: 0.00 }, repulsion: { centralGravity: 0.2, @@ -159,17 +158,21 @@ const getGender = (gender) => { } /** - * TODO Repeat getAge() in PersonRenderBox.vue - * @param birthdate + * TODO only one abstract function (-> getAge() is repeated in PersonRenderBox.vue) + * @param person * @returns {string|null} */ -const getAge = (birthdate) => { - if (null === birthdate) { - return null +const getAge = (person) => { + if (person.birthdate) { + let birthdate = new Date(person.birthdate.datetime) + if (person.deathdate) { + let deathdate = new Date(person.deathdate.datetime) + return (deathdate.getFullYear() - birthdate.getFullYear()) + visMessages.fr.visgraph.years + } + let now = new Date() + return (now.getFullYear() - birthdate.getFullYear()) + visMessages.fr.visgraph.years } - const birthday = new Date(birthdate.datetime) - const now = new Date() - return (now.getFullYear() - birthday.getFullYear()) + ' '+ visMessages.fr.visgraph.years + return '' } /** diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue index ffb9ef3d4..ab7074ffc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue @@ -192,6 +192,7 @@ export default { return `/fr/person/${this.person.id}/general`; }, getAge: function() { + // TODO only one abstract function if(this.person.birthdate && !this.person.deathdate){ const birthday = new Date(this.person.birthdate.datetime) const now = new Date() diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig index 56fcce85c..acaeebb96 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/relationship.html.twig @@ -17,7 +17,8 @@

+ data-persons="{{ persons|e('html_attr') }}" + data-household-id="{{ household.id|e('html_attr') }}">
{% endblock %}