mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Admin CRUD for user groups
This commit is contained in:
parent
2e71808be1
commit
debca1f474
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
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 Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
class UserGroupAdminController extends CRUDController
|
||||||
|
{
|
||||||
|
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||||
|
{
|
||||||
|
$query->addSelect('JSON_EXTRACT(e.label, :lang) AS HIDDEN labeli18n')
|
||||||
|
->setParameter('lang', $request->getLocale());
|
||||||
|
$query->addOrderBy('labeli18n', 'ASC');
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ use Chill\MainBundle\Controller\LocationTypeController;
|
|||||||
use Chill\MainBundle\Controller\NewsItemController;
|
use Chill\MainBundle\Controller\NewsItemController;
|
||||||
use Chill\MainBundle\Controller\RegroupmentController;
|
use Chill\MainBundle\Controller\RegroupmentController;
|
||||||
use Chill\MainBundle\Controller\UserController;
|
use Chill\MainBundle\Controller\UserController;
|
||||||
|
use Chill\MainBundle\Controller\UserGroupAdminController;
|
||||||
use Chill\MainBundle\Controller\UserGroupApiController;
|
use Chill\MainBundle\Controller\UserGroupApiController;
|
||||||
use Chill\MainBundle\Controller\UserJobApiController;
|
use Chill\MainBundle\Controller\UserJobApiController;
|
||||||
use Chill\MainBundle\Controller\UserJobController;
|
use Chill\MainBundle\Controller\UserJobController;
|
||||||
@ -70,6 +71,7 @@ use Chill\MainBundle\Form\LocationFormType;
|
|||||||
use Chill\MainBundle\Form\LocationTypeType;
|
use Chill\MainBundle\Form\LocationTypeType;
|
||||||
use Chill\MainBundle\Form\NewsItemType;
|
use Chill\MainBundle\Form\NewsItemType;
|
||||||
use Chill\MainBundle\Form\RegroupmentType;
|
use Chill\MainBundle\Form\RegroupmentType;
|
||||||
|
use Chill\MainBundle\Form\UserGroupType;
|
||||||
use Chill\MainBundle\Form\UserJobType;
|
use Chill\MainBundle\Form\UserJobType;
|
||||||
use Chill\MainBundle\Form\UserType;
|
use Chill\MainBundle\Form\UserType;
|
||||||
use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType;
|
use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType;
|
||||||
@ -355,6 +357,28 @@ class ChillMainExtension extends Extension implements
|
|||||||
{
|
{
|
||||||
$container->prependExtensionConfig('chill_main', [
|
$container->prependExtensionConfig('chill_main', [
|
||||||
'cruds' => [
|
'cruds' => [
|
||||||
|
[
|
||||||
|
'class' => UserGroup::class,
|
||||||
|
'controller' => UserGroupAdminController::class,
|
||||||
|
'name' => 'admin_user_group',
|
||||||
|
'base_path' => '/admin/main/user-group',
|
||||||
|
'base_role' => 'ROLE_ADMIN',
|
||||||
|
'form_class' => UserGroupType::class,
|
||||||
|
'actions' => [
|
||||||
|
'index' => [
|
||||||
|
'role' => 'ROLE_ADMIN',
|
||||||
|
'template' => '@ChillMain/UserGroup/index.html.twig',
|
||||||
|
],
|
||||||
|
'new' => [
|
||||||
|
'role' => 'ROLE_ADMIN',
|
||||||
|
'template' => '@ChillMain/UserGroup/new.html.twig',
|
||||||
|
],
|
||||||
|
'edit' => [
|
||||||
|
'role' => 'ROLE_ADMIN',
|
||||||
|
'template' => '@ChillMain/UserGroup/edit.html.twig',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'class' => UserJob::class,
|
'class' => UserJob::class,
|
||||||
'controller' => UserJobController::class,
|
'controller' => UserJobController::class,
|
||||||
|
@ -13,6 +13,10 @@ namespace Chill\MainBundle\Entity;
|
|||||||
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\Common\Collections\Criteria;
|
||||||
|
use Doctrine\Common\Collections\Order;
|
||||||
|
use Doctrine\Common\Collections\ReadableCollection;
|
||||||
|
use Doctrine\Common\Collections\Selectable;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
@ -31,11 +35,11 @@ class UserGroup
|
|||||||
private array $label = [];
|
private array $label = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Doctrine\Common\Collections\Collection<int, \Chill\MainBundle\Entity\User>
|
* @var Collection<int, User>&Selectable<int, User>
|
||||||
*/
|
*/
|
||||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||||
#[ORM\JoinTable(name: 'chill_main_user_group_user')]
|
#[ORM\JoinTable(name: 'chill_main_user_group_user')]
|
||||||
private Collection $users;
|
private Collection&Selectable $users;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, User>&Selectable<int, User>
|
* @var Collection<int, User>&Selectable<int, User>
|
||||||
@ -61,6 +65,7 @@ class UserGroup
|
|||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
$this->adminUsers = new \Doctrine\Common\Collections\ArrayCollection();
|
||||||
$this->users = new ArrayCollection();
|
$this->users = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +77,7 @@ class UserGroup
|
|||||||
public function setActive(bool $active): self
|
public function setActive(bool $active): self
|
||||||
{
|
{
|
||||||
$this->active = $active;
|
$this->active = $active;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +125,10 @@ class UserGroup
|
|||||||
return $this->label;
|
return $this->label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUsers(): Collection
|
/**
|
||||||
|
* @return Selectable<int, User>&Collection<int, User>
|
||||||
|
*/
|
||||||
|
public function getUsers(): Collection&Selectable
|
||||||
{
|
{
|
||||||
return $this->users;
|
return $this->users;
|
||||||
}
|
}
|
||||||
@ -191,4 +200,20 @@ class UserGroup
|
|||||||
{
|
{
|
||||||
return $this->users->contains($user);
|
return $this->users->contains($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUserListByLabelAscending(): ReadableCollection
|
||||||
|
{
|
||||||
|
$criteria = Criteria::create();
|
||||||
|
$criteria->orderBy(['label' => Order::Ascending]);
|
||||||
|
|
||||||
|
return $this->getUsers()->matching($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdminUserListByLabelAscending(): ReadableCollection
|
||||||
|
{
|
||||||
|
$criteria = Criteria::create();
|
||||||
|
$criteria->orderBy(['label' => Order::Ascending]);
|
||||||
|
|
||||||
|
return $this->getAdminUsers()->matching($criteria);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
58
src/Bundle/ChillMainBundle/Form/UserGroupType.php
Normal file
58
src/Bundle/ChillMainBundle/Form/UserGroupType.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
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 Chill\MainBundle\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||||
|
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ColorType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class UserGroupType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('label', TranslatableStringFormType::class, [
|
||||||
|
'label' => 'user_group.Label',
|
||||||
|
'required' => true,
|
||||||
|
])
|
||||||
|
->add('active')
|
||||||
|
->add('backgroundColor', ColorType::class, [
|
||||||
|
'label' => 'user_group.BackgroundColor',
|
||||||
|
])
|
||||||
|
->add('foregroundColor', ColorType::class, [
|
||||||
|
'label' => 'user_group.ForegroundColor',
|
||||||
|
])
|
||||||
|
->add('excludeKey', TextType::class, [
|
||||||
|
'label' => 'user_group.ExcludeKey',
|
||||||
|
'help' => 'user_group.ExcludeKeyHelp',
|
||||||
|
'required' => false,
|
||||||
|
'empty_data' => '',
|
||||||
|
])
|
||||||
|
->add('users', PickUserDynamicType::class, [
|
||||||
|
'label' => 'user_group.Users',
|
||||||
|
'multiple' => true,
|
||||||
|
'required' => false,
|
||||||
|
'empty_data' => [],
|
||||||
|
])
|
||||||
|
->add('adminUsers', PickUserDynamicType::class, [
|
||||||
|
'label' => 'user_group.adminUsers',
|
||||||
|
'multiple' => true,
|
||||||
|
'required' => false,
|
||||||
|
'empty_data' => [],
|
||||||
|
'help' => 'user_group.adminUsersHelp',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
@ -59,8 +59,9 @@ final readonly class UserGroupRepository implements UserGroupRepositoryInterface
|
|||||||
->setSelectPertinence('3 + SIMILARITY(LOWER(UNACCENT(?)), ug.label->>?) + CASE WHEN (EXISTS(SELECT 1 FROM unnest(string_to_array(label->>?, \' \')) AS t WHERE LOWER(t) LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\')) THEN 100 ELSE 0 END', [$pattern, $lang, $lang, $pattern])
|
->setSelectPertinence('3 + SIMILARITY(LOWER(UNACCENT(?)), ug.label->>?) + CASE WHEN (EXISTS(SELECT 1 FROM unnest(string_to_array(label->>?, \' \')) AS t WHERE LOWER(t) LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\')) THEN 100 ELSE 0 END', [$pattern, $lang, $lang, $pattern])
|
||||||
->setFromClause('chill_main_user_group AS ug')
|
->setFromClause('chill_main_user_group AS ug')
|
||||||
->setWhereClauses('
|
->setWhereClauses('
|
||||||
|
ug.active AND (
|
||||||
SIMILARITY(LOWER(UNACCENT(?)), ug.label->>?) > 0.15
|
SIMILARITY(LOWER(UNACCENT(?)), ug.label->>?) > 0.15
|
||||||
OR ug.label->>? LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\'
|
OR ug.label->>? LIKE \'%\' || LOWER(UNACCENT(?)) || \'%\')
|
||||||
', [$pattern, $lang, $pattern, $lang]);
|
', [$pattern, $lang, $pattern, $lang]);
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
|
@ -233,7 +233,7 @@ div.wrap-header {
|
|||||||
}
|
}
|
||||||
&:last-child {}
|
&:last-child {}
|
||||||
|
|
||||||
div.wh-col {
|
& > div.wh-col {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
flex-grow: 0; flex-shrink: 1; flex-basis: auto;
|
flex-grow: 0; flex-shrink: 1; flex-basis: auto;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block admin_content %}
|
||||||
|
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
|
||||||
|
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
|
{% endblock admin_content %}
|
@ -0,0 +1,86 @@
|
|||||||
|
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||||
|
|
||||||
|
{% block admin_content %}
|
||||||
|
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||||
|
|
||||||
|
{% block table_entities %}
|
||||||
|
<div class="flex-table">
|
||||||
|
{% for entity in entities %}
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="wrap-header">
|
||||||
|
<div class="wh-row">
|
||||||
|
<div class="wh-col">
|
||||||
|
{{ entity|chill_entity_render_box }}
|
||||||
|
</div>
|
||||||
|
<div class="wh-col">
|
||||||
|
{%- if not entity.active -%}
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-danger">{{ 'user_group.inactive'|trans }}</span>
|
||||||
|
</div>
|
||||||
|
{%- endif -%}
|
||||||
|
<div>{{ 'user_group.with_count_users'|trans({'count': entity.users|length}) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="wrap-list">
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<strong>{{ 'user_group.with_users'|trans }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% for user in entity.userListByLabelAscending %}
|
||||||
|
<p class="wl-item">
|
||||||
|
<span class="badge-user">
|
||||||
|
{{ user|chill_entity_render_box }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="wl-item chill-no-data-statement">{{ 'user_group.no_users'|trans }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="wrap-list">
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<strong>{{ 'user_group.adminUsers'|trans }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% for user in entity.adminUserListByLabelAscending %}
|
||||||
|
<p class="wl-item">
|
||||||
|
<span class="badge-user">
|
||||||
|
{{ user|chill_entity_render_box }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="wl-item chill-no-data-statement">{{ 'user_group.no_admin_users'|trans }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row separator">
|
||||||
|
<ul class="record_actions slim">
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_crud_admin_user_group_edit', {'id': entity.id}) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block actions_before %}
|
||||||
|
<li class='cancel'>
|
||||||
|
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% endembed %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,21 @@
|
|||||||
|
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block admin_content %}
|
||||||
|
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
||||||
|
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
|
{% endblock admin_content %}
|
@ -67,6 +67,10 @@ class AdminUserMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
'route' => 'chill_crud_admin_user_index',
|
'route' => 'chill_crud_admin_user_index',
|
||||||
])->setExtras(['order' => 1040]);
|
])->setExtras(['order' => 1040]);
|
||||||
|
|
||||||
|
$menu->addChild('crud.admin_user_group.index.title', [
|
||||||
|
'route' => 'chill_crud_admin_user_group_index',
|
||||||
|
])->setExtras(['order' => 1042]);
|
||||||
|
|
||||||
$menu->addChild('User jobs', [
|
$menu->addChild('User jobs', [
|
||||||
'route' => 'chill_crud_admin_user_job_index',
|
'route' => 'chill_crud_admin_user_job_index',
|
||||||
])->setExtras(['order' => 1050]);
|
])->setExtras(['order' => 1050]);
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
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 Chill\Migrations\Main;
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
@ -5,6 +5,15 @@ years_old: >-
|
|||||||
other {# ans}
|
other {# ans}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user_group:
|
||||||
|
with_count_users: >-
|
||||||
|
{count, plural,
|
||||||
|
=0 {Aucun membre}
|
||||||
|
one {1 utilisateur}
|
||||||
|
many {# utilisateurs}
|
||||||
|
other {# utilisateurs}
|
||||||
|
}
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
My notifications with counter: >-
|
My notifications with counter: >-
|
||||||
{nb, plural,
|
{nb, plural,
|
||||||
|
@ -51,6 +51,20 @@ user:
|
|||||||
no job: Pas de métier assigné
|
no job: Pas de métier assigné
|
||||||
no scope: Pas de cercle assigné
|
no scope: Pas de cercle assigné
|
||||||
|
|
||||||
|
user_group:
|
||||||
|
inactive: Inactif
|
||||||
|
with_users: Associé aux utilisateurs
|
||||||
|
no_users: Aucun utilisateur associé
|
||||||
|
no_admin_users: Aucun administrateur
|
||||||
|
Label: Nom du groupe
|
||||||
|
BackgroundColor: Couleur de fond du badge
|
||||||
|
ForegroundColor: Couleur de la police du badge
|
||||||
|
ExcludeKey: Clé d'exclusion
|
||||||
|
ExcludeKeyHelp: Lorsque cela est pertinent, les groupes comportant la même clé d'exclusion s'excluent mutuellement.
|
||||||
|
Users: Membres du groupe
|
||||||
|
adminUsers: Administrateurs du groupe
|
||||||
|
adminUsersHelp: Les administrateurs du groupe peuvent ajouter ou retirer des membres dans le groupe.
|
||||||
|
|
||||||
inactive: inactif
|
inactive: inactif
|
||||||
|
|
||||||
Edit: Modifier
|
Edit: Modifier
|
||||||
@ -395,6 +409,12 @@ crud:
|
|||||||
add_new: Créer
|
add_new: Créer
|
||||||
title_new: Nouveau métier
|
title_new: Nouveau métier
|
||||||
title_edit: Modifier un métier
|
title_edit: Modifier un métier
|
||||||
|
admin_user_group:
|
||||||
|
index:
|
||||||
|
title: Groupes d'utilisateurs
|
||||||
|
add_new: Créer
|
||||||
|
title_edit: Modifier un groupe d'utilisateur
|
||||||
|
title_new: Nouveau groupe utilisateur
|
||||||
main_location_type:
|
main_location_type:
|
||||||
index:
|
index:
|
||||||
title: Liste des types de localisations
|
title: Liste des types de localisations
|
||||||
|
Loading…
x
Reference in New Issue
Block a user