mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge remote-tracking branch 'origin/master' into features/docgen-widget-generate-template
This commit is contained in:
commit
5a61f05faa
16
CHANGELOG.md
16
CHANGELOG.md
@ -11,8 +11,18 @@ and this project adheres to
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
<!-- write down unreleased development here -->
|
<!-- write down unreleased development here -->
|
||||||
|
* [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] fix bug when using birthdate after and birthdate before
|
||||||
* [person search] increase pertinence when lastname begins with search pattern
|
* [person search] increase pertinence when lastname begins with search pattern
|
||||||
|
* [activity/actions] Améliore la cohérence du design entre
|
||||||
|
* la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes)
|
||||||
|
* la page liste des actions
|
||||||
|
* la page liste des activités (contexte personne / contexte parcours)
|
||||||
|
* [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322)
|
||||||
|
* [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
|
## Test releases
|
||||||
|
|
||||||
@ -23,6 +33,7 @@ and this project adheres to
|
|||||||
* [activity] layout for issues / actions
|
* [activity] layout for issues / actions
|
||||||
* [activity][bugfix] in edit mode, the form will now load the social action list
|
* [activity][bugfix] in edit mode, the form will now load the social action list
|
||||||
|
|
||||||
|
|
||||||
### Test release 2021-11-29
|
### 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)
|
* [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)
|
||||||
@ -50,6 +61,9 @@ and this project adheres to
|
|||||||
* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
|
* [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
|
* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
|
||||||
|
|
||||||
|
|
||||||
|
## Test releases
|
||||||
|
|
||||||
### Test release 2021-11-22
|
### Test release 2021-11-22
|
||||||
|
|
||||||
* [activity] delete admin_user_show in twig template because this route is not defined and should be defined
|
* [activity] delete admin_user_show in twig template because this route is not defined and should be defined
|
||||||
@ -164,7 +178,7 @@ and this project adheres to
|
|||||||
* fast creation buttons
|
* fast creation buttons
|
||||||
* add ordering for types
|
* add ordering for types
|
||||||
|
|
||||||
* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities;
|
* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities;
|
||||||
* Improve badges behaviour with small screens;
|
* Improve badges behaviour with small screens;
|
||||||
|
|
||||||
* [ThirdParty]:
|
* [ThirdParty]:
|
||||||
|
@ -41,6 +41,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
|
|||||||
$loader->load('services/form.yaml');
|
$loader->load('services/form.yaml');
|
||||||
$loader->load('services/templating.yaml');
|
$loader->load('services/templating.yaml');
|
||||||
$loader->load('services/accompanyingPeriodConsistency.yaml');
|
$loader->load('services/accompanyingPeriodConsistency.yaml');
|
||||||
|
$loader->load('services/doctrine.entitylistener.yaml');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function prepend(ContainerBuilder $container)
|
public function prepend(ContainerBuilder $container)
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\ActivityBundle\EntityListener;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
class ActivityEntityListener
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
|
private AccompanyingPeriodWorkRepository $workRepository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em, AccompanyingPeriodWorkRepository $workRepository)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
$this->workRepository = $workRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function persistActionToCourse(Activity $activity)
|
||||||
|
{
|
||||||
|
if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
|
||||||
|
$period = $activity->getAccompanyingPeriod();
|
||||||
|
|
||||||
|
$accompanyingCourseWorks = $this->workRepository->findByAccompanyingPeriod($period);
|
||||||
|
$periodActions = [];
|
||||||
|
$now = new DateTimeImmutable();
|
||||||
|
|
||||||
|
foreach ($accompanyingCourseWorks as $key => $work) {
|
||||||
|
// take only the actions which are still opened
|
||||||
|
if ($work->getEndDate() === null || $work->getEndDate() > ($activity->getDate() ?? $now)) {
|
||||||
|
$periodActions[$key] = spl_object_hash($work->getSocialAction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$associatedPersons = $activity->getPersonsAssociated();
|
||||||
|
$associatedThirdparties = $activity->getThirdParties();
|
||||||
|
|
||||||
|
foreach ($activity->getSocialActions() as $action) {
|
||||||
|
if (in_array(spl_object_hash($action), $periodActions, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$newAction = new AccompanyingPeriodWork();
|
||||||
|
$newAction->setSocialAction($action);
|
||||||
|
$period->addWork($newAction);
|
||||||
|
|
||||||
|
$date = DateTimeImmutable::createFromMutable($activity->getDate());
|
||||||
|
$newAction->setStartDate($date);
|
||||||
|
|
||||||
|
foreach ($associatedPersons as $person) {
|
||||||
|
$newAction->addPerson($person);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($associatedThirdparties as $thirdparty) {
|
||||||
|
$newAction->setHandlingThierparty($thirdparty);
|
||||||
|
}
|
||||||
|
$this->em->persist($newAction);
|
||||||
|
$this->em->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -143,7 +143,7 @@ class ActivityType extends AbstractType
|
|||||||
|
|
||||||
return array_map(
|
return array_map(
|
||||||
fn (string $id): ?SocialIssue => $this->om->getRepository(SocialIssue::class)->findOneBy(['id' => (int) $id]),
|
fn (string $id): ?SocialIssue => $this->om->getRepository(SocialIssue::class)->findOneBy(['id' => (int) $id]),
|
||||||
explode(',', $socialIssuesAsString)
|
explode(',', (string) $socialIssuesAsString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@ -169,7 +169,7 @@ class ActivityType extends AbstractType
|
|||||||
|
|
||||||
return array_map(
|
return array_map(
|
||||||
fn (string $id): ?SocialAction => $this->om->getRepository(SocialAction::class)->findOneBy(['id' => (int) $id]),
|
fn (string $id): ?SocialAction => $this->om->getRepository(SocialAction::class)->findOneBy(['id' => (int) $id]),
|
||||||
explode(',', $socialActionsAsString)
|
explode(',', (string) $socialActionsAsString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@ -262,7 +262,7 @@ class ActivityType extends AbstractType
|
|||||||
function (?string $personsAsString): array {
|
function (?string $personsAsString): array {
|
||||||
return array_map(
|
return array_map(
|
||||||
fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
|
fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
|
||||||
explode(',', $personsAsString)
|
explode(',', (string) $personsAsString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@ -284,7 +284,7 @@ class ActivityType extends AbstractType
|
|||||||
function (?string $thirdpartyAsString): array {
|
function (?string $thirdpartyAsString): array {
|
||||||
return array_map(
|
return array_map(
|
||||||
fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
|
fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
|
||||||
explode(',', $thirdpartyAsString)
|
explode(',', (string) $thirdpartyAsString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@ -317,7 +317,7 @@ class ActivityType extends AbstractType
|
|||||||
function (?string $usersAsString): array {
|
function (?string $usersAsString): array {
|
||||||
return array_map(
|
return array_map(
|
||||||
fn (string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]),
|
fn (string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]),
|
||||||
explode(',', $usersAsString)
|
explode(',', (string) $usersAsString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@ -332,7 +332,7 @@ class ActivityType extends AbstractType
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $location->getId();
|
return (string) $location->getId();
|
||||||
},
|
},
|
||||||
function (?string $id): ?Location {
|
function (?string $id): ?Location {
|
||||||
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
|
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
|
||||||
@ -379,7 +379,7 @@ class ActivityType extends AbstractType
|
|||||||
$timezoneUTC = new DateTimeZone('GMT');
|
$timezoneUTC = new DateTimeZone('GMT');
|
||||||
/** @var DateTime $data */
|
/** @var DateTime $data */
|
||||||
$data = $formEvent->getData() === null ?
|
$data = $formEvent->getData() === null ?
|
||||||
DateTime::createFromFormat('U', 300) :
|
DateTime::createFromFormat('U', '300') :
|
||||||
$formEvent->getData();
|
$formEvent->getData();
|
||||||
$seconds = $data->getTimezone()->getOffset($data);
|
$seconds = $data->getTimezone()->getOffset($data);
|
||||||
$data->setTimeZone($timezoneUTC);
|
$data->setTimeZone($timezoneUTC);
|
||||||
|
@ -24,14 +24,16 @@ div.new-activity-select-type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//// ACTIVITY LIST PAGE
|
//// ACTIVITY LIST PAGE
|
||||||
// precise badge-title specific details
|
// precise dashboard specific details
|
||||||
|
|
||||||
|
p.date-label {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18pt;
|
||||||
|
}
|
||||||
|
div.dashboard,
|
||||||
h2.badge-title {
|
h2.badge-title {
|
||||||
div.duration {
|
|
||||||
font-size: smaller;
|
|
||||||
padding-left: 1em;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
ul.list-content {
|
ul.list-content {
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
@ -39,16 +41,13 @@ h2.badge-title {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
li {
|
li {
|
||||||
margin-bottom: 0.2em;
|
margin-bottom: 0.2em;
|
||||||
// exception: change bg color for action badges above badge-title
|
// exception: change bg color for action badges above dashboard
|
||||||
.bg-light {
|
.bg-light {
|
||||||
background-color: $chill-light-gray !important;
|
background-color: $chill-light-gray !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div.main {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
//// ACTIVITY SHOW AND FORM PAGES
|
//// ACTIVITY SHOW AND FORM PAGES
|
||||||
// Exceptions for flex-bloc in concerned-groups
|
// Exceptions for flex-bloc in concerned-groups
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
<h2 class="badge-title">
|
|
||||||
<span class="title_label">
|
|
||||||
|
|
||||||
{% if activity.date %}
|
|
||||||
<h3>{{ activity.date|format_date('short') }}</h3>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="duration">
|
|
||||||
{% if activity.durationTime and t.durationTimeVisible %}
|
|
||||||
<p>
|
|
||||||
<abbr class="fa fa-fw fa-hourglass-end" title="{{ 'Duration Time'|trans }}"></abbr>
|
|
||||||
{{ activity.durationTime|date('H:i') }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if activity.travelTime and t.travelTimeVisible %}
|
|
||||||
<p>
|
|
||||||
<abbr class="fa fa-fw fa-car" title="{{ 'Travel time'|trans }}"></abbr>
|
|
||||||
{{ activity.travelTime|date('H:i') }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
<span class="title_action">
|
|
||||||
|
|
||||||
{{ activity.type.name | localize_translatable_string }}
|
|
||||||
|
|
||||||
{% if activity.emergency %}
|
|
||||||
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<ul class="small_in_title mt-3">
|
|
||||||
|
|
||||||
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
|
|
||||||
<li>
|
|
||||||
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
|
|
||||||
<b>{{ activity.sentReceived|capitalize|trans }}</b>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if activity.location and t.locationVisible %}
|
|
||||||
<li>
|
|
||||||
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
|
|
||||||
<b>
|
|
||||||
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
|
|
||||||
{{ activity.location.name }}
|
|
||||||
</b>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if activity.user and t.userVisible %}
|
|
||||||
<li>
|
|
||||||
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
|
||||||
<b>{{ activity.user.usernameCanonical }}</b>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<li class="associated-persons">
|
|
||||||
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
|
|
||||||
{% for p in activity.personsAssociated %}
|
|
||||||
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="list-content my-3">
|
|
||||||
{%- if t.reasonsVisible -%}
|
|
||||||
{%- if activity.reasons is not empty -%}
|
|
||||||
<li class="reasons">
|
|
||||||
{% for r in activity.reasons %}
|
|
||||||
{{ r|chill_entity_render_box }}
|
|
||||||
{% endfor %}
|
|
||||||
</li>
|
|
||||||
{%- endif -%}
|
|
||||||
{% endif %}
|
|
||||||
{%- if t.socialIssuesVisible %}
|
|
||||||
{%- if activity.socialIssues is not empty -%}
|
|
||||||
<li class="social-issues">
|
|
||||||
{% for r in activity.socialIssues %}
|
|
||||||
{{ r|chill_entity_render_box }}
|
|
||||||
{% endfor %}
|
|
||||||
</li>
|
|
||||||
{%- endif -%}
|
|
||||||
{% endif %}
|
|
||||||
{%- if t.socialActionsVisible -%}
|
|
||||||
{%- if activity.socialActions is not empty -%}
|
|
||||||
<li class="social-actions">
|
|
||||||
{% for r in activity.socialActions %}
|
|
||||||
{{ r|chill_entity_render_box }}
|
|
||||||
{% endfor %}
|
|
||||||
</li>
|
|
||||||
{%- endif -%}
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
|
||||||
<div class="mt-3">
|
|
||||||
<a class="btn btn-sm btn-outline-primary"
|
|
||||||
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
|
|
||||||
href="{{ chill_path_add_return_path(
|
|
||||||
"chill_person_accompanying_course_index",
|
|
||||||
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
|
||||||
) }}"><i class="fa fa-random"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
@ -126,7 +126,7 @@
|
|||||||
<div class="wl-row">
|
<div class="wl-row">
|
||||||
{% if bloc.items|length > 0 %}
|
{% if bloc.items|length > 0 %}
|
||||||
<div class="wl-col title">
|
<div class="wl-col title">
|
||||||
<h4>{{ bloc.title }}</h4>
|
<h3>{{ bloc.title }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="wl-col list">
|
<div class="wl-col list">
|
||||||
{% for item in bloc.items %}
|
{% for item in bloc.items %}
|
||||||
|
@ -10,50 +10,175 @@
|
|||||||
{% for activity in activities %}
|
{% for activity in activities %}
|
||||||
{% set t = activity.type %}
|
{% set t = activity.type %}
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
|
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
<div class="wrap-list">
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
|
||||||
|
{% if activity.date %}
|
||||||
|
<p class="date-label">
|
||||||
|
{{ activity.date|format_date('short') }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
|
||||||
|
<h2 class="badge-title">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
{{ activity.type.name | localize_translatable_string }}
|
||||||
|
|
||||||
|
{% if activity.emergency %}
|
||||||
|
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if activity.comment.comment is not empty
|
<div class="item-row column separator">
|
||||||
or activity.persons|length > 0
|
<div class="wrap-list">
|
||||||
or activity.thirdParties|length > 0
|
{% if activity.location and t.locationVisible %}
|
||||||
or activity.users|length > 0
|
<div class="wl-row">
|
||||||
%}
|
<div class="wl-col title"><h3>{{ 'location'|trans }}</h3></div>
|
||||||
<div class="main">
|
<div class="wl-col list">
|
||||||
{% if activity.comment.comment is not empty %}
|
<p class="wl-item">
|
||||||
{{ activity.comment|chill_entity_render_box({
|
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
|
||||||
'disable_markdown': false,
|
{{ activity.location.name }}
|
||||||
'limit_lines': 3,
|
</p>
|
||||||
'metadata': false,
|
</div>
|
||||||
}) }}
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title"><h3>{{ 'Sent received'|trans }}</h3></div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item">
|
||||||
|
{{ activity.sentReceived|capitalize|trans }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if activity.user and t.userVisible %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item">
|
||||||
|
{{ activity.user.usernameCanonical|chill_entity_render_string|capitalize }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||||
'context': context,
|
'context': context,
|
||||||
'with_display': 'row',
|
'with_display': 'wrap-list',
|
||||||
'entity': activity,
|
'entity': activity,
|
||||||
'badge_person': true
|
'badge_person': true
|
||||||
} %}
|
} %}
|
||||||
|
|
||||||
|
<div class="wrap-list">
|
||||||
|
{%- if activity.reasons is not empty and t.reasonsVisible -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Reasons'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% for r in activity.reasons %}
|
||||||
|
<p class="wl-item reasons">
|
||||||
|
{{ r|chill_entity_render_box }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if activity.socialIssues is not empty and t.socialIssuesVisible -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Social issues'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% for r in activity.socialIssues %}
|
||||||
|
<p class="wl-item social-issues">
|
||||||
|
{{ r|chill_entity_render_box }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if activity.socialActions is not empty and t.socialActionsVisible -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Social actions'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% for r in activity.socialActions %}
|
||||||
|
<p class="wl-item social-actions">
|
||||||
|
{{ r|chill_entity_render_box }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# SEULEMENT SI DÉTAILLÉ
|
||||||
|
{% if activity.comment.comment is not empty %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Comment'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{{ activity.comment|chill_entity_render_box({
|
||||||
|
'disable_markdown': false,
|
||||||
|
'limit_lines': 3,
|
||||||
|
'metadata': false
|
||||||
|
}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
#}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="item-row separator">
|
<div class="item-row separator">
|
||||||
<div class="updatedBy"></div>
|
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
|
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-primary"
|
||||||
|
title="{{ 'See activity in accompanying course context'|trans }}"
|
||||||
|
href="{{ chill_path_add_return_path(
|
||||||
|
"chill_activity_activity_list",
|
||||||
|
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
||||||
|
) }}"><i class="fa fa-random fa-fw"></i>
|
||||||
|
{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||||
class="btn btn-sm btn-show "></a>
|
class="btn btn-show" title="{{ 'Show'|trans }}"></a>
|
||||||
</li>
|
</li>
|
||||||
{% if no_action is not defined or no_action == false %}
|
{% if no_action is not defined or no_action == false %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||||
class="btn btn-sm btn-update "></a>
|
class="btn btn-update" title="{{ 'Edit'|trans }}"></a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
||||||
class="btn btn-sm btn-delete "></a>
|
class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %}
|
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %}
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create">
|
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}"
|
||||||
{{ 'Create'|trans }}
|
class="btn btn-create">{{ 'Create'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -3,10 +3,88 @@
|
|||||||
{% set t = activity.type %}
|
{% set t = activity.type %}
|
||||||
|
|
||||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||||
class="badge-link" title="{{ 'Show the activity'|trans }}">
|
class="dashboard-link" title="{{ 'Show the activity'|trans }}">
|
||||||
|
|
||||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
<div class="dashboard">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
|
||||||
|
{%- if activity.date -%}
|
||||||
|
<p class="date-label">{{ activity.date|format_date('short') }}</p>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
<span class="like-h3">{{ activity.type.name | localize_translatable_string }}</span>
|
||||||
|
|
||||||
|
{% if activity.emergency %}
|
||||||
|
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<ul class="small_in_title mt-3">
|
||||||
|
|
||||||
|
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ activity.sentReceived|capitalize|trans }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if activity.location and t.locationVisible %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
|
||||||
|
<b>
|
||||||
|
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
|
||||||
|
{{ activity.location.name }}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if activity.user and t.userVisible %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
||||||
|
<b>{{ activity.user.usernameCanonical }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="associated-persons">
|
||||||
|
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
|
||||||
|
{% for p in activity.personsAssociated %}
|
||||||
|
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="list-content my-3">
|
||||||
|
{%- if t.reasonsVisible -%}
|
||||||
|
{%- if activity.reasons is not empty -%}
|
||||||
|
<li class="reasons">
|
||||||
|
{% for r in activity.reasons %}
|
||||||
|
{{ r|chill_entity_render_box }}
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
{%- endif -%}
|
||||||
|
{% endif %}
|
||||||
|
{%- if t.socialIssuesVisible %}
|
||||||
|
{%- if activity.socialIssues is not empty -%}
|
||||||
|
<li class="social-issues">
|
||||||
|
{% for r in activity.socialIssues %}
|
||||||
|
{{ r|chill_entity_render_box }}
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
{%- endif -%}
|
||||||
|
{% endif %}
|
||||||
|
{%- if t.socialActionsVisible -%}
|
||||||
|
{%- if activity.socialActions is not empty -%}
|
||||||
|
<li class="social-actions">
|
||||||
|
{% for r in activity.socialActions %}
|
||||||
|
{{ r|chill_entity_render_box }}
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
{%- endif -%}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
Chill\ActivityBundle\EntityListener\ActivityEntityListener:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
tags:
|
||||||
|
-
|
||||||
|
name: 'doctrine.orm.entity_listener'
|
||||||
|
event: 'postPersist'
|
||||||
|
entity: 'Chill\ActivityBundle\Entity\Activity'
|
||||||
|
method: 'persistActionToCourse'
|
||||||
|
-
|
||||||
|
name: 'doctrine.orm.entity_listener'
|
||||||
|
event: 'postUpdate'
|
||||||
|
entity: 'Chill\ActivityBundle\Entity\Activity'
|
||||||
|
method: 'persistActionToCourse'
|
@ -222,3 +222,5 @@ Aggregate by activity type: Aggréger par type d'activité
|
|||||||
Aggregate by activity reason: Aggréger par sujet de l'activité
|
Aggregate by activity reason: Aggréger par sujet de l'activité
|
||||||
|
|
||||||
Last activities: Les dernières activités
|
Last activities: Les dernières activités
|
||||||
|
|
||||||
|
See activity in accompanying course context: Voir l'activité dans le contexte du parcours d'accompagnement
|
||||||
|
@ -12,14 +12,66 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Controller;
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||||
|
use Chill\MainBundle\Repository\AddressReferenceRepository;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use function trim;
|
||||||
|
|
||||||
/**
|
final class AddressReferenceAPIController extends ApiController
|
||||||
* Class AddressReferenceAPIController.
|
|
||||||
*/
|
|
||||||
class AddressReferenceAPIController extends ApiController
|
|
||||||
{
|
{
|
||||||
|
private AddressReferenceRepository $addressReferenceRepository;
|
||||||
|
|
||||||
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
|
public function __construct(AddressReferenceRepository $addressReferenceRepository, PaginatorFactory $paginatorFactory)
|
||||||
|
{
|
||||||
|
$this->addressReferenceRepository = $addressReferenceRepository;
|
||||||
|
$this->paginatorFactory = $paginatorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/1.0/main/address-reference/by-postal-code/{id}/search.json")
|
||||||
|
*/
|
||||||
|
public function search(PostalCode $postalCode, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||||
|
|
||||||
|
if (!$request->query->has('q')) {
|
||||||
|
throw new BadRequestHttpException('You must supply a "q" parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = $request->query->get('q');
|
||||||
|
|
||||||
|
if ('' === trim($pattern)) {
|
||||||
|
throw new BadRequestHttpException('the search pattern is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nb = $this->addressReferenceRepository->countByPostalCodePattern($postalCode, $pattern);
|
||||||
|
$paginator = $this->paginatorFactory->create($nb);
|
||||||
|
$addresses = $this->addressReferenceRepository->findByPostalCodePattern(
|
||||||
|
$postalCode,
|
||||||
|
$pattern,
|
||||||
|
false,
|
||||||
|
$paginator->getCurrentPageFirstItemNumber(),
|
||||||
|
$paginator->getItemsPerPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json(
|
||||||
|
new Collection($addresses, $paginator),
|
||||||
|
Response::HTTP_OK,
|
||||||
|
[],
|
||||||
|
[AbstractNormalizer::GROUPS => ['read']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected function customizeQuery(string $action, Request $request, $qb): void
|
protected function customizeQuery(string $action, Request $request, $qb): void
|
||||||
{
|
{
|
||||||
if ($request->query->has('postal_code')) {
|
if ($request->query->has('postal_code')) {
|
||||||
|
@ -12,13 +12,80 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Controller;
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Chill\MainBundle\Repository\CountryRepository;
|
||||||
|
use Chill\MainBundle\Repository\PostalCodeRepository;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
|
||||||
/**
|
final class PostalCodeAPIController extends ApiController
|
||||||
* Class PostalCodeAPIController.
|
|
||||||
*/
|
|
||||||
class PostalCodeAPIController extends ApiController
|
|
||||||
{
|
{
|
||||||
|
private CountryRepository $countryRepository;
|
||||||
|
|
||||||
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
|
private PostalCodeRepository $postalCodeRepository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
CountryRepository $countryRepository,
|
||||||
|
PostalCodeRepository $postalCodeRepository,
|
||||||
|
PaginatorFactory $paginatorFactory
|
||||||
|
) {
|
||||||
|
$this->countryRepository = $countryRepository;
|
||||||
|
$this->postalCodeRepository = $postalCodeRepository;
|
||||||
|
$this->paginatorFactory = $paginatorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/1.0/main/postal-code/search.json")
|
||||||
|
*/
|
||||||
|
public function search(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||||
|
|
||||||
|
if (!$request->query->has('q')) {
|
||||||
|
throw new BadRequestHttpException('You must supply a "q" parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = $request->query->get('q');
|
||||||
|
|
||||||
|
if ('' === trim($pattern)) {
|
||||||
|
throw new BadRequestHttpException('the search pattern is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->query->has('country')) {
|
||||||
|
$country = $this->countryRepository->find($request->query->getInt('country'));
|
||||||
|
|
||||||
|
if (null === $country) {
|
||||||
|
throw new NotFoundHttpException('country not found');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$country = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nb = $this->postalCodeRepository->countByPattern($pattern, $country);
|
||||||
|
$paginator = $this->paginatorFactory->create($nb);
|
||||||
|
$codes = $this->postalCodeRepository->findByPattern(
|
||||||
|
$pattern,
|
||||||
|
$country,
|
||||||
|
$paginator->getCurrentPageFirstItemNumber(),
|
||||||
|
$paginator->getItemsPerPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json(
|
||||||
|
new Collection($codes, $paginator),
|
||||||
|
Response::HTTP_OK,
|
||||||
|
[],
|
||||||
|
[AbstractNormalizer::GROUPS => ['read']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected function customizeQuery(string $action, Request $request, $qb): void
|
protected function customizeQuery(string $action, Request $request, $qb): void
|
||||||
{
|
{
|
||||||
if ($request->query->has('country')) {
|
if ($request->query->has('country')) {
|
||||||
|
@ -22,6 +22,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
*/
|
*/
|
||||||
class AddressReference
|
class AddressReference
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* This is an internal column which is populated by database.
|
||||||
|
*
|
||||||
|
* This column will ease the search operations
|
||||||
|
*
|
||||||
|
* @ORM\Column(type="text", options={"default": ""})
|
||||||
|
*/
|
||||||
|
private string $addressCanonical = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
|
@ -29,6 +29,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
*/
|
*/
|
||||||
class PostalCode
|
class PostalCode
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* This is an internal column which is populated by database.
|
||||||
|
*
|
||||||
|
* This column will ease the search operations
|
||||||
|
*
|
||||||
|
* @ORM\Column(type="text", options={"default": ""})
|
||||||
|
*/
|
||||||
|
private string $canonical = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Point
|
* @var Point
|
||||||
*
|
*
|
||||||
|
@ -12,17 +12,29 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Repository;
|
namespace Chill\MainBundle\Repository;
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\AddressReference;
|
use Chill\MainBundle\Entity\AddressReference;
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMapping;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
use RuntimeException;
|
||||||
|
use function explode;
|
||||||
|
use function implode;
|
||||||
|
use function strtr;
|
||||||
|
use function trim;
|
||||||
|
|
||||||
final class AddressReferenceRepository implements ObjectRepository
|
final class AddressReferenceRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(AddressReference::class);
|
$this->repository = $entityManager->getRepository(AddressReference::class);
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countAll(): int
|
public function countAll(): int
|
||||||
@ -33,6 +45,18 @@ final class AddressReferenceRepository implements ObjectRepository
|
|||||||
return $qb->getQuery()->getSingleScalarResult();
|
return $qb->getQuery()->getSingleScalarResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countByPostalCodePattern(PostalCode $postalCode, string $pattern): int
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPostalCodePattern($postalCode, $pattern);
|
||||||
|
$sql = $query->buildQuery(true);
|
||||||
|
$rsm = new ResultSetMapping();
|
||||||
|
$rsm->addScalarResult('c', 'c');
|
||||||
|
|
||||||
|
$nq = $this->entityManager->createNativeQuery($sql, $rsm)->setParameters($query->buildParameters(true));
|
||||||
|
|
||||||
|
return (int) $nq->getSingleResult()['c'];
|
||||||
|
}
|
||||||
|
|
||||||
public function find($id, $lockMode = null, $lockVersion = null): ?AddressReference
|
public function find($id, $lockMode = null, $lockVersion = null): ?AddressReference
|
||||||
{
|
{
|
||||||
return $this->repository->find($id, $lockMode, $lockVersion);
|
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||||
@ -57,6 +81,33 @@ final class AddressReferenceRepository implements ObjectRepository
|
|||||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return AddressReference[]|array
|
||||||
|
*/
|
||||||
|
public function findByPostalCodePattern(PostalCode $postalCode, string $pattern, bool $simplify = false, int $start = 0, int $limit = 50): array
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPostalCodePattern($postalCode, $pattern);
|
||||||
|
|
||||||
|
if (!$simplify) {
|
||||||
|
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||||
|
$rsm->addRootEntityFromClassMetadata(AddressReference::class, 'cma');
|
||||||
|
$query->addSelectClause($rsm->generateSelectClause());
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = strtr(
|
||||||
|
$query->buildQuery() . 'ORDER BY pertinence DESC, lpad(streetnumber, 10, \'0\') ASC OFFSET ? LIMIT ? ',
|
||||||
|
// little hack for adding sql method to point
|
||||||
|
['cma.point AS point' => 'ST_AsGeojson(cma.point) AS point']
|
||||||
|
);
|
||||||
|
$parameters = [...$query->buildParameters(), $start, $limit];
|
||||||
|
|
||||||
|
return $this->entityManager->createNativeQuery($sql, $rsm)
|
||||||
|
->setParameters($parameters)
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?AddressReference
|
public function findOneBy(array $criteria, ?array $orderBy = null): ?AddressReference
|
||||||
{
|
{
|
||||||
return $this->repository->findOneBy($criteria, $orderBy);
|
return $this->repository->findOneBy($criteria, $orderBy);
|
||||||
@ -66,4 +117,44 @@ final class AddressReferenceRepository implements ObjectRepository
|
|||||||
{
|
{
|
||||||
return AddressReference::class;
|
return AddressReference::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildQueryByPostalCodePattern(PostalCode $postalCode, string $pattern): SearchApiQuery
|
||||||
|
{
|
||||||
|
$pattern = trim($pattern);
|
||||||
|
|
||||||
|
if ('' === $pattern) {
|
||||||
|
throw new RuntimeException('the search pattern must not be empty');
|
||||||
|
}
|
||||||
|
$query = new SearchApiQuery();
|
||||||
|
|
||||||
|
$query
|
||||||
|
->setFromClause('chill_main_address_reference cma')
|
||||||
|
->andWhereClause('postcode_id = ?', [$postalCode->getId()]);
|
||||||
|
|
||||||
|
$pertinenceClause = ['STRICT_WORD_SIMILARITY(addresscanonical, UNACCENT(?))'];
|
||||||
|
$pertinenceArgs = [$pattern];
|
||||||
|
$orWhere = ['addresscanonical %>> UNACCENT(?)'];
|
||||||
|
$orWhereArgs = [$pattern];
|
||||||
|
|
||||||
|
foreach (explode(' ', $pattern) as $part) {
|
||||||
|
$part = trim($part);
|
||||||
|
|
||||||
|
if ('' === $part) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orWhere[] = "addresscanonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
|
||||||
|
$orWhereArgs[] = $part;
|
||||||
|
$pertinenceClause[] =
|
||||||
|
"(EXISTS (SELECT 1 FROM unnest(string_to_array(addresscanonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
|
||||||
|
$pertinenceClause[] =
|
||||||
|
'(addresscanonical LIKE UNACCENT(LOWER(?)))::int';
|
||||||
|
array_push($pertinenceArgs, $part, $part);
|
||||||
|
}
|
||||||
|
$query
|
||||||
|
->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs)
|
||||||
|
->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,18 +11,39 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Repository;
|
namespace Chill\MainBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Country;
|
||||||
use Chill\MainBundle\Entity\PostalCode;
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMapping;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
final class PostalCodeRepository implements ObjectRepository
|
final class PostalCodeRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->repository = $entityManager->getRepository(PostalCode::class);
|
$this->repository = $entityManager->getRepository(PostalCode::class);
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countByPattern(string $pattern, ?Country $country): int
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPattern($pattern, $country);
|
||||||
|
$sql = $query->buildQuery(true);
|
||||||
|
$rsm = new ResultSetMapping();
|
||||||
|
$rsm->addScalarResult('c', 'c');
|
||||||
|
|
||||||
|
$nq = $this->entityManager->createNativeQuery($sql, $rsm)
|
||||||
|
->setParameters($query->buildParameters(true));
|
||||||
|
|
||||||
|
return (int) $nq->getSingleResult()['c'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function find($id, $lockMode = null, $lockVersion = null): ?PostalCode
|
public function find($id, $lockMode = null, $lockVersion = null): ?PostalCode
|
||||||
@ -49,6 +70,26 @@ final class PostalCodeRepository implements ObjectRepository
|
|||||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findByPattern(string $pattern, ?Country $country, ?int $start = 0, ?int $limit = 50): array
|
||||||
|
{
|
||||||
|
$query = $this->buildQueryByPattern($pattern, $country);
|
||||||
|
|
||||||
|
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||||
|
$rsm->addRootEntityFromClassMetadata(PostalCode::class, 'cmpc');
|
||||||
|
$query->addSelectClause($rsm->generateSelectClause());
|
||||||
|
|
||||||
|
$sql = strtr(
|
||||||
|
$query->buildQuery() . 'ORDER BY pertinence DESC, canonical ASC OFFSET ? LIMIT ? ',
|
||||||
|
// little hack for adding sql method to point
|
||||||
|
['cmpc.center AS center' => 'ST_AsGeojson(cmpc.center) AS center']
|
||||||
|
);
|
||||||
|
$parameters = [...$query->buildParameters(), $start, $limit];
|
||||||
|
|
||||||
|
return $this->entityManager->createNativeQuery($sql, $rsm)
|
||||||
|
->setParameters($parameters)
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?PostalCode
|
public function findOneBy(array $criteria, ?array $orderBy = null): ?PostalCode
|
||||||
{
|
{
|
||||||
return $this->repository->findOneBy($criteria, $orderBy);
|
return $this->repository->findOneBy($criteria, $orderBy);
|
||||||
@ -58,4 +99,48 @@ final class PostalCodeRepository implements ObjectRepository
|
|||||||
{
|
{
|
||||||
return PostalCode::class;
|
return PostalCode::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildQueryByPattern(string $pattern, ?Country $country): SearchApiQuery
|
||||||
|
{
|
||||||
|
$pattern = trim($pattern);
|
||||||
|
|
||||||
|
if ('' === $pattern) {
|
||||||
|
throw new RuntimeException('the search pattern must not be empty');
|
||||||
|
}
|
||||||
|
$query = new SearchApiQuery();
|
||||||
|
|
||||||
|
$query
|
||||||
|
->setFromClause('chill_main_postal_code cmpc')
|
||||||
|
->andWhereClause('cmpc.origin = 0');
|
||||||
|
|
||||||
|
if (null !== $country) {
|
||||||
|
$query->andWhereClause('cmpc.country_id = ?', [$country->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pertinenceClause = ['STRICT_WORD_SIMILARITY(canonical, UNACCENT(?))'];
|
||||||
|
$pertinenceArgs = [$pattern];
|
||||||
|
$orWhere = ['canonical %>> UNACCENT(?)'];
|
||||||
|
$orWhereArgs = [$pattern];
|
||||||
|
|
||||||
|
foreach (explode(' ', $pattern) as $part) {
|
||||||
|
$part = trim($part);
|
||||||
|
|
||||||
|
if ('' === $part) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orWhere[] = "canonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
|
||||||
|
$orWhereArgs[] = $part;
|
||||||
|
$pertinenceClause[] =
|
||||||
|
"(EXISTS (SELECT 1 FROM unnest(string_to_array(canonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
|
||||||
|
$pertinenceClause[] =
|
||||||
|
'(canonical LIKE UNACCENT(LOWER(?)))::int';
|
||||||
|
array_push($pertinenceArgs, $part, $part);
|
||||||
|
}
|
||||||
|
$query
|
||||||
|
->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs)
|
||||||
|
->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,10 @@ div.flex-table {
|
|||||||
div.item-row {
|
div.item-row {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
|
&.column { // exception
|
||||||
|
flex-direction: column
|
||||||
|
}
|
||||||
|
|
||||||
div.item-col {
|
div.item-col {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
flex-grow: 0; flex-shrink: 0; flex-basis: auto;
|
flex-grow: 0; flex-shrink: 0; flex-basis: auto;
|
||||||
@ -160,6 +164,12 @@ div.wrap-list {
|
|||||||
& > * {
|
& > * {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3, h4 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 100%;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.wl-col.list {
|
div.wl-col.list {
|
||||||
@ -171,6 +181,10 @@ div.wrap-list {
|
|||||||
padding: 0em;
|
padding: 0em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockquote.chill-user-quote {
|
||||||
|
margin: 0.7em 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ const fetchCountries = () => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint chill_api_single_postal_code__index
|
* Endpoint chill_api_single_postal_code__index
|
||||||
* method GET, get Country Object
|
* method GET, get Cities Object
|
||||||
|
* @params {object} a country object
|
||||||
* @returns {Promise} a promise containing all Postal Code objects filtered with country
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country
|
||||||
*/
|
*/
|
||||||
const fetchCities = (country) => {
|
const fetchCities = (country) => {
|
||||||
@ -29,6 +30,40 @@ const fetchCities = (country) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint chill_main_postalcodeapi_search
|
||||||
|
* method GET, get Cities Object
|
||||||
|
* @params {string} search a search string
|
||||||
|
* @params {object} country a country object
|
||||||
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string
|
||||||
|
*/
|
||||||
|
const searchCities = (search, country) => {
|
||||||
|
const url = `/api/1.0/main/postal-code/search.json?q=${search}&country=${country.id}`;
|
||||||
|
return fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) { return response.json(); }
|
||||||
|
throw Error('Error with request resource response');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint chill_main_addressreferenceapi_search
|
||||||
|
* method GET, get AddressReference Object
|
||||||
|
* @params {string} search a search string
|
||||||
|
* @params {object} postalCode a postalCode object
|
||||||
|
* @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string
|
||||||
|
*/
|
||||||
|
const searchReferenceAddresses = (search, postalCode) => {
|
||||||
|
const url = `/api/1.0/main/address-reference/by-postal-code/${postalCode.id}/search.json?q=${search}`;
|
||||||
|
return fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) { return response.json(); }
|
||||||
|
throw Error('Error with request resource response');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint chill_api_single_address_reference__index
|
* Endpoint chill_api_single_address_reference__index
|
||||||
* method GET, get AddressReference Object
|
* method GET, get AddressReference Object
|
||||||
@ -170,5 +205,7 @@ export {
|
|||||||
postAddress,
|
postAddress,
|
||||||
patchAddress,
|
patchAddress,
|
||||||
postPostalCode,
|
postPostalCode,
|
||||||
getAddress
|
getAddress,
|
||||||
|
searchCities,
|
||||||
|
searchReferenceAddresses
|
||||||
};
|
};
|
||||||
|
@ -556,8 +556,8 @@ export default {
|
|||||||
this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: null;
|
this.entity.selected.address.distribution = this.context.edit ? this.entity.address.distribution: null;
|
||||||
this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: null;
|
this.entity.selected.address.extra = this.context.edit ? this.entity.address.extra: null;
|
||||||
|
|
||||||
this.entity.selected.writeNew.address = this.context.edit;
|
this.entity.selected.writeNew.address = this.context.edit && this.entity.address.addressReference === null && this.entity.address.street.length > 0
|
||||||
this.entity.selected.writeNew.postcode = this.context.edit;
|
this.entity.selected.writeNew.postcode = false // NB: this used to be this.context.edit, but think it was erroneous;
|
||||||
console.log('!! just set writeNew.postcode to', this.entity.selected.writeNew.postcode);
|
console.log('!! just set writeNew.postcode to', this.entity.selected.writeNew.postcode);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -569,7 +569,6 @@ export default {
|
|||||||
applyChanges()
|
applyChanges()
|
||||||
{
|
{
|
||||||
console.log('apply changes');
|
console.log('apply changes');
|
||||||
|
|
||||||
let newAddress = {
|
let newAddress = {
|
||||||
'isNoAddress': this.entity.selected.isNoAddress,
|
'isNoAddress': this.entity.selected.isNoAddress,
|
||||||
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
|
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
|
||||||
@ -633,7 +632,6 @@ export default {
|
|||||||
if (!this.context.edit) {
|
if (!this.context.edit) {
|
||||||
this.addNewAddress(newAddress)
|
this.addNewAddress(newAddress)
|
||||||
.then(payload => this.addressChangedCallback(payload));
|
.then(payload => this.addressChangedCallback(payload));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.updateAddress({
|
this.updateAddress({
|
||||||
addressId: this.context.addressId,
|
addressId: this.context.addressId,
|
||||||
@ -697,8 +695,7 @@ export default {
|
|||||||
* Async PATCH transactions,
|
* Async PATCH transactions,
|
||||||
* then update existing address with backend datas when promise is resolved
|
* then update existing address with backend datas when promise is resolved
|
||||||
*/
|
*/
|
||||||
updateAddress(payload)
|
updateAddress(payload) {
|
||||||
{
|
|
||||||
this.flag.loading = true;
|
this.flag.loading = true;
|
||||||
|
|
||||||
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
|
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
:taggable="true"
|
:taggable="true"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
@tag="addAddress"
|
@tag="addAddress"
|
||||||
|
:loading="isLoading"
|
||||||
:options="addresses">
|
:options="addresses">
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
</div>
|
</div>
|
||||||
@ -48,14 +49,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VueMultiselect from 'vue-multiselect';
|
import VueMultiselect from 'vue-multiselect';
|
||||||
|
import { searchReferenceAddresses, fetchReferenceAddresses } from '../../api.js';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddressSelection',
|
name: 'AddressSelection',
|
||||||
components: { VueMultiselect },
|
components: { VueMultiselect },
|
||||||
props: ['entity', 'updateMapCenter'],
|
props: ['entity', 'context', 'updateMapCenter'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
value: null
|
value: this.context.edit ? this.entity.address.addressReference : null,
|
||||||
|
isLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -107,6 +111,36 @@ export default {
|
|||||||
},
|
},
|
||||||
listenInputSearch(query) {
|
listenInputSearch(query) {
|
||||||
//console.log('listenInputSearch', query, this.isAddressSelectorOpen);
|
//console.log('listenInputSearch', query, this.isAddressSelectorOpen);
|
||||||
|
if (!this.entity.selected.writeNew.postcode) {
|
||||||
|
if (query.length > 2) {
|
||||||
|
this.isLoading = true;
|
||||||
|
searchReferenceAddresses(query, this.entity.selected.city).then(
|
||||||
|
addresses => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.addresses = addresses.results;
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error); //TODO better error handling
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (query.length === 0) { // Fetch all cities when suppressing the query
|
||||||
|
this.isLoading = true;
|
||||||
|
fetchReferenceAddresses(this.entity.selected.city).then(
|
||||||
|
addresses => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.addresses = addresses.results;
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isAddressSelectorOpen) {
|
if (this.isAddressSelectorOpen) {
|
||||||
this.$data.value = { text: query };
|
this.$data.value = { text: query };
|
||||||
} else if (this.isEnteredCustomAddress) {
|
} else if (this.isEnteredCustomAddress) {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
:multiple="false"
|
:multiple="false"
|
||||||
@tag="addPostcode"
|
@tag="addPostcode"
|
||||||
:tagPlaceholder="$t('create_postal_code')"
|
:tagPlaceholder="$t('create_postal_code')"
|
||||||
|
:loading="isLoading"
|
||||||
:options="cities">
|
:options="cities">
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
</div>
|
</div>
|
||||||
@ -48,15 +49,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VueMultiselect from 'vue-multiselect';
|
import VueMultiselect from 'vue-multiselect';
|
||||||
|
import { searchCities, fetchCities } from '../../api.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CitySelection',
|
name: 'CitySelection',
|
||||||
components: { VueMultiselect },
|
components: { VueMultiselect },
|
||||||
props: ['entity', 'focusOnAddress', 'updateMapCenter'],
|
props: ['entity', 'context', 'focusOnAddress', 'updateMapCenter'],
|
||||||
emits: ['getReferenceAddresses'],
|
emits: ['getReferenceAddresses'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
value: null
|
value: this.context.edit ? this.entity.address.postcode : null,
|
||||||
|
isLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -93,6 +96,15 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
console.log('writeNew.postcode', this.entity.selected.writeNew.postcode, 'in mounted');
|
console.log('writeNew.postcode', this.entity.selected.writeNew.postcode, 'in mounted');
|
||||||
|
if (this.context.edit) {
|
||||||
|
this.entity.selected.city = this.value;
|
||||||
|
this.entity.selected.postcode.name = this.value.name;
|
||||||
|
this.entity.selected.postcode.code = this.value.code;
|
||||||
|
this.$emit('getReferenceAddresses', this.value);
|
||||||
|
if (this.value.center) {
|
||||||
|
this.updateMapCenter(this.value.center);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
transName(value) {
|
transName(value) {
|
||||||
@ -105,7 +117,6 @@ export default {
|
|||||||
this.entity.selected.postcode.code = value.code;
|
this.entity.selected.postcode.code = value.code;
|
||||||
this.entity.selected.postcode.coordinates = value.center.coordinates;
|
this.entity.selected.postcode.coordinates = value.center.coordinates;
|
||||||
this.entity.selected.writeNew.postcode = false;
|
this.entity.selected.writeNew.postcode = false;
|
||||||
console.log('writeNew.postcode false, in selectCity');
|
|
||||||
this.$emit('getReferenceAddresses', value);
|
this.$emit('getReferenceAddresses', value);
|
||||||
this.focusOnAddress();
|
this.focusOnAddress();
|
||||||
if (value.center) {
|
if (value.center) {
|
||||||
@ -113,7 +124,33 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
listenInputSearch(query) {
|
listenInputSearch(query) {
|
||||||
//console.log('listenInputSearch', query, this.isCitySelectorOpen);
|
if (query.length > 2) {
|
||||||
|
this.isLoading = true;
|
||||||
|
searchCities(query, this.entity.selected.country).then(
|
||||||
|
cities => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.cities = cities.results.filter(c => c.origin !== 3); // filter out user-defined cities
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error); //TODO better error handling
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (query.length === 0) { // Fetch all cities when suppressing the query
|
||||||
|
this.isLoading = true;
|
||||||
|
fetchCities(this.entity.selected.country).then(
|
||||||
|
cities => new Promise((resolve, reject) => {
|
||||||
|
this.entity.loaded.cities = cities.results.filter(c => c.origin !== 3); // filter out user-defined cities
|
||||||
|
this.isLoading = false;
|
||||||
|
resolve();
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.isCitySelectorOpen) {
|
if (this.isCitySelectorOpen) {
|
||||||
this.$data.value = { text: query };
|
this.$data.value = { text: query };
|
||||||
} else if (this.isEnteredCustomCity) {
|
} else if (this.isEnteredCustomCity) {
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
<city-selection
|
<city-selection
|
||||||
v-bind:entity="entity"
|
v-bind:entity="entity"
|
||||||
|
v-bind:context="context"
|
||||||
v-bind:focusOnAddress="focusOnAddress"
|
v-bind:focusOnAddress="focusOnAddress"
|
||||||
v-bind:updateMapCenter="updateMapCenter"
|
v-bind:updateMapCenter="updateMapCenter"
|
||||||
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
|
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
|
||||||
@ -37,6 +38,7 @@
|
|||||||
|
|
||||||
<address-selection v-if="!isNoAddress"
|
<address-selection v-if="!isNoAddress"
|
||||||
v-bind:entity="entity"
|
v-bind:entity="entity"
|
||||||
|
v-bind:context="context"
|
||||||
v-bind:updateMapCenter="updateMapCenter">
|
v-bind:updateMapCenter="updateMapCenter">
|
||||||
</address-selection>
|
</address-selection>
|
||||||
|
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class AddressReferenceApiControllerTest extends WebTestCase
|
||||||
|
{
|
||||||
|
use PrepareClientTrait;
|
||||||
|
|
||||||
|
public function provideData()
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
/** @var EntityManagerInterface $em */
|
||||||
|
$em = self::$container->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
$postalCode = $em->createQueryBuilder()
|
||||||
|
->select('pc')
|
||||||
|
->from(PostalCode::class, 'pc')
|
||||||
|
->where('pc.origin = :origin')
|
||||||
|
->setParameter('origin', 0)
|
||||||
|
->setMaxResults(1)
|
||||||
|
->getQuery()
|
||||||
|
->getSingleResult();
|
||||||
|
|
||||||
|
yield [$postalCode->getId(), 'rue'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideData
|
||||||
|
*/
|
||||||
|
public function testSearch(int $postCodeId, string $pattern)
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
'GET',
|
||||||
|
"/api/1.0/main/address-reference/by-postal-code/{$postCodeId}/search.json",
|
||||||
|
['q' => $pattern]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Test\PrepareClientTrait;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
use function json_decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class PostalCodeApiControllerTest extends WebTestCase
|
||||||
|
{
|
||||||
|
use PrepareClientTrait;
|
||||||
|
|
||||||
|
public function testSearch()
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
'GET',
|
||||||
|
'/api/1.0/main/postal-code/search.json',
|
||||||
|
['q' => 'fontenay le comte']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
$data = json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertEquals('Fontenay Le Comte', $data['results'][0]['name']);
|
||||||
|
|
||||||
|
// test response with invalid search pattern
|
||||||
|
$client->request(
|
||||||
|
'GET',
|
||||||
|
'/api/1.0/main/postal-code/search.json',
|
||||||
|
['q' => '']
|
||||||
|
);
|
||||||
|
$this->assertResponseStatusCodeSame(400);
|
||||||
|
}
|
||||||
|
}
|
@ -360,6 +360,40 @@ paths:
|
|||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/main/address-reference/by-postal-code/{id}/search.json:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- address
|
||||||
|
- search
|
||||||
|
summary: Return a reference address by id
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The reference address id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
- name: q
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: The search pattern
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AddressReference'
|
||||||
|
404:
|
||||||
|
description: "not found"
|
||||||
|
401:
|
||||||
|
description: "Unauthorized"
|
||||||
|
400:
|
||||||
|
description: "Bad request"
|
||||||
/1.0/main/postal-code.json:
|
/1.0/main/postal-code.json:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -430,6 +464,37 @@ paths:
|
|||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/main/postal-code/search.json:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- address
|
||||||
|
- search
|
||||||
|
summary: Search a postal code
|
||||||
|
parameters:
|
||||||
|
- name: q
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: The search pattern
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: country
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The country id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PostalCode'
|
||||||
|
404:
|
||||||
|
description: "not found"
|
||||||
|
400:
|
||||||
|
description: "Bad Request"
|
||||||
|
|
||||||
/1.0/main/country.json:
|
/1.0/main/country.json:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20211125142016 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_address_reference_on_insert ON chill_main_address_reference');
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_address_reference_on_update ON chill_main_address_reference');
|
||||||
|
$this->addSql('DROP FUNCTION canonicalize_address_reference()');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_address_reference DROP COLUMN addressCanonical');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add a column "canonicalized" on chill_main_address_reference and add trigger and indexed on it';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_main_address_reference ADD addressCanonical TEXT DEFAULT \'\' NOT NULL');
|
||||||
|
|
||||||
|
$this->addSql('UPDATE chill_main_address_reference
|
||||||
|
SET addresscanonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
street ||
|
||||||
|
\' \' ||
|
||||||
|
streetnumber
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)');
|
||||||
|
|
||||||
|
$this->addSql('CREATE OR REPLACE FUNCTION public.canonicalize_address_reference() RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
NEW.addresscanonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
NEW.street ||
|
||||||
|
\' \' ||
|
||||||
|
NEW.streetnumber
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
return NEW;
|
||||||
|
END
|
||||||
|
$$');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_address_reference_on_insert
|
||||||
|
BEFORE INSERT
|
||||||
|
ON chill_main_address_reference
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_address_reference()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_address_reference_on_update
|
||||||
|
BEFORE UPDATE
|
||||||
|
ON chill_main_address_reference
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_address_reference()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE INDEX chill_internal_address_reference_canonicalized ON chill_main_address_reference USING GIST (postcode_id, addressCanonical gist_trgm_ops)');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20211125142017 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_postal_code_on_insert ON chill_main_postal_code');
|
||||||
|
$this->addSql('DROP TRIGGER canonicalize_postal_code_on_update ON chill_main_postal_code');
|
||||||
|
$this->addSql('DROP FUNCTION canonicalize_postal_code()');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_postal_code DROP COLUMN canonical');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add a column "canonicalized" on postal code';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_main_postal_code ADD canonical TEXT DEFAULT \'\' NOT NULL');
|
||||||
|
|
||||||
|
$this->addSql('UPDATE chill_main_postal_code
|
||||||
|
SET canonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
code ||
|
||||||
|
\' \' ||
|
||||||
|
label
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)');
|
||||||
|
|
||||||
|
$this->addSql('CREATE OR REPLACE FUNCTION public.canonicalize_postal_code() RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
NEW.canonical =
|
||||||
|
TRIM(
|
||||||
|
UNACCENT(
|
||||||
|
LOWER(
|
||||||
|
NEW.code ||
|
||||||
|
\' \' ||
|
||||||
|
NEW.label
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
return NEW;
|
||||||
|
END
|
||||||
|
$$');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_postal_code_on_insert
|
||||||
|
BEFORE INSERT
|
||||||
|
ON chill_main_postal_code
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_postal_code()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE TRIGGER canonicalize_postal_code_on_update
|
||||||
|
BEFORE UPDATE
|
||||||
|
ON chill_main_postal_code
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE procedure canonicalize_postal_code()');
|
||||||
|
|
||||||
|
$this->addSql('CREATE INDEX chill_internal_postal_code_canonicalized ON chill_main_postal_code USING GIST (canonical gist_trgm_ops) WHERE origin = 0');
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,6 @@ namespace Chill\PersonBundle\Form;
|
|||||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
class HouseholdMemberType extends AbstractType
|
class HouseholdMemberType extends AbstractType
|
||||||
@ -26,17 +25,6 @@ class HouseholdMemberType extends AbstractType
|
|||||||
'label' => 'household.Start date',
|
'label' => 'household.Start date',
|
||||||
'input' => 'datetime_immutable',
|
'input' => 'datetime_immutable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($options['data']->getPosition()->isAllowHolder()) {
|
|
||||||
$builder
|
|
||||||
->add('holder', ChoiceType::class, [
|
|
||||||
'label' => 'household.holder',
|
|
||||||
'choices' => [
|
|
||||||
'household.is holder' => true,
|
|
||||||
'household.is not holder' => false,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$builder
|
$builder
|
||||||
->add('comment', ChillTextareaType::class, [
|
->add('comment', ChillTextareaType::class, [
|
||||||
'label' => 'household.Comment',
|
'label' => 'household.Comment',
|
||||||
|
@ -1,51 +1,6 @@
|
|||||||
/// AccompanyingCourse Work list Page
|
/// AccompanyingCourse Work list Page
|
||||||
div.accompanying_course_work-list {
|
div.accompanying_course_work-list {
|
||||||
|
|
||||||
div.timeline {
|
|
||||||
width: 100%;
|
|
||||||
ul {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
padding: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
|
|
||||||
> li {
|
|
||||||
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
|
|
||||||
|
|
||||||
div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&.date {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
&.label {
|
|
||||||
border-top: 3px solid $chill-green;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
top: -9px;
|
|
||||||
|
|
||||||
background-color: $white;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 2px solid $chill-green;
|
|
||||||
}
|
|
||||||
&.no-label:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.objective_results {
|
div.objective_results {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -69,8 +24,10 @@ div.accompanying_course_work-list {
|
|||||||
//&:nth-child(even) { background-color: $chill-llight-gray; }
|
//&:nth-child(even) { background-color: $chill-llight-gray; }
|
||||||
&.without-objectives {}
|
&.without-objectives {}
|
||||||
&.with-objectives {}
|
&.with-objectives {}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.objective_results,
|
||||||
|
div.evaluations {
|
||||||
h4.title_label {
|
h4.title_label {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0.4em 0;
|
margin: 0.4em 0;
|
||||||
|
@ -39,69 +39,15 @@ span.fa-holder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BADGE_TITLE
|
* DASHBOARDS
|
||||||
* Display Title like a badge (with background-colored label)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
h2.badge-title {
|
/// Theses links apply on dashboards as parent tag.
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
width: 100%;
|
|
||||||
color: $dark;
|
|
||||||
|
|
||||||
span.title_label {
|
|
||||||
border-radius: 0.35rem 0 0 0.35rem;
|
|
||||||
color: $white;
|
|
||||||
font-size: 80%;
|
|
||||||
padding: 0.5em;
|
|
||||||
padding-right: 0;
|
|
||||||
h3 {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
//position: relative;
|
|
||||||
span {
|
|
||||||
display: none;
|
|
||||||
//position: absolute;
|
|
||||||
//top: 0;
|
|
||||||
//left: 0;
|
|
||||||
//transform: rotate(270deg);
|
|
||||||
//transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
span.title_action {
|
|
||||||
flex-grow: 1;
|
|
||||||
margin: 0 0 0 auto;
|
|
||||||
border-radius: 0 0.35rem 0.35rem 0;
|
|
||||||
background-color: $chill-llight-gray;
|
|
||||||
padding: 0.2em 1em;
|
|
||||||
|
|
||||||
ul.small_in_title {
|
|
||||||
margin: 0;
|
|
||||||
//margin-top: 0.5em;
|
|
||||||
font-size: 70%;
|
|
||||||
padding-left: 1rem;
|
|
||||||
&.evaluations {
|
|
||||||
@include list_marker_triangle($orange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2
|
|
||||||
@include media-breakpoint-only(sm) {
|
|
||||||
columns: 2; -webkit-columns: 2; -moz-columns: 2;
|
|
||||||
}
|
|
||||||
@include media-breakpoint-up(lg) {
|
|
||||||
columns: 2; -webkit-columns: 2; -moz-columns: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Theses links apply on badge as parent tag.
|
|
||||||
/// They don't look like button, picto or simple text links
|
/// They don't look like button, picto or simple text links
|
||||||
a.badge-link {
|
a.dashboard-link {
|
||||||
color: unset;
|
color: unset;
|
||||||
text-decoration: unset;
|
text-decoration: unset;
|
||||||
& > h2.badge-title {
|
& > div.dashboard {
|
||||||
&:hover {
|
&:hover {
|
||||||
//box-shadow: 0 0 7px 0 $chill-gray;
|
//box-shadow: 0 0 7px 0 $chill-gray;
|
||||||
//opacity: 0.8;
|
//opacity: 0.8;
|
||||||
@ -114,21 +60,80 @@ a.badge-link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// badge_title in AccompanyingCourse Work list Page
|
div.dashboard {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
span.like-h3 {
|
||||||
|
color: #334d5c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div.dashboard,
|
||||||
|
h2.badge-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
color: $dark;
|
||||||
|
span.title_label {
|
||||||
|
color: $white;
|
||||||
|
font-size: 80%;
|
||||||
|
padding: 0.5em;
|
||||||
|
padding-right: 0;
|
||||||
|
border-radius: 0.35rem 0 0 0.35rem;
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.title_action {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0 0 0 auto;
|
||||||
|
background-color: $chill-llight-gray;
|
||||||
|
padding: 0.2em 1em;
|
||||||
|
border-radius: 0 0.35rem 0.35rem 0;
|
||||||
|
|
||||||
|
ul.small_in_title {
|
||||||
|
font-size: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.small_in_title {
|
||||||
|
margin: 0;
|
||||||
|
//margin-top: 0.5em;
|
||||||
|
padding-left: 1rem;
|
||||||
|
&.evaluations {
|
||||||
|
@include list_marker_triangle($orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2
|
||||||
|
@include media-breakpoint-only(sm) {
|
||||||
|
columns: 2; -webkit-columns: 2; -moz-columns: 2;
|
||||||
|
}
|
||||||
|
@include media-breakpoint-up(lg) {
|
||||||
|
columns: 2; -webkit-columns: 2; -moz-columns: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// dashboard_like_badge in AccompanyingCourse Work list Page
|
||||||
div.accompanying_course_work-list {
|
div.accompanying_course_work-list {
|
||||||
|
div.dashboard,
|
||||||
h2.badge-title {
|
h2.badge-title {
|
||||||
span.title_label {
|
span.title_label {
|
||||||
// Calculate same color then border:groove
|
// Calculate same color then border:groove
|
||||||
background-color: shade-color($social-action-color, 34%);
|
background-color: shade-color($social-action-color, 34%);
|
||||||
}
|
}
|
||||||
span.title_action {
|
span.title_action {
|
||||||
@include badge_title($social-action-color);
|
@include dashboard_like_badge($social-action-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// badge_title in Activities on resume page
|
/// dashboard_like_badge in Activities on resume page
|
||||||
div.activity-list {
|
div.activity-list {
|
||||||
|
div.dashboard,
|
||||||
h2.badge-title {
|
h2.badge-title {
|
||||||
span.title_label {
|
span.title_label {
|
||||||
// Calculate same color then border:groove
|
// Calculate same color then border:groove
|
||||||
@ -138,7 +143,7 @@ div.activity-list {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
span.title_action {
|
span.title_action {
|
||||||
@include badge_title($activity-color);
|
@include dashboard_like_badge($activity-color);
|
||||||
}
|
}
|
||||||
span.title_label {
|
span.title_label {
|
||||||
div.duration {
|
div.duration {
|
||||||
|
@ -18,12 +18,6 @@ div.accompanyingcourse-list {
|
|||||||
//&:nth-child(2) { flex-direction: row; }
|
//&:nth-child(2) { flex-direction: row; }
|
||||||
//&:last-child { flex-direction: column; }
|
//&:last-child { flex-direction: column; }
|
||||||
}
|
}
|
||||||
div.title h3 {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 100%;
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
}
|
|
||||||
div.list {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search Page (list_with_period.html.twig)
|
/// Search Page (list_with_period.html.twig)
|
||||||
|
@ -27,11 +27,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Generic mixin for titles like badge
|
/// Mixin for dashboards (with design like badge_social)
|
||||||
// define visual badge used in title area
|
|
||||||
///
|
///
|
||||||
|
|
||||||
@mixin badge_title($color) {
|
@mixin dashboard_like_badge($color) {
|
||||||
@include chill_badge($color);
|
@include chill_badge($color);
|
||||||
&:before {
|
&:before {
|
||||||
margin: 0 0.3em 0 -1.05em;
|
margin: 0 0.3em 0 -1.05em;
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 text-center">{{ $t('visgraph.between') }}<br>{{ $t('visgraph.and') }}</div>
|
<div class="col-12 text-center">{{ $t('visgraph.between') }}<br>{{ $t('visgraph.and') }}</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
<small>{{ getPersonAge(modal.data.from) }}</small>
|
||||||
<h4>{{ getPerson(modal.data.from).text }}</h4>
|
<h4>{{ getPerson(modal.data.from).text }}</h4>
|
||||||
<p class="text-start" v-if="relation && relation.title">
|
<p class="text-start" v-if="relation && relation.title">
|
||||||
<span v-if="reverse">
|
<span v-if="reverse">
|
||||||
@ -64,6 +65,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-end">
|
<div class="col text-end">
|
||||||
|
<small>{{ getPersonAge(modal.data.to) }}</small>
|
||||||
<h4>{{ getPerson(modal.data.to).text }}</h4>
|
<h4>{{ getPerson(modal.data.to).text }}</h4>
|
||||||
<p class="text-end" v-if="relation && relation.title">
|
<p class="text-end" v-if="relation && relation.title">
|
||||||
<span v-if="reverse">
|
<span v-if="reverse">
|
||||||
@ -119,8 +121,9 @@ 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, patchRelationship, deleteRelationship } from "./api";
|
import { getRelationsList, postRelationship, patchRelationship, deleteRelationship } from "./api"
|
||||||
import { splitId } from "./vis-network";
|
import { splitId, getAge } from "./vis-network"
|
||||||
|
import { visMessages } from "./i18n";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
@ -128,6 +131,7 @@ export default {
|
|||||||
Modal,
|
Modal,
|
||||||
VueMultiselect
|
VueMultiselect
|
||||||
},
|
},
|
||||||
|
props: ['household_id'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
container: '',
|
container: '',
|
||||||
@ -152,7 +156,9 @@ export default {
|
|||||||
class: null,
|
class: null,
|
||||||
text: null
|
text: null
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
canvas: null,
|
||||||
|
link: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -164,7 +170,7 @@ export default {
|
|||||||
]),
|
]),
|
||||||
|
|
||||||
visgraph_data() {
|
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 {
|
return {
|
||||||
nodes: this.nodes,
|
nodes: this.nodes,
|
||||||
edges: this.edges
|
edges: this.edges
|
||||||
@ -172,12 +178,12 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refreshNetwork() {
|
refreshNetwork() {
|
||||||
console.log('--- refresh network')
|
//console.log('--- refresh network')
|
||||||
window.network.setData(this.visgraph_data)
|
window.network.setData(this.visgraph_data)
|
||||||
},
|
},
|
||||||
|
|
||||||
legendLayers() {
|
legendLayers() {
|
||||||
console.log('--- refresh legend and rebuild checked Layers')
|
//console.log('--- refresh legend and rebuild checked Layers')
|
||||||
this.checkedLayers = []
|
this.checkedLayers = []
|
||||||
let layersDisplayed = [
|
let layersDisplayed = [
|
||||||
...this.nodes.filter(n => n.id.startsWith('household')),
|
...this.nodes.filter(n => n.id.startsWith('household')),
|
||||||
@ -193,7 +199,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
checkedLayers() { // required to refresh data checkedLayers
|
checkedLayers() { // required to refresh data checkedLayers
|
||||||
console.log('--- checkedLayers')
|
//console.log('--- checkedLayers')
|
||||||
return this.checkedLayers
|
return this.checkedLayers
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -218,7 +224,7 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
updateHack(newValue, oldValue) {
|
updateHack(newValue, oldValue) {
|
||||||
console.log(`--- updateHack ${oldValue} <> ${newValue}`)
|
//console.log(`--- updateHack ${oldValue} <> ${newValue}`)
|
||||||
if (oldValue !== newValue) {
|
if (oldValue !== newValue) {
|
||||||
this.forceUpdateComponent()
|
this.forceUpdateComponent()
|
||||||
}
|
}
|
||||||
@ -229,6 +235,9 @@ export default {
|
|||||||
this.initGraph()
|
this.initGraph()
|
||||||
this.listenOnGraph()
|
this.listenOnGraph()
|
||||||
this.getRelationsList()
|
this.getRelationsList()
|
||||||
|
|
||||||
|
this.canvas = document.getElementById('visgraph').querySelector('canvas')
|
||||||
|
this.link = document.getElementById('exportCanvasBtn')
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
@ -255,27 +264,27 @@ export default {
|
|||||||
|
|
||||||
case 'person':
|
case 'person':
|
||||||
let person = this.nodes.filter(n => n.id === node)[0]
|
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 (this.listenPersonFlag === 'normal') {
|
||||||
if (person.folded === true) {
|
if (person.folded === true) {
|
||||||
console.log(' @@> expand mode event')
|
//console.log(' @@> expand mode event')
|
||||||
this.$store.commit('unfoldPerson', person)
|
this.$store.commit('unfoldPerson', person)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(' @@> create link mode event')
|
//console.log(' @@> create link mode event')
|
||||||
this.listenStepsToAddRelationship(person)
|
this.listenStepsToAddRelationship(person)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'household':
|
case 'household':
|
||||||
let household = this.nodes.filter(n => n.id === node)[0]
|
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)
|
this.$store.dispatch('unfoldPersonsByHousehold', household)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'accompanying_period':
|
case 'accompanying_period':
|
||||||
let course = this.nodes.filter(n => n.id === node)[0]
|
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)
|
this.$store.dispatch('unfoldPersonsByCourse', course)
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -290,7 +299,7 @@ export default {
|
|||||||
}
|
}
|
||||||
let link = data.edges[0]
|
let link = data.edges[0]
|
||||||
let linkType = splitId(link, 'link')
|
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')) {
|
if (linkType.startsWith('relationship')) {
|
||||||
//console.log('linkType relationship')
|
//console.log('linkType relationship')
|
||||||
@ -314,7 +323,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
listenStepsToAddRelationship(person) {
|
listenStepsToAddRelationship(person) {
|
||||||
console.log(' @@> listenStep', this.listenPersonFlag)
|
//console.log(' @@> listenStep', this.listenPersonFlag)
|
||||||
if (this.listenPersonFlag === 'step2') {
|
if (this.listenPersonFlag === 'step2') {
|
||||||
//console.log(' @@> person 2', person)
|
//console.log(' @@> person 2', person)
|
||||||
this.newEdgeData.to = person.id
|
this.newEdgeData.to = person.id
|
||||||
@ -333,7 +342,7 @@ export default {
|
|||||||
/// control Layers
|
/// control Layers
|
||||||
toggleLayer(value) {
|
toggleLayer(value) {
|
||||||
let id = value.target.value
|
let id = value.target.value
|
||||||
console.log('@@@@@@ toggle Layer', id)
|
//console.log('@@@@@@ toggle Layer', id)
|
||||||
this.forceUpdateComponent()
|
this.forceUpdateComponent()
|
||||||
if (this.checkedLayers.includes(id)) {
|
if (this.checkedLayers.includes(id)) {
|
||||||
this.removeLayer(id)
|
this.removeLayer(id)
|
||||||
@ -382,7 +391,7 @@ export default {
|
|||||||
title: null,
|
title: null,
|
||||||
button: { class: null, text: null, }
|
button: { class: null, text: null, }
|
||||||
}
|
}
|
||||||
console.log('==- reset Form', this.modal.data)
|
//console.log('==- reset Form', this.modal.data)
|
||||||
},
|
},
|
||||||
getRelationsList() {
|
getRelationsList() {
|
||||||
//console.log('fetch relationsList')
|
//console.log('fetch relationsList')
|
||||||
@ -400,12 +409,16 @@ export default {
|
|||||||
let person = this.persons.filter(p => p.id === id)
|
let person = this.persons.filter(p => p.id === id)
|
||||||
return person[0]
|
return person[0]
|
||||||
},
|
},
|
||||||
|
getPersonAge(id) {
|
||||||
|
let person = this.getPerson(id)
|
||||||
|
return getAge(person)
|
||||||
|
},
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
createRelationship() {
|
createRelationship() {
|
||||||
this.displayHelpMessage = true
|
this.displayHelpMessage = true
|
||||||
this.listenPersonFlag = 'step1' // toggle listener in create link mode
|
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() {
|
dropRelationship() {
|
||||||
//console.log('delete', this.modal.data)
|
//console.log('delete', this.modal.data)
|
||||||
@ -417,13 +430,13 @@ export default {
|
|||||||
this.forceUpdateComponent()
|
this.forceUpdateComponent()
|
||||||
},
|
},
|
||||||
submitRelationship() {
|
submitRelationship() {
|
||||||
console.log('submitRelationship', this.modal.action)
|
//console.log('submitRelationship', this.modal.action)
|
||||||
switch (this.modal.action) {
|
switch (this.modal.action) {
|
||||||
|
|
||||||
case 'create':
|
case 'create':
|
||||||
return postRelationship(this.modal.data)
|
return postRelationship(this.modal.data)
|
||||||
.then(relationship => new Promise(resolve => {
|
.then(relationship => new Promise(resolve => {
|
||||||
console.log('post relationship response', relationship)
|
//console.log('post relationship response', relationship)
|
||||||
this.$store.dispatch('addLinkFromRelationship', relationship)
|
this.$store.dispatch('addLinkFromRelationship', relationship)
|
||||||
this.modal.showModal = false
|
this.modal.showModal = false
|
||||||
this.resetForm()
|
this.resetForm()
|
||||||
@ -435,7 +448,7 @@ export default {
|
|||||||
case 'edit':
|
case 'edit':
|
||||||
return patchRelationship(this.modal.data)
|
return patchRelationship(this.modal.data)
|
||||||
.then(relationship => new Promise(resolve => {
|
.then(relationship => new Promise(resolve => {
|
||||||
console.log('patch relationship response', relationship)
|
//console.log('patch relationship response', relationship)
|
||||||
this.$store.commit('updateLink', relationship)
|
this.$store.commit('updateLink', relationship)
|
||||||
this.modal.showModal = false
|
this.modal.showModal = false
|
||||||
this.resetForm()
|
this.resetForm()
|
||||||
@ -450,39 +463,44 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// export image
|
// export image
|
||||||
exportCanvasAsImage() {
|
async exportCanvasAsImage() {
|
||||||
const canvas = document.getElementById('visgraph')
|
|
||||||
.querySelector('canvas')
|
|
||||||
console.log(canvas)
|
|
||||||
|
|
||||||
let link = document.getElementById('exportCanvasBtn')
|
let
|
||||||
link.download = "filiation.png"
|
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 => {
|
// resolve toBlob in a Promise
|
||||||
console.log(blob)
|
const getCanvasBlob = canvas => new Promise(resolve => {
|
||||||
link.href = URL.createObjectURL(blob)
|
canvas.toBlob(blob => resolve(blob), mime, quality)
|
||||||
}, 'image/png')
|
})
|
||||||
|
|
||||||
/*
|
// build image from new temporary canvas
|
||||||
TODO improve feature
|
let tmpCanvas = document.createElement('canvas')
|
||||||
|
tmpCanvas.width = this.canvas.width
|
||||||
|
tmpCanvas.height = this.canvas.height
|
||||||
|
|
||||||
// 1. fonctionne, mais pas de contrôle sur le nom
|
let ctx = tmpCanvas.getContext('2d')
|
||||||
if (canvas && canvas.getContext('2d')) {
|
ctx.beginPath()
|
||||||
let img = canvas.toDataURL('image/png;base64;')
|
ctx.fillStyle = '#fff'
|
||||||
img = img.replace('image/png','image/octet-stream')
|
ctx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height);
|
||||||
window.open(img, '', 'width=1000, height=1000')
|
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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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')
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -24,6 +24,7 @@ const visMessages = {
|
|||||||
refresh: "Rafraîchir",
|
refresh: "Rafraîchir",
|
||||||
screenshot: "Prendre une photo",
|
screenshot: "Prendre une photo",
|
||||||
choose_relation: "Choisissez le lien de parenté",
|
choose_relation: "Choisissez le lien de parenté",
|
||||||
|
relationship_household: "Filiation du ménage",
|
||||||
},
|
},
|
||||||
edit: 'Éditer',
|
edit: 'Éditer',
|
||||||
del: 'Supprimer',
|
del: 'Supprimer',
|
||||||
|
@ -16,7 +16,12 @@ persons.forEach(person => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app></app>`
|
template: `<app :household_id="this.household_id"></app>`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
household_id: JSON.parse(container.dataset.householdId)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.use(store)
|
.use(store)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
|
@ -112,7 +112,7 @@ const store = createStore({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
//console.log('array', array.map(item => item.person.id))
|
//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
|
return group
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -120,13 +120,17 @@ const store = createStore({
|
|||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
addPerson(state, [person, options]) {
|
addPerson(state, [person, options]) {
|
||||||
|
let age = getAge(person)
|
||||||
|
age = (age === '')? '' : ' - ' + age
|
||||||
|
|
||||||
let debug = ''
|
let debug = ''
|
||||||
/// Debug mode: uncomment to display person_id on visgraph
|
/// Debug mode: uncomment to display person_id on visgraph
|
||||||
//debug = `\nid ${person.id}`
|
//debug = `\nid ${person.id}`
|
||||||
|
|
||||||
person.group = person.type
|
person.group = person.type
|
||||||
person._id = person.id
|
person._id = person.id
|
||||||
person.id = `person_${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
|
person.folded = false
|
||||||
// folded is used for missing persons
|
// folded is used for missing persons
|
||||||
if (options.folded) {
|
if (options.folded) {
|
||||||
@ -161,7 +165,7 @@ const store = createStore({
|
|||||||
state.links.push(link)
|
state.links.push(link)
|
||||||
},
|
},
|
||||||
updateLink(state, link) {
|
updateLink(state, link) {
|
||||||
console.log('updateLink', link)
|
//console.log('updateLink', link)
|
||||||
let link_ = {
|
let link_ = {
|
||||||
from: `person_${link.fromPerson.id}`,
|
from: `person_${link.fromPerson.id}`,
|
||||||
to: `person_${link.toPerson.id}`,
|
to: `person_${link.toPerson.id}`,
|
||||||
@ -264,7 +268,7 @@ const store = createStore({
|
|||||||
fetchInfoForPerson({ dispatch }, person) {
|
fetchInfoForPerson({ dispatch }, person) {
|
||||||
// TODO enfants hors ménages
|
// TODO enfants hors ménages
|
||||||
// example: household 61
|
// 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) {
|
if (null !== person.current_household_id) {
|
||||||
dispatch('fetchHouseholdForPerson', person)
|
dispatch('fetchHouseholdForPerson', person)
|
||||||
}
|
}
|
||||||
@ -305,15 +309,16 @@ const store = createStore({
|
|||||||
*/
|
*/
|
||||||
addLinkFromPersonsToHousehold({ commit, getters, dispatch }, household) {
|
addLinkFromPersonsToHousehold({ commit, getters, dispatch }, household) {
|
||||||
let members = getters.getMembersByHousehold(household.id)
|
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 => {
|
members.forEach(m => {
|
||||||
commit('addLink', {
|
commit('addLink', {
|
||||||
from: `${m.person.type}_${m.person.id}`,
|
from: `${m.person.type}_${m.person.id}`,
|
||||||
to: `household_${m.person.current_household_id}`,
|
to: `${household.id}`,
|
||||||
id: `household_${m.person.current_household_id}-person_${m.person.id}`,
|
id: `${household.id}-person_${m.person.id}`,
|
||||||
arrows: 'from',
|
arrows: 'from',
|
||||||
color: 'pink',
|
color: 'pink',
|
||||||
font: { color: '#D04A60' },
|
font: { color: '#D04A60' },
|
||||||
|
dashes: (getHouseholdWidth(m) === 1)? [0,4] : false, //edge style: [dash, gap, dash, gap]
|
||||||
label: getHouseholdLabel(m),
|
label: getHouseholdLabel(m),
|
||||||
width: getHouseholdWidth(m),
|
width: getHouseholdWidth(m),
|
||||||
})
|
})
|
||||||
@ -362,7 +367,7 @@ const store = createStore({
|
|||||||
*/
|
*/
|
||||||
addLinkFromPersonsToCourse({ commit, getters, dispatch }, course) {
|
addLinkFromPersonsToCourse({ commit, getters, dispatch }, course) {
|
||||||
const participations = getters.getParticipationsByCourse(course.id)
|
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 => {
|
participations.forEach(p => {
|
||||||
//console.log(p.person.id)
|
//console.log(p.person.id)
|
||||||
commit('addLink', {
|
commit('addLink', {
|
||||||
@ -445,7 +450,7 @@ const store = createStore({
|
|||||||
* @param array
|
* @param array
|
||||||
*/
|
*/
|
||||||
addMissingPerson({ commit, getters, dispatch }, [person, parent]) {
|
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('markPersonLoaded', person.id)
|
||||||
commit('addPerson', [person, { folded: true }])
|
commit('addPerson', [person, { folded: true }])
|
||||||
if (getters.isExcludedNode(parent.id)) {
|
if (getters.isExcludedNode(parent.id)) {
|
||||||
@ -467,7 +472,7 @@ const store = createStore({
|
|||||||
getters.getPersonsGroup(participations)
|
getters.getPersonsGroup(participations)
|
||||||
.forEach(person => {
|
.forEach(person => {
|
||||||
if (person.folded === true) {
|
if (person.folded === true) {
|
||||||
console.log('-=. unfold and expand person', person.id)
|
//console.log('-=. unfold and expand person', person.id)
|
||||||
commit('unfoldPerson', person)
|
commit('unfoldPerson', person)
|
||||||
dispatch('fetchInfoForPerson', person)
|
dispatch('fetchInfoForPerson', person)
|
||||||
}
|
}
|
||||||
@ -485,7 +490,7 @@ const store = createStore({
|
|||||||
getters.getPersonsGroup(members)
|
getters.getPersonsGroup(members)
|
||||||
.forEach(person => {
|
.forEach(person => {
|
||||||
if (person.folded === true) {
|
if (person.folded === true) {
|
||||||
console.log('-=. unfold and expand person', person.id)
|
//console.log('-=. unfold and expand person', person.id)
|
||||||
commit('unfoldPerson', person)
|
commit('unfoldPerson', person)
|
||||||
dispatch('fetchInfoForPerson', person)
|
dispatch('fetchInfoForPerson', person)
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,12 @@ window.options = {
|
|||||||
locale: 'fr',
|
locale: 'fr',
|
||||||
locales: visMessages,
|
locales: visMessages,
|
||||||
/*
|
/*
|
||||||
|
*/
|
||||||
configure: {
|
configure: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
filter: 'nodes,edges',
|
filter: 'physics',
|
||||||
//container: undefined,
|
|
||||||
showButton: true
|
showButton: true
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
physics: {
|
physics: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
barnesHut: {
|
barnesHut: {
|
||||||
@ -37,8 +36,8 @@ window.options = {
|
|||||||
centralGravity: 0.01,
|
centralGravity: 0.01,
|
||||||
springLength: 100,
|
springLength: 100,
|
||||||
springConstant: 0.08,
|
springConstant: 0.08,
|
||||||
damping: 0.4,
|
damping: 0.75,
|
||||||
avoidOverlap: 0
|
avoidOverlap: 0.00
|
||||||
},
|
},
|
||||||
repulsion: {
|
repulsion: {
|
||||||
centralGravity: 0.2,
|
centralGravity: 0.2,
|
||||||
@ -159,17 +158,21 @@ const getGender = (gender) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO Repeat getAge() in PersonRenderBox.vue
|
* TODO only one abstract function (-> getAge() is repeated in PersonRenderBox.vue)
|
||||||
* @param birthdate
|
* @param person
|
||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
*/
|
*/
|
||||||
const getAge = (birthdate) => {
|
const getAge = (person) => {
|
||||||
if (null === birthdate) {
|
if (person.birthdate) {
|
||||||
return null
|
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
|
||||||
}
|
}
|
||||||
const birthday = new Date(birthdate.datetime)
|
let now = new Date()
|
||||||
const now = new Date()
|
return (now.getFullYear() - birthdate.getFullYear()) + visMessages.fr.visgraph.years
|
||||||
return (now.getFullYear() - birthday.getFullYear()) + ' '+ visMessages.fr.visgraph.years
|
}
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -192,6 +192,7 @@ export default {
|
|||||||
return `/fr/person/${this.person.id}/general`;
|
return `/fr/person/${this.person.id}/general`;
|
||||||
},
|
},
|
||||||
getAge: function() {
|
getAge: function() {
|
||||||
|
// TODO only one abstract function
|
||||||
if(this.person.birthdate && !this.person.deathdate){
|
if(this.person.birthdate && !this.person.deathdate){
|
||||||
const birthday = new Date(this.person.birthdate.datetime)
|
const birthday = new Date(this.person.birthdate.datetime)
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="social-actions my-4">
|
<div class="social-actions my-4">
|
||||||
<h2 class="mb-3 d-none">{{ 'Last social actions'|trans }}</h2>
|
<h2 class="mb-3 visually-hidden">{{ 'Last social actions'|trans }}</h2>
|
||||||
{% include 'ChillPersonBundle:AccompanyingCourseWork:list_recent_by_accompanying_period.html.twig' with {'buttonText': false } %}
|
{% include 'ChillPersonBundle:AccompanyingCourseWork:list_recent_by_accompanying_period.html.twig' with {'buttonText': false } %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -121,7 +121,7 @@
|
|||||||
{% set accompanying_course_id = accompanyingCourse.id %}
|
{% set accompanying_course_id = accompanyingCourse.id %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h2 class="mb-3 d-none">{{ 'Last activities' |trans }}</h2>
|
<h2 class="mb-3 visually-hidden">{{ 'Last activities' |trans }}</h2>
|
||||||
{% include 'ChillActivityBundle:Activity:list_recent.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %}
|
{% include 'ChillActivityBundle:Activity:list_recent.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<div class="accompanying_course_work-list">
|
<div class="accompanying_course_work-list">
|
||||||
<h2 class="badge-title">
|
<h2 class="badge-title">
|
||||||
<span class="title_label">{{ 'accompanying_course_work.action'|trans }}</span>
|
<span class="title_label"></span>
|
||||||
<span class="title_action">{{ work.socialAction|chill_entity_render_string }}</span>
|
<span class="title_action">{{ work.socialAction|chill_entity_render_string }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
@ -7,7 +7,217 @@
|
|||||||
|
|
||||||
<h1>{{ block('title') }}</h1>
|
<h1>{{ block('title') }}</h1>
|
||||||
|
|
||||||
{% include 'ChillPersonBundle:AccompanyingCourseWork:list_by_accompanying_period.html.twig' %}
|
{% if works|length == 0 %}
|
||||||
|
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}
|
||||||
|
<a class="btn btn-sm btn-create"
|
||||||
|
title="{{ 'accompanying_course_work.create'|trans }}"
|
||||||
|
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_new', { 'id': accompanyingCourse.id }) }}"
|
||||||
|
></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-table accompanying_course_work-list">
|
||||||
|
{% for w in works %}
|
||||||
|
<div class="item-bloc">
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<h2 class="badge-title">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">{{ w.socialAction|chill_entity_render_string }}
|
||||||
|
|
||||||
|
<ul class="small_in_title columns mt-1">
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ w.startDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% if w.endDate %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ w.endDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="wrap-list">
|
||||||
|
|
||||||
|
{% if w.createdBy %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Referrer'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item">
|
||||||
|
{{ w.createdBy.usernameCanonical|chill_entity_render_string|capitalize }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if w.persons -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Persons in accompanying course'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% for p in w.persons %}
|
||||||
|
<span class="wl-item badge-person">
|
||||||
|
{{ p|chill_entity_render_box({
|
||||||
|
'render': 'raw',
|
||||||
|
'addAltNames': false
|
||||||
|
}) }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if w.handlingThierParty -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Thirdparty handling'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<span class="wl-item badge-thirdparty">
|
||||||
|
{{ w.handlingThierParty|chill_entity_render_box({
|
||||||
|
'render': 'raw',
|
||||||
|
'addAltNames': false
|
||||||
|
}) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if w.socialAction.issue -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Social issue'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item social-issues">
|
||||||
|
{{ w.socialAction.issue|chill_entity_render_box }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if w.accompanyingPeriodWorkEvaluations|length > 0 %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'accompanying_course_work.evaluations'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<ul class="small_in_title evaluations mt-1">
|
||||||
|
{% for e in w.accompanyingPeriodWorkEvaluations %}
|
||||||
|
<li>
|
||||||
|
{{ e.evaluation.title|localize_translatable_string }}
|
||||||
|
|
||||||
|
<ul class="columns">
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ e.startDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% if e.endDate %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ e.endDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if e.maxDate %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ e.maxDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if e.warningInterval and e.warningInterval.d > 0 %}
|
||||||
|
<li>
|
||||||
|
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
|
||||||
|
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row column">
|
||||||
|
{# SEULEMENT SI DÉTAILLÉ
|
||||||
|
{% if w.results|length > 0 %}
|
||||||
|
<div class="objective_results without-objectives">
|
||||||
|
<div class="objective">
|
||||||
|
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
|
||||||
|
<p class="chill-no-data-statement">{{ 'accompanying_course_work.results without objective'|trans }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="results">
|
||||||
|
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
|
||||||
|
<ul class="result_list">
|
||||||
|
{% for r in w.results %}
|
||||||
|
<li>{{ r.title|localize_translatable_string }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if w.goals|length > 0 %}
|
||||||
|
{% for g in w.goals %}
|
||||||
|
<div class="objective_results with-objectives">
|
||||||
|
<div class="objective">
|
||||||
|
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
|
||||||
|
<ul class="goal_title"><li>{{ g.goal.title|localize_translatable_string }}</li></ul>
|
||||||
|
</div>
|
||||||
|
<div class="results">
|
||||||
|
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
|
||||||
|
{% if g.results|length == 0 %}
|
||||||
|
<p class="chill-no-data-statement">{{ 'accompanying_course_work.no_results'|trans }}</p>
|
||||||
|
{% else %}
|
||||||
|
<ul class="result_list">
|
||||||
|
{% for r in g.results %}
|
||||||
|
<li>{{ r.title|localize_translatable_string }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
#}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="updatedBy">
|
||||||
|
{{ 'Last updated by'|trans}} <b>{{ w.updatedBy|chill_entity_render_box }}</b>,<br>
|
||||||
|
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
|
||||||
|
</div>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
||||||
|
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
|
||||||
|
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
<li>
|
<li>
|
||||||
|
@ -1,118 +0,0 @@
|
|||||||
{% if works|length == 0 %}
|
|
||||||
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}
|
|
||||||
<a class="btn btn-sm btn-create"
|
|
||||||
href="" title="TODO"></a>{# TODO link #}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="flex-table accompanying_course_work-list">
|
|
||||||
{% for w in works %}
|
|
||||||
<div class="item-bloc">
|
|
||||||
<div class="item-row">
|
|
||||||
|
|
||||||
<h2 class="badge-title">
|
|
||||||
<span class="title_label">{{ 'accompanying_course_work.action'|trans }}</span>
|
|
||||||
<span class="title_action">{{ w.socialAction|chill_entity_render_string }}</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="item-row separator">
|
|
||||||
|
|
||||||
<div class="timeline">
|
|
||||||
<ul>
|
|
||||||
<li class="completed">
|
|
||||||
<div class="date">
|
|
||||||
<span>{{ w.startDate|format_date('long') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="label">
|
|
||||||
<span>{{ 'accompanying_course_work.start_date'|trans }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% if w.endDate == null %}
|
|
||||||
<li>
|
|
||||||
<div class="label no-label"></div>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="{%if date(w.endDate) < date('now') %}completed{% endif %}">
|
|
||||||
<div class="date">
|
|
||||||
<span>{{ w.endDate|format_date('long') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="label">
|
|
||||||
<span>{{ 'accompanying_course_work.end_date'|trans }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="">
|
|
||||||
|
|
||||||
{% if w.results|length > 0 and w.goals|length > 0 %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% if w.results|length > 0 %}
|
|
||||||
<div class="objective_results without-objectives">
|
|
||||||
<div class="objective">
|
|
||||||
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
|
|
||||||
<p class="chill-no-data-statement">{{ 'accompanying_course_work.results without objective'|trans }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="results">
|
|
||||||
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
|
|
||||||
<ul class="result_list">
|
|
||||||
{% for r in w.results %}
|
|
||||||
<li>{{ r.title|localize_translatable_string }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if w.goals|length > 0 %}
|
|
||||||
{% for g in w.goals %}
|
|
||||||
<div class="objective_results with-objectives">
|
|
||||||
<div class="objective">
|
|
||||||
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
|
|
||||||
<ul class="goal_title"><li>{{ g.goal.title|localize_translatable_string }}</li></ul>
|
|
||||||
</div>
|
|
||||||
<div class="results">
|
|
||||||
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
|
|
||||||
{% if g.results|length == 0 %}
|
|
||||||
<p class="chill-no-data-statement">{{ 'accompanying_course_work.no_results'|trans }}</p>
|
|
||||||
{% else %}
|
|
||||||
<ul class="result_list">
|
|
||||||
{% for r in g.results %}
|
|
||||||
<li>{{ r.title|localize_translatable_string }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="item-row separator">
|
|
||||||
|
|
||||||
<div class="updatedBy">
|
|
||||||
{{ 'Last updated by'|trans}} <b>{{ w.updatedBy|chill_entity_render_box }}</b>,<br>
|
|
||||||
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
|
|
||||||
</div>
|
|
||||||
<ul class="record_actions">
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
|
||||||
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 %}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
|
|
||||||
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
|
|
||||||
>{% if buttonText is not defined or buttonText == true %}{{ 'Delete'|trans }}{% endif %}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
@ -2,14 +2,11 @@
|
|||||||
{% for w in works | slice(0,5) %}
|
{% for w in works | slice(0,5) %}
|
||||||
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
||||||
class="badge-link" title="{{ 'crud.social_action.title_link'|trans }}">
|
class="dashboard-link" title="{{ 'crud.social_action.title_link'|trans }}">
|
||||||
|
|
||||||
<h2 class="badge-title">
|
<div class="dashboard">
|
||||||
<span class="title_label">
|
<span class="title_label"></span>
|
||||||
<span>{{ 'accompanying_course_work.action'|trans }}</span>
|
<span class="title_action"><span class="like-h3">{{ w.socialAction|chill_entity_render_string }}</span>
|
||||||
</span>
|
|
||||||
<span class="title_action">
|
|
||||||
{{ w.socialAction|chill_entity_render_string }}
|
|
||||||
|
|
||||||
<ul class="small_in_title columns mt-3">
|
<ul class="small_in_title columns mt-3">
|
||||||
<li>
|
<li>
|
||||||
@ -75,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</div>
|
||||||
|
|
||||||
</a>{# {{ dump(w) }} #}
|
</a>{# {{ dump(w) }} #}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
|
|
||||||
<div id="relationship-graph"
|
<div id="relationship-graph"
|
||||||
style="margin-top: -3rem"
|
style="margin-top: -3rem"
|
||||||
data-persons="{{ persons|e('html_attr') }}">
|
data-persons="{{ persons|e('html_attr') }}"
|
||||||
|
data-household-id="{{ household.id|e('html_attr') }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -18,6 +18,7 @@ use Chill\PersonBundle\Entity\Household\Household;
|
|||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Repository\PersonRepository;
|
use Chill\PersonBundle\Repository\PersonRepository;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use DateTimeImmutable;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||||
@ -114,18 +115,14 @@ class PersonJsonNormalizer implements
|
|||||||
case 'birthdate':
|
case 'birthdate':
|
||||||
$object = $this->denormalizer->denormalize($data[$item], DateTime::class, $format, $context);
|
$object = $this->denormalizer->denormalize($data[$item], DateTime::class, $format, $context);
|
||||||
|
|
||||||
if ($object instanceof DateTime) {
|
|
||||||
$person->setBirthdate($object);
|
$person->setBirthdate($object);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'deathdate':
|
case 'deathdate':
|
||||||
$object = $this->denormalizer->denormalize($data[$item], DateTime::class, $format, $context);
|
$object = $this->denormalizer->denormalize($data[$item], DateTimeImmutable::class, $format, $context);
|
||||||
|
|
||||||
if ($object instanceof DateTime) {
|
|
||||||
$person->setDeathdate($object);
|
$person->setDeathdate($object);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -353,6 +353,19 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Person"
|
$ref: "#/components/schemas/Person"
|
||||||
|
examples:
|
||||||
|
Update a person:
|
||||||
|
value:
|
||||||
|
type: "person"
|
||||||
|
firstName: "string"
|
||||||
|
lastName: "string"
|
||||||
|
birthdate:
|
||||||
|
datetime: "2016-06-01T00:00:00+02:00"
|
||||||
|
deathdate:
|
||||||
|
datetime: "2021-06-01T00:00:00+02:00"
|
||||||
|
phonenumber: "string"
|
||||||
|
mobilenumber: "string"
|
||||||
|
gender: "male"
|
||||||
responses:
|
responses:
|
||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
@ -375,6 +388,19 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Person"
|
$ref: "#/components/schemas/Person"
|
||||||
|
examples:
|
||||||
|
Create a new person:
|
||||||
|
value:
|
||||||
|
type: "person"
|
||||||
|
firstName: "string"
|
||||||
|
lastName: "string"
|
||||||
|
birthdate:
|
||||||
|
datetime: "2016-06-01T00:00:00+02:00"
|
||||||
|
deathdate:
|
||||||
|
datetime: "2021-06-01T00:00:00+02:00"
|
||||||
|
phonenumber: "string"
|
||||||
|
mobilenumber: "string"
|
||||||
|
gender: "male"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
|
@ -198,6 +198,7 @@ Resources: Interlocuteurs privilégiés
|
|||||||
Any requestor to this accompanying course: Aucun demandeur pour ce parcours
|
Any requestor to this accompanying course: Aucun demandeur pour ce parcours
|
||||||
Social actions: Actions d'accompagnement
|
Social actions: Actions d'accompagnement
|
||||||
Last social actions: Les dernières actions d'accompagnement
|
Last social actions: Les dernières actions d'accompagnement
|
||||||
|
Social issue: Problématique sociale
|
||||||
Social issues: Problématiques sociales
|
Social issues: Problématiques sociales
|
||||||
Last events on accompanying course: Dernières actions de suivi
|
Last events on accompanying course: Dernières actions de suivi
|
||||||
Edit & activate accompanying course: Modifier et valider
|
Edit & activate accompanying course: Modifier et valider
|
||||||
@ -434,10 +435,14 @@ accompanying_course_work:
|
|||||||
create_date: Date de création
|
create_date: Date de création
|
||||||
start_date: Date de début
|
start_date: Date de début
|
||||||
end_date: Date de fin
|
end_date: Date de fin
|
||||||
|
max_date: Date d'échéance
|
||||||
|
warning_interval: Rappel
|
||||||
|
'%days% days before max_date': "%days% jour(s) avant l'échéance"
|
||||||
results without objective: Aucun objectif - motif - dispositif
|
results without objective: Aucun objectif - motif - dispositif
|
||||||
no_results: Aucun résultat - orientation
|
no_results: Aucun résultat - orientation
|
||||||
results: Résultats - orientations
|
results: Résultats - orientations
|
||||||
goal: Objectif - motif - dispositif
|
goal: Objectif - motif - dispositif
|
||||||
|
evaluations: Évaluations
|
||||||
Any work: Aucune action d'accompagnement
|
Any work: Aucune action d'accompagnement
|
||||||
remove: Supprimer une action d'accompagnement
|
remove: Supprimer une action d'accompagnement
|
||||||
social_evaluation: Évaluation
|
social_evaluation: Évaluation
|
||||||
|
Loading…
x
Reference in New Issue
Block a user