Merge branch 'household_filiation' of gitlab.com:Chill-Projet/chill-bundles into household_filiation

This commit is contained in:
Julie Lenaerts 2021-11-03 09:28:36 +01:00
commit 6d5f94ab45
8 changed files with 198 additions and 70 deletions

View File

@ -900,7 +900,28 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
] ]
], ],
] ]
] ],
[
'class' => \Chill\PersonBundle\Entity\Relationships\Relation::class,
'controller' => \Chill\PersonBundle\Controller\RelationshipApiController::class,
'name' => 'relations',
'base_path' => '/api/1.0/relations/relation',
'base_role' => 'ROLE_USER',
'actions' => [
'_index' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true
],
],
'_entity' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true
]
],
]
],
] ]
]); ]);
} }

View File

@ -20,6 +20,7 @@ class Relation
* @ORM\Id * @ORM\Id
* @ORM\GeneratedValue * @ORM\GeneratedValue
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
*/ */
private ?int $id = null; private ?int $id = null;
@ -78,7 +79,7 @@ class Relation
public function setIsActive(?bool $isActive): self public function setIsActive(?bool $isActive): self
{ {
$this->isActive = $isActive; $this->isActive = $isActive;
return $this; return $this;
} }
} }

View File

@ -1,9 +1,14 @@
<template> <template>
<!-- TEST appelle la fonction sans passer par le callback de vis-network -->
<button class="btn btn-sm btn-create" @click="addRelationshipModal({ from: 'person_1617', to: 'person_1614' })">add link</button>
<!--// <=== InternalError: too much recursion -->
<div id="visgraph"></div> <div id="visgraph"></div>
<teleport to="#visgraph-legend"> <teleport to="#visgraph-legend">
<div class="my-4 post-menu legend"> <div class="my-4 post-menu legend">
<h3>{{ $t('visgraph.Legend')}}</h3> <h3>{{ $t('visgraph.Legend') }}</h3>
<div class="list-group"> <div class="list-group">
<label class="list-group-item" v-for="layer in legendLayers"> <label class="list-group-item" v-for="layer in legendLayers">
<input <input
@ -24,45 +29,74 @@
<modal v-if="modal.showModal" :modalDialogClass="modal.modalDialogClass" @close="modal.showModal = false"> <modal v-if="modal.showModal" :modalDialogClass="modal.modalDialogClass" @close="modal.showModal = false">
<template v-slot:header> <template v-slot:header>
<h2 class="modal-title">{{ $t(modal.title) }}</h2> <h2 class="modal-title">{{ $t(modal.title) }}</h2>
<!-- {{ modal.data.id }} -->
</template> </template>
<template v-slot:body> <template v-slot:body>
<div v-if="modal.action === 'delete'"> <div v-if="modal.action === 'delete'">
<p> <p>{{ $t('visgraph.delete_confirmation_text') }}</p>
{{ $t('visgraph.delete_confirmation_text') }}
</p>
</div> </div>
<div v-else> <div v-else>
<form> <form>
{{ modal.data.id }}
<div class="row"> <div class="row">
<div class="col-12 text-center">entre<br>et</div>
<div class="col"> <div class="col">
{{ modal.data.from }} <h4>
{{ getPerson(modal.data.from).text }}
</h4>
<p class="text-start" v-if="relation && relation.title">
<span v-if="reverse">
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.from).text, getPerson(modal.data.to).text, relation.reverseTitle.fr.toLowerCase() ])}}
</span>
<span v-else>
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.from).text, getPerson(modal.data.to).text, relation.title.fr.toLowerCase() ])}}
</span>
</p>
</div> </div>
<div class="col text-end"> <div class="col text-end">
{{ modal.data.to }} <h4>
{{ getPerson(modal.data.to).text }}
</h4>
<p class="text-end" v-if="relation && relation.title">
<span v-if="reverse">
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.to).text, getPerson(modal.data.from).text, relation.title.fr.toLowerCase() ])}}
</span>
<span v-else>
{{ $t('visgraph.relation_from_to_like', [ getPerson(modal.data.to).text, getPerson(modal.data.from).text, relation.reverseTitle.fr.toLowerCase() ])}}
</span>
</p>
</div> </div>
</div> </div>
<div><!-- <div class="my-3"><!--
:value="relation"
-->
<VueMultiselect <VueMultiselect
id="relationshipLink" id="relation"
label="title"
track-by="id"
:custom-label="customLabel"
:placeholder="$t('Choisissez le lien de parenté')" :placeholder="$t('Choisissez le lien de parenté')"
:options="relationLinks" :close-on-select="true"
v-model="relationshipLink" :multiple="false"
:searchable="true"
:options="relations"
v-model="relation"
> >
</VueMultiselect> </VueMultiselect>
-->
</div> </div>
<p> <div class="form-check form-switch">
phrase <input
</p> class="form-check-input"
<div> type="checkbox"
toggle reverse id="reverse"
v-model="reverse"
>
<label class="form-check-label" for="reverse">{{ $t('visgraph.reverse_relation') }}</label>
</div> </div>
</form> </form>
</div> </div>
</template> </template>
<template v-slot:footer> <template v-slot:footer>
<button class="btn" :class="modal.button.class" @click="modal.showModal = false"> <button class="btn" :class="modal.button.class" @click="submitRelationship">
{{ $t(modal.button.text)}}</button> {{ $t(modal.button.text)}}</button>
</template> </template>
</modal> </modal>
@ -75,6 +109,7 @@ import vis from 'vis-network/dist/vis-network'
import { mapState, mapGetters } from "vuex" import { mapState, mapGetters } from "vuex"
import Modal from 'ChillMainAssets/vuejs/_components/Modal' import Modal from 'ChillMainAssets/vuejs/_components/Modal'
import VueMultiselect from 'vue-multiselect' import VueMultiselect from 'vue-multiselect'
import { getRelationsList, postRelationship } from "./api";
export default { export default {
name: "App", name: "App",
@ -86,9 +121,12 @@ export default {
return { return {
container: '', container: '',
checkedLayers: [], checkedLayers: [],
relations: [],
relation: null,
reverse: false,
modal: { modal: {
showModal: false, showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-md", modalDialogClass: "modal-md",
title: null, title: null,
action: null, action: null,
data: {}, data: {},
@ -101,9 +139,8 @@ export default {
}, },
computed: { computed: {
...mapGetters(['nodes', 'edges']), ...mapGetters(['nodes', 'edges']),
...mapState(['households', 'courses', 'excludedNodesIds', ...mapState(['households', 'courses', 'excludedNodesIds', 'persons',
// not used // not used 'links', 'relationships', 'personLoadedIds', 'householdLoadingIds', 'courseLoadedIds', 'relationshipLoadedIds',
'persons', 'links', 'relationships', 'personLoadedIds', 'householdLoadingIds', 'courseLoadedIds', 'relationshipLoadedIds',
]), ]),
visgraph_data() { visgraph_data() {
@ -141,7 +178,28 @@ export default {
checkedLayers() { // required to refresh data checkedLayers checkedLayers() { // required to refresh data checkedLayers
return this.checkedLayers return this.checkedLayers
} },
relation: {
get() {
return this.relation
},
set(value) {
//console.log('setter relation', value) // <=== InternalError: too much recursion
this.relation = value
}
},
reverse: {
get() {
return this.reverse
},
set(value) {
//console.log('setter reverse', value) // <=== InternalError: too much recursion
this.reverse = value
}
},
}, },
created() { created() {
@ -157,6 +215,7 @@ export default {
mounted() { mounted() {
console.log('=== mounted: init graph') console.log('=== mounted: init graph')
this.initGraph() this.initGraph()
this.getRelationsList()
}, },
methods: { methods: {
initGraph() { initGraph() {
@ -191,8 +250,8 @@ export default {
}, },
addRelationshipModal(edgeData) { addRelationshipModal(edgeData) {
console.log('==> addRelationshipModal <=======================', edgeData)
this.modal.data = edgeData this.modal.data = edgeData
console.log('==- addRelationshipModal', edgeData) // { from: "person_1617", to: "person_1614" }
this.modal.action = 'create' this.modal.action = 'create'
this.modal.title = 'visgraph.add_relationship_link' this.modal.title = 'visgraph.add_relationship_link'
this.modal.button.class = 'btn-create' this.modal.button.class = 'btn-create'
@ -200,8 +259,8 @@ export default {
this.modal.showModal = true this.modal.showModal = true
}, },
editRelationshipModal(edgeData) { editRelationshipModal(edgeData) {
console.log('==> editRelationshipModal <=======================', edgeData)
this.modal.data = edgeData this.modal.data = edgeData
console.log('==- editRelationshipModal', edgeData)
this.modal.action = 'edit' this.modal.action = 'edit'
this.modal.title = 'visgraph.edit_relationship_link' this.modal.title = 'visgraph.edit_relationship_link'
this.modal.button.class = 'btn-edit' this.modal.button.class = 'btn-edit'
@ -209,40 +268,62 @@ export default {
this.modal.showModal = true this.modal.showModal = true
}, },
deleteRelationshipModal(edgeData) { deleteRelationshipModal(edgeData) {
console.log('==> deleteRelationshipModal <=======================', edgeData)
this.modal.data = edgeData this.modal.data = edgeData
console.log('==- deleteRelationshipModal', edgeData)
this.modal.action = 'delete' this.modal.action = 'delete'
this.modal.title = 'visgraph.delete_relationship_link' this.modal.title = 'visgraph.delete_relationship_link'
this.modal.button.class = 'btn-delete' this.modal.button.class = 'btn-delete'
this.modal.button.text = 'action.delete' this.modal.button.text = 'action.delete'
this.modal.showModal = true this.modal.showModal = true
}, },
resetForm() {
console.log('==- reset Form')
this.modal.data = {}
this.modal.action = null
this.modal.title = null
this.modal.button.class = null
this.modal.button.text = null
},
relationLinks() { getRelationsList() {
console.log('fetch relationLinks') console.log('fetch relationsList')
return getRelations().then(relationsList => new Promise(resolve => { return getRelationsList().then(relations => new Promise(resolve => {
console.log('relationsList', relationsList) console.log('+ relations list', relations.results.filter(r => r.isActive === true))
this.relations = relations.results
resolve() resolve()
})).catch() })).catch()
}, },
} customLabel(value) {
/* console.log('customLabel', value)
TODO / TO CHECK / TO UNDERSTAND return (value.title && value.reverseTitle) ? `${value.title.fr}${value.reverseTitle.fr}` : ''
},
getPerson(idtext) {
let person = this.persons.filter(p => p.id === idtext)
return person[0]
},
///// A submitRelationship() {
new vis.Network(), param 2: why we don't need to instanciate node and edges with new vis.DataSet like in example ? console.log('submitRelationship')
{ if (this.modal.action !== 'delete') {
nodes: new vis.DataSet(this.nodes), return postRelationship(
edges: new vis.DataSet(this.relationships) this.getPerson(this.modal.data.from), this.getPerson(this.modal.data.to), this.relation, this.reverse
)
.then(response => new Promise(resolve => {
console.log('', response)
modal.showModal = false
this.resetForm()
resolve()
}))
.catch()
}
}
} }
///// B
refreshNetwork() computed: need to watch/listen event to force refreshing ?
*/
} }
</script> </script>
<style src="vis-network/dist/dist/vis-network.min.css"></style> <style src="vis-network/dist/dist/vis-network.min.css"></style>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<style lang="scss" scoped> <style lang="scss" scoped>
div#visgraph { div#visgraph {
margin: 2em auto; margin: 2em auto;
@ -253,4 +334,7 @@ div#visgraph-legend {
div.post-menu.legend { div.post-menu.legend {
} }
} }
.modal-mask {
background-color: rgba(0, 0, 0, 0.25);
}
</style> </style>

View File

@ -111,19 +111,30 @@ const getRelationshipsByPerson = (person) => {
} }
/** /**
* @function postRelationship * Return list of relations
* @param person
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
const postRelationship = (person) => { const getRelationsList = () => {
//console.log('postRelationship', person.id) return getFetch(`/api/1.0/relations/relation.json`)
}
/**
* @function postRelationship
* @param fromPerson
* @param toPerson
* @param relation
* @param reverse
* @returns {Promise<Response>}
*/
const postRelationship = (fromPerson, toPerson, relation, reverse) => {
return postFetch( return postFetch(
`/api/1.0/relations/relationship.json`, `/api/1.0/relations/relationship.json`,
{ {
from: { type: 'person', id: 0 }, type: 'relationship',
to: { type: 'person', id: 0 }, fromPerson: { type: 'person', id: fromPerson._id },
relation: { type: 'relation', id: 0 }, toPerson: { type: 'person', id: toPerson._id },
reverse: bool relation: { type: 'relation', id: relation.id },
reverse: reverse
} }
) )
} }
@ -132,5 +143,6 @@ export {
getHouseholdByPerson, getHouseholdByPerson,
getCoursesByPerson, getCoursesByPerson,
getRelationshipsByPerson, getRelationshipsByPerson,
postRelationship getRelationsList,
postRelationship,
} }

View File

@ -15,14 +15,16 @@ const visMessages = {
edit_relationship_link: "Modifier le lien de filiation", edit_relationship_link: "Modifier le lien de filiation",
delete_relationship_link: "Êtes-vous sûr ?", delete_relationship_link: "Êtes-vous sûr ?",
delete_confirmation_text: "Vous allez supprimer le lien entre ces 2 usagers.", 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}
}, },
edit: 'Éditer', edit: 'Éditer',
del: 'Supprimer', del: 'Supprimer',
back: 'Revenir en arrière', back: 'Revenir en arrière',
addNode: 'Ajouter un noeuds', addNode: 'Ajouter un noeuds',
addEdge: 'Ajouter un lien', addEdge: 'Ajouter un lien de filiation',
editNode: 'Éditer un noeuds', editNode: 'Éditer le noeuds',
editEdge: 'Éditer un lien', editEdge: 'Éditer le lien',
addDescription: 'Cliquez dans un espace vide pour créer un nouveau nœud.', 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.', 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.', editEdgeDescription: 'Cliquez sur les points de contrôle et faites-les glisser vers un nœud pour les relier.',

View File

@ -152,7 +152,7 @@ const store = createStore({
.then(household => new Promise(resolve => { .then(household => new Promise(resolve => {
//console.log('getHouseholdByPerson', household) //console.log('getHouseholdByPerson', household)
commit('addHousehold', adapt2vis(household)) commit('addHousehold', adapt2vis(household))
commit('addExcludedNode', household.id) //commit('addExcludedNode', household.id)
dispatch('addLinkFromPersonsToHousehold', household) dispatch('addLinkFromPersonsToHousehold', household)
resolve() resolve()
}) })
@ -290,7 +290,7 @@ const store = createStore({
commit('addLink', { commit('addLink', {
from: `person_${r.fromPerson.id}`, from: `person_${r.fromPerson.id}`,
to: `person_${r.toPerson.id}`, to: `person_${r.toPerson.id}`,
id: 'r' + r.id + '_p' + r.fromPerson.id + '_p' + r.toPerson.id, id: 'r' + splitId(r.id,'id') + '_p' + r.fromPerson.id + '_p' + r.toPerson.id,
arrows: 'to', arrows: 'to',
color: 'lightblue', color: 'lightblue',
font: { color: '#33839d' }, font: { color: '#33839d' },
@ -315,7 +315,7 @@ const store = createStore({
commit('markPersonLoaded', person.id) commit('markPersonLoaded', person.id)
commit('addPerson', adapt2vis(person, { folded: true })) commit('addPerson', adapt2vis(person, { folded: true }))
console.log('********* fetch infos for missing', person.id, '******') console.log(' fetch infos for missing', person.id)
//dispatch('fetchInfoForPerson', person) //dispatch('fetchInfoForPerson', person)
}, },
} }

View File

@ -39,8 +39,8 @@ window.options = {
theta: 0.5, theta: 0.5,
gravitationalConstant: -50, gravitationalConstant: -50,
centralGravity: 0.01, centralGravity: 0.01,
springConstant: 0.08,
springLength: 100, springLength: 100,
springConstant: 0.08,
damping: 0.4, damping: 0.4,
avoidOverlap: 0 avoidOverlap: 0
}, },
@ -61,7 +61,7 @@ window.options = {
}, },
maxVelocity: 50, maxVelocity: 50,
minVelocity: 0.1, minVelocity: 0.1,
solver: 'barnesHut', solver: 'forceAtlas2Based', //'barnesHut', //
stabilization: { stabilization: {
enabled: true, enabled: true,
iterations: 1000, iterations: 1000,
@ -85,11 +85,8 @@ window.options = {
}, },
manipulation: { manipulation: {
enabled: true, enabled: true,
initiallyActive: true, initiallyActive: false,
addNode: function(nodeData, callback) { addNode: false,
console.log('addNode', nodeData)
//callback(nodeData) disabled
},
editNode: function(nodeData, callback) { editNode: function(nodeData, callback) {
console.log('editNode', nodeData) console.log('editNode', nodeData)
//callback(nodeData) disabled //callback(nodeData) disabled

View File

@ -307,7 +307,7 @@ components:
- $ref: "#/components/schemas/RelationById" - $ref: "#/components/schemas/RelationById"
reverse: reverse:
type: boolean type: boolean
paths: paths:
/1.0/person/person/{id}.json: /1.0/person/person/{id}.json:
@ -1093,7 +1093,7 @@ paths:
description: "OK" description: "OK"
400: 400:
description: "transition cannot be applyed" description: "transition cannot be applyed"
/1.0/person/accompanying-course/by-person/{person_id}.json: /1.0/person/accompanying-course/by-person/{person_id}.json:
get: get:
tags: tags:
@ -1647,7 +1647,7 @@ paths:
description: "OK" description: "OK"
400: 400:
description: "Bad Request" description: "Bad Request"
/1.0/relations/relationship.json: /1.0/relations/relationship.json:
post: post:
tags: tags:
@ -1671,3 +1671,14 @@ paths:
description: "Unauthorized" description: "Unauthorized"
422: 422:
description: "Invalid data: the data is a valid json, could be deserialized, but does not pass validation" description: "Invalid data: the data is a valid json, could be deserialized, but does not pass validation"
/1.0/relations/relation.json:
get:
tags:
- relationships
summary: get a list of relations
responses:
401:
description: "Unauthorized"
200:
description: "OK"