Merge branch 'export_vue_multiselect' into 111_exports_suite

This commit is contained in:
Mathieu Jaumotte 2022-09-08 14:23:22 +02:00
commit 17494b3e9f
6 changed files with 472 additions and 80 deletions

View File

@ -0,0 +1,295 @@
<template>
<teleport to="#export_filters_social_work_type_filter_form">
<fieldset class="mb-3" id="actionType">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('action.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
name="actionType"
v-model="action"
:options="actions.options"
@select="selectAction"
@remove="unselectAction"
:multiple="true"
:close-on-select="false"
:placeholder="$t('action.placeholder')"
label="text"
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
<fieldset class="mb-3" id="goal">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('goal.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
name="goal"
v-model="goal"
:options="goals.options"
@select="selectGoal"
@remove="unselectGoal"
:multiple="true"
:close-on-select="false"
:placeholder="$t('goal.placeholder')"
label="title"
:custom-label="transTitle"
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
<fieldset class="mb-3" id="result">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('result.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
name="result"
v-model="result"
:options="results.options"
:multiple="true"
:close-on-select="false"
:placeholder="$t('result.placeholder')"
label="title"
:custom-label="transTitle"
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
</teleport>
</template>
<script>
import VueMultiselect from 'vue-multiselect';
import { getSocialActions, getGoalByAction, getResultByAction, getResultByGoal } from './api';
export default {
name: "App",
components: {
VueMultiselect
},
i18n: {
messages: {
fr: {
action: {
label: 'Types d\'actions',
placeholder: 'Choisissez une ou plusieurs actions',
},
goal: {
label: 'Objectifs',
placeholder: 'Choisissez un ou plusieurs objectifs',
},
result: {
label: 'Résultats',
placeholder: 'Choisissez un ou plusieurs résultats',
}
}
}
},
data() {
return {
actions: {
options: [], // array with multiselect options
value: [], // array with selected values
hiddenField: document.getElementById(
'export_filters_social_work_type_filter_form_actionType'),
},
goals: {
options: [],
value: [],
hiddenField: document.getElementById(
'export_filters_social_work_type_filter_form_goal'),
},
results: {
options: [],
value: [],
hiddenField: document.getElementById(
'export_filters_social_work_type_filter_form_result'),
},
}
},
computed: {
action: {
get() {
return this.actions.value;
},
set(value) {
this.actions.value = value;
this.rebuildHiddenFieldValues('actions');
}
},
goal: {
get() {
return this.goals.value;
},
set(value) {
this.goals.value = value;
this.rebuildHiddenFieldValues('goals');
}
},
result: {
get() {
return this.results.value;
},
set(value) {
this.results.value = value;
this.rebuildHiddenFieldValues('results');
}
},
},
mounted() {
this.getSocialActionsList();
this.actions.hiddenField.value = '';
this.goals.hiddenField.value = '';
this.results.hiddenField.value = '';
},
methods: {
async getSocialActionsList() {
this.actions.options = await getSocialActions();
},
/**
* Select/unselect in Action Multiselect
* @param value
*/
selectAction(value) {
console.log('selectAction', value);
getGoalByAction(value.id).then(response => new Promise((resolve, reject) => {
//console.log('fetch Goals', response);
this.addElementInData('goals', response.results);
resolve();
})).catch;
getResultByAction(value.id).then(response => new Promise((resolve, reject) => {
//console.log('fetch Results', response);
this.addElementInData('results', response.results);
resolve();
})).catch;
},
unselectAction(value) {
console.log('unselectAction', value);
getGoalByAction(value.id).then(response => new Promise((resolve, reject) => {
//console.log('fetch Goals', response);
this.goals.options = this.removeElementInData('goals', response.results);
resolve();
})).catch;
getResultByAction(value.id).then(response => new Promise((resolve, reject) => {
//console.log('fetch Results', response);
this.results.options = this.removeElementInData('results', response.results);
resolve();
})).catch;
},
/**
* Select/unselect in Goal Multiselect
* @param value
*/
selectGoal(value) {
console.log('selectGoal', value);
getResultByGoal(value.id).then(response => new Promise((resolve, reject) => {
//console.log('fetch Results', response);
this.addElementInData('results', response.results);
resolve();
})).catch;
},
unselectGoal(value) {
console.log('unselectGoal', value);
getResultByGoal(value.id).then(response => new Promise((resolve, reject) => {
//console.log('fetch Results', response);
this.results.options = this.removeElementInData('results', response.results);
resolve();
})).catch;
},
/**
* Add response elements in data target
* @param target string -> 'actions', 'goals' or 'results'
* @param response array of objects with fetch results
*/
addElementInData(target, response) {
let data = this[target];
response.forEach(elem => {
let found = data.options.some(e => e.id === elem.id);
if (!found) {
console.log('push elem in', target, elem.id);
data.options.push(elem);
}
})
},
/**
* Remove response elements from data target
* @param target string -> 'actions', 'goals' or 'results'
* @param response array of objects with fetch results
* @returns data.<target>.options
*/
removeElementInData(target, response) {
let data = this[target];
response.forEach(elem => {
let found = data.options.some(e => e.id === elem.id);
if (found) {
console.log('remove elem from', target, elem.id);
data.options = data.options.filter(e => e.id !== elem.id);
/// remove too from selected and from hiddenField
let selected = data.value.some(e => e.id === elem.id);
if (selected) {
// remove from selected
data.value = data.value.filter(e => e.id !== elem.id);
// remove from hiddenField
this.rebuildHiddenFieldValues(target);
// remove should be recursive; here it works but is not fine
if (target === 'goals') { // <==== TODO improve loop
this.unselectGoal(elem);
}
}
///
}
})
return data.options;
},
/**
* Rebuild values serie (string) in target HiddenField
* @param target
*/
rebuildHiddenFieldValues(target) {
let data = this[target];
data.hiddenField.value = ''; // reset
data.value.forEach(elem => {
data.hiddenField.value = this.addIdToValue(data.hiddenField.value, elem.id);
})
},
addIdToValue(string, id) {
let array = string ? string.split(',') : [];
array.push(id.toString());
let str = array.join();
return str;
},
transTitle ({ title }) {
return title.fr //TODO multilang
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,41 @@
import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods';
const getSocialActions = () => fetchResults(
'/api/1.0/person/social/social-action.json', {
item_per_page: 200
}
);
const getGoalByAction = (id) => {
let url = `/api/1.0/person/social-work/goal/by-social-action/${id}.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
const getResultByAction = (id) => {
let url = `/api/1.0/person/social-work/result/by-social-action/${id}.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
const getResultByGoal = (id) => {
let url = `/api/1.0/person/social-work/result/by-goal/${id}.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
export {
getSocialActions,
getGoalByAction,
getResultByAction,
getResultByGoal,
}

View File

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

View File

@ -22,6 +22,9 @@
{% block js %} {% block js %}
{{ encore_entry_script_tags('page_export') }} {{ encore_entry_script_tags('page_export') }}
{% if export_alias == 'count_social_work_actions' %}
{{ encore_entry_script_tags('vue_form_action_goal_result') }}
{% endif %}
{% endblock js %} {% endblock js %}
{% block content %} {% block content %}

View File

@ -74,4 +74,5 @@ module.exports = function(encore, entries)
// Vue entrypoints // Vue entrypoints
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js'); encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
encore.addEntry('vue_onthefly', __dirname + '/Resources/public/vuejs/OnTheFly/index.js'); encore.addEntry('vue_onthefly', __dirname + '/Resources/public/vuejs/OnTheFly/index.js');
encore.addEntry('vue_form_action_goal_result', __dirname + '/Resources/public/vuejs/FormActionGoalResult/index.js');
}; };

View File

@ -13,105 +13,114 @@ namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\Result;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Doctrine\ORM\EntityManagerInterface;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent; use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
class SocialWorkTypeFilter implements FilterInterface class SocialWorkTypeFilter implements FilterInterface
{ {
private SocialActionRender $socialActionRender; private SocialActionRender $socialActionRender;
private SocialActionRepository $socialActionRepository;
private TranslatableStringHelper $translatableStringHelper; private TranslatableStringHelper $translatableStringHelper;
public function __construct( private EntityManagerInterface $em;
public function __construct
(
SocialActionRender $socialActionRender, SocialActionRender $socialActionRender,
TranslatableStringHelper $translatableStringHelper, TranslatableStringHelper $translatableStringHelper,
SocialActionRepository $socialActionRepository EntityManagerInterface $em
) { )
{
$this->socialActionRender = $socialActionRender; $this->socialActionRender = $socialActionRender;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->socialActionRepository = $socialActionRepository; $this->em = $em;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('r', ':referrers');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('referrers', $data['referrers']);
}
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$socialActions = $this->socialActionRepository->findAll(); $builder
->add('actionType', HiddenType::class)
$builder->add('actionType', ChoiceType::class, [ ->get('actionType')
'choices' => $socialActions, ->addModelTransformer(new CallbackTransformer(
'choice_label' => function (SocialAction $sa) { static function (?iterable $actionsAsIterable): string {
return $this->socialActionRender->renderString($sa, []); if (null === $actionsAsIterable) { return ''; }
}, $actionIds = [];
'multiple' => true, foreach ($actionsAsIterable as $value) {
'expanded' => true, $actionIds[] = $value->getId();
]);
/*
$refreshGoals = function (FormInterface $form, SocialAction $actionType = null) {
$goals = null === $actionType ? [] : $actionType->getGoals();
$form->add('goal', ChoiceType::class, [
'placeholder' => '',
'choices' => $goals,
'choice_label' => function (Goal $g) {
return $this->translatableStringHelper->localize($g->getTitle());
},
]);
};
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($refreshGoals) {
$data = $event->getData();
dump($data);
$refreshGoals($event->getForm(), $data);
});
$builder->get('actionType')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($refreshGoals) {
$actionType = $event->getForm()->getData();
dump($actionType);
$refreshGoals($event->getForm()->getParent(), $actionType);
} }
return implode(',', $actionIds);
},
function (?string $actionsAsString): array {
if (null === $actionsAsString) { return []; }
return array_map(
fn (string $id): ?SocialAction
=> $this->socialActionRepository->findOneBy(['id' => (int) $id]),
explode(',', $actionsAsString)
); );
*/ }
))
;
$builder
->add('goal', HiddenType::class)
->get('goal')
->addModelTransformer(new CallbackTransformer(
static function (?iterable $goalsAsIterable): string {
if (null === $goalsAsIterable) { return ''; }
$goalIds = [];
foreach ($goalsAsIterable as $value) {
$goalIds[] = $value->getId();
}
return implode(',', $goalIds);
},
function (?string $goalsAsString): array {
if (null === $goalsAsString) { return []; }
return array_map(
fn (string $id): ?Goal
=> $this->socialActionRepository->findOneBy(['id' => (int) $id]),
explode(',', $goalsAsString)
);
}
))
;
$builder
->add('result', HiddenType::class)
->get('result')
->addModelTransformer(new CallbackTransformer(
static function (?iterable $resultsAsIterable): string {
if (null === $resultsAsIterable) { return ''; }
$resultIds = [];
foreach ($resultsAsIterable as $value) {
$resultIds[] = $value->getId();
}
return implode(',', $resultIds);
},
function (?string $resultsAsString): array {
if (null === $resultsAsString) { return []; }
return array_map(
fn (string $id): ?Result
=> $this->socialActionRepository->findOneBy(['id' => (int) $id]),
explode(',', $resultsAsString)
);
}
))
;
}
public function getTitle(): string
{
return 'Filter by type of action, objectives and results';
} }
public function describeAction($data, $format = 'string'): array public function describeAction($data, $format = 'string'): array
@ -123,22 +132,52 @@ class SocialWorkTypeFilter implements FilterInterface
foreach ($data['actionType'] as $at) { foreach ($data['actionType'] as $at) {
$actionTypes[] = $at->getTitle(); $actionTypes[] = $at->getTitle();
} }
foreach ($data['objectives'] as $o) { foreach ($data['objectives'] as $o) {
$objectives[] = $o->getTitle(); $objectives[] = $o->getTitle();
} }
foreach ($data['results'] as $r) { foreach ($data['results'] as $r) {
$results[] = $r->getTitle(); $results[] = $r->getTitle();
} }
return ['Filtered by referrers: only %actionTypes%', [ return ['Filtered by referrers: only %actionTypes%', [
'%actionTypes%' => implode(', ou ', $actionTypes), '%actionTypes%' => implode(', ou ', $actionTypes)
]]; ]];
} }
public function getTitle(): string public function addRole(): ?string
{ {
return 'Filter by type of action, objectives and results'; return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->eq('acpw.socialAction',':actionType');
/*
if (!empty($data['goal'])) {
$clause
$qb->expr()->in('acpw.goals', ':goals');
}
$qb->expr()->in('acpw.results', ':results');
*/
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameters([
'actionType' => $data['actionType'],
'goal' => $data['goal'],
'result' => $data['result'],
]);
}
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
} }
} }