From f88fa2ef009c1a2b8210f76d0a75afc3641716a5 Mon Sep 17 00:00:00 2001 From: Sarahshr <51380592+Sarahshr@users.noreply.github.com> Date: Wed, 3 Jan 2024 19:15:31 +0100 Subject: [PATCH 1/3] feat: add more infos in dashboard page (#673) Co-authored-by: Sarahshr --- fixtures/test/service_request.yaml | 1 + fixtures/test/user.yaml | 2 + src/Controller/Admin/DashboardController.php | 16 +++++ src/Repository/ServiceRequestRepository.php | 24 ++++++++ src/Repository/UserRepository.php | 42 +++++++++++++ templates/admin/dashboard.html.twig | 47 ++++++++++++++- .../templates/admin/dashboard/admin.fr.xlf | 59 +++++++++++++++++++ 7 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 translations/templates/admin/dashboard/admin.fr.xlf diff --git a/fixtures/test/service_request.yaml b/fixtures/test/service_request.yaml index 95e6c00..202fd1f 100644 --- a/fixtures/test/service_request.yaml +++ b/fixtures/test/service_request.yaml @@ -11,6 +11,7 @@ App\Entity\ServiceRequest: recipient: '@user_17' startAt: '' endAt: '' + createdAt: # ongoing service request, can be finalized manually # @see ServiceRequestStatusWorkflowControllerFinalizeTest diff --git a/fixtures/test/user.yaml b/fixtures/test/user.yaml index 8f41e95..17baa41 100644 --- a/fixtures/test/user.yaml +++ b/fixtures/test/user.yaml @@ -75,6 +75,7 @@ App\Entity\User: address: '@address_region_hauts_de_france' schedule: '9h30 - 17h30' phoneNumber: null + createdAt: # —— Users ————————————————————————————————————————————————————————————————— # user with vacation mode @@ -111,6 +112,7 @@ App\Entity\User: confirmationExpiresAt: firstname: lastname: + createdAt: # user with an expired confirmation token # user with an unconfirmed email diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 259af95..6909cf8 100755 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -11,8 +11,13 @@ use App\Entity\Page; use App\Entity\ServiceRequest; use App\Entity\User; use App\Entity\UserGroup; +use App\Enum\User\UserType; use App\Repository\GroupRepository; +use App\Repository\ServiceRequestRepository; +use App\Repository\UserRepository; use App\Security\Checker\AuthorizationChecker; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem; @@ -55,6 +60,8 @@ final class DashboardController extends AbstractDashboardController private readonly GroupRepository $groupRepository, private readonly AdminUrlGenerator $adminUrlGenerator, private readonly AuthorizationChecker $authorizationChecker, + private readonly UserRepository $userRepository, + private readonly ServiceRequestRepository $requestRepository ) { } @@ -74,6 +81,10 @@ final class DashboardController extends AbstractDashboardController ; } + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ #[Route('/admin', name: 'admin')] public function index(): Response { @@ -87,6 +98,11 @@ final class DashboardController extends AbstractDashboardController return $this->render('admin/dashboard.html.twig', [ 'group_count' => $this->groupRepository->count([]), + 'user_count' => $this->userRepository->getUserCountByType(UserType::USER), + 'place_count' => $this->userRepository->getUserCountByType(UserType::PLACE), + 'month_users_count' => $this->userRepository->getNewUsersOfMonthByType(UserType::USER), + 'month_places_count' => $this->userRepository->getNewUsersOfMonthByType(UserType::PLACE), + 'month_requests_count' => $this->requestRepository->getNewServiceRequestsOfMonth(), ]); } diff --git a/src/Repository/ServiceRequestRepository.php b/src/Repository/ServiceRequestRepository.php index 207c63b..971ced9 100644 --- a/src/Repository/ServiceRequestRepository.php +++ b/src/Repository/ServiceRequestRepository.php @@ -10,6 +10,8 @@ use App\Entity\User; use App\Enum\ServiceRequest\ServiceRequestStatus; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; @@ -126,4 +128,26 @@ final class ServiceRequestRepository extends ServiceEntityRepository { return $this->getActionSoon('endAt'); } + + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function getNewServiceRequestsOfMonth(): int + { + $today = new \DateTime(); + + /** @var int */ + return $this + ->createQueryBuilder('sr') + ->select('COUNT(sr.id)') + ->where('sr.createdAt >= :firstDay') + ->andWhere('sr.createdAt <= :lastDay') + ->setParameters([ + 'firstDay' => $today->modify('first day of this month')->format('Y-m-d'), + 'lastDay' => $today->modify('last day of this month')->format('Y-m-d'), + ]) + ->getQuery() + ->getSingleScalarResult(); + } } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index cfc22d8..f82f0a2 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -7,6 +7,8 @@ namespace App\Repository; use App\Entity\User; use App\Enum\User\UserType; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; @@ -105,4 +107,44 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader ->orderBy('p.name', 'ASC') ; } + + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function getUserCountByType(UserType $type): int + { + /** @var int */ + return $this + ->createQueryBuilder('u') + ->select('COUNT(u.id)') + ->where('u.type = :type') + ->setParameter('type', $type) + ->getQuery() + ->getSingleScalarResult(); + } + + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function getNewUsersOfMonthByType(UserType $type): int + { + $today = new \DateTime(); + + /** @var int */ + return $this + ->createQueryBuilder('u') + ->select('COUNT(u.id)') + ->where('u.type = :type') + ->andWhere('u.createdAt >= :firstDay') + ->andWhere('u.createdAt <= :lastDay') + ->setParameters([ + 'firstDay' => $today->modify('first day of this month')->format('Y-m-d'), + 'lastDay' => $today->modify('last day of this month')->format('Y-m-d'), + 'type' => $type, + ]) + ->getQuery() + ->getSingleScalarResult(); + } } diff --git a/templates/admin/dashboard.html.twig b/templates/admin/dashboard.html.twig index 0e66af8..e230e50 100644 --- a/templates/admin/dashboard.html.twig +++ b/templates/admin/dashboard.html.twig @@ -3,6 +3,8 @@ {% trans_default_domain 'admin' %} +{% set i18n_prefix = _self|i18n_prefix %} + {% block content_title %}Dashboard{% endblock %} {% block page_actions %} @@ -17,13 +19,54 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nombre de groupes{{ (i18n_prefix ~ '.group')|trans }}
{{ group_count }}{{ (i18n_prefix ~ '.group_count')|trans({'%group_count%': group_count}) }}
{{ (i18n_prefix ~ '.users')|trans }}
+

{{ (i18n_prefix ~ '.month_users_count')|trans({'%month_users_count%': month_users_count}) }}

+

{{ (i18n_prefix ~ '.user_count')|trans({'%user_count%': user_count}) }}

+
{{ (i18n_prefix ~ '.places')|trans }}
+

{{ (i18n_prefix ~ '.month_places_count')|trans({'%month_places_count%': month_places_count}) }}

+

{{ (i18n_prefix ~ '.place_count')|trans({'%place_count%': place_count}) }}

+
{{ (i18n_prefix ~ '.loans')|trans }}
+

{{ (i18n_prefix ~ '.month_requests_count')|trans({'%month_requests_count%': month_requests_count}) }}

+
{% endblock %} diff --git a/translations/templates/admin/dashboard/admin.fr.xlf b/translations/templates/admin/dashboard/admin.fr.xlf new file mode 100644 index 0000000..ed7e125 --- /dev/null +++ b/translations/templates/admin/dashboard/admin.fr.xlf @@ -0,0 +1,59 @@ + + + +
+ +
+ + + templates.admin.dashboard.group + Groupes + + + + templates.admin.dashboard.group_count + Nombre de groupe: %group_count% + + + + templates.admin.dashboard.users + Utilisateurs + + + + templates.admin.dashboard.month_users_count + Nombre de nouveaux utilisateurs du mois: %month_users_count% + + + + templates.admin.dashboard.user_count + Nombre total: %user_count% + + + + templates.admin.dashboard.places + Lieux + + + + templates.admin.dashboard.month_places_count + Nombre de nouveaux lieux du mois: %month_places_count% + + + + templates.admin.dashboard.place_count + Nombre total: %place_count% + + + + templates.admin.dashboard.loans + Emprunts + + + + templates.admin.dashboard.month_requests_count + Nombre d’emprunts du mois: %month_requests_count% + + +
+
From 9f661ebe0a079b82fde997102ce514699c0fa75d Mon Sep 17 00:00:00 2001 From: Sarahshr <51380592+Sarahshr@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:09:32 +0100 Subject: [PATCH 2/3] Feat user list (#674) * add group count column in users & places lists * add export button for products pages --------- Co-authored-by: Sarahshr --- .../Admin/AbstractProductCrudController.php | 44 ++++++++++++++++++- .../Admin/AbstractUserCrudController.php | 2 + src/Controller/Admin/PlaceCrudController.php | 3 +- src/Controller/Admin/UserCrudController.php | 3 +- translations/admin.fr.xlf | 5 +++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/Controller/Admin/AbstractProductCrudController.php b/src/Controller/Admin/AbstractProductCrudController.php index 8db4bf8..5f0f351 100755 --- a/src/Controller/Admin/AbstractProductCrudController.php +++ b/src/Controller/Admin/AbstractProductCrudController.php @@ -15,6 +15,7 @@ use App\Enum\Product\ProductType; use App\Enum\Product\ProductVisibility; use App\Flysystem\EasyAdminHelper; use App\Flysystem\MediaManager; +use App\Helper\CsvExporter; use App\Repository\CategoryRepository; use App\Repository\ProductRepository; use Doctrine\ORM\QueryBuilder; @@ -27,8 +28,10 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; +use EasyCorp\Bundle\EasyAdminBundle\Dto\CrudDto; use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto; +use EasyCorp\Bundle\EasyAdminBundle\Factory\FilterFactory; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; @@ -45,6 +48,8 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\String\Slugger\SluggerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; abstract class AbstractProductCrudController extends AbstractCrudController implements AdminSecuredCrudControllerInterface { @@ -65,6 +70,10 @@ abstract class AbstractProductCrudController extends AbstractCrudController impl private readonly MediaManager $mediaManager, #[Autowire('%product_base_path%')] private readonly string $productBasePath, + private readonly CsvExporter $csvExporter, + private readonly TranslatorInterface $translator, + private readonly FilterFactory $filterFactory, + private readonly SluggerInterface $slugger, ) { } @@ -112,13 +121,29 @@ abstract class AbstractProductCrudController extends AbstractCrudController impl $availability = Action::new('availability', 'action.availability') ->linkToCrudAction('linkToProductAvailabilityPage'); + $exportAction = Action::new('export') + ->linkToUrl(function () { + /** @var AdminContext $context */ + $context = $this->getContext(); + + return $this->adminUrlGenerator->setAll($context->getRequest()->query->all()) + ->setEntityId(null) + ->setAction('export') + ->generateUrl(); + }) + ->addCssClass('btn btn-success') + ->setIcon('fa fa-download') + ->createAsGlobalAction() + ; + return $actions ->add(Crud::PAGE_INDEX, Action::DETAIL) ->add(Crud::PAGE_EDIT, Action::DETAIL) ->add(Crud::PAGE_EDIT, Action::INDEX) ->add(Crud::PAGE_INDEX, $onBreak) ->add(Crud::PAGE_INDEX, $activate) - ->add(Crud::PAGE_DETAIL, $availability); + ->add(Crud::PAGE_DETAIL, $availability) + ->add(Crud::PAGE_INDEX, $exportAction); } private function redirectToObjectCrudPage(): RedirectResponse @@ -147,6 +172,23 @@ abstract class AbstractProductCrudController extends AbstractCrudController impl return $this->redirectToObjectCrudPage(); } + /** + * For now, we export exactly what we see in the list to avoid security problems. + */ + public function export(AdminContext $context): Response + { + $fields = FieldCollection::new($this->configureFields(Crud::PAGE_INDEX)); + /** @var CrudDto $crud Crud is defined here */ + $crud = $context->getCrud(); + $filters = $this->filterFactory->create($crud->getFiltersConfig(), $fields, $context->getEntity()); + /** @var SearchDto $search */ + $search = $context->getSearch(); + $queryBuilder = $this->createIndexQueryBuilder($search, $context->getEntity(), $fields, $filters); + $fileName = $this->slugger->slug($this->translator->trans($this->getEntityLabelInPlural(), [], DashboardController::DOMAIN)); + + return $this->csvExporter->createResponseFromQueryBuilder($queryBuilder, $fields, $fileName.'.csv'); + } + public static function getEntityFqcn(): string { return Product::class; diff --git a/src/Controller/Admin/AbstractUserCrudController.php b/src/Controller/Admin/AbstractUserCrudController.php index c914d39..1943880 100755 --- a/src/Controller/Admin/AbstractUserCrudController.php +++ b/src/Controller/Admin/AbstractUserCrudController.php @@ -354,6 +354,7 @@ abstract class AbstractUserCrudController extends AbstractCrudController impleme $smsNotificationsField = BooleanField::new('smsNotifications'); $vacationModeField = BooleanField::new('vacationMode'); $addressField = AssociationField::new('address'); + $groupsCountField = AssociationField::new('userGroups')->setLabel('Groups number'); return compact( 'idField', @@ -376,6 +377,7 @@ abstract class AbstractUserCrudController extends AbstractCrudController impleme 'smsNotificationsField', 'vacationModeField', 'addressField', + 'groupsCountField', ); } diff --git a/src/Controller/Admin/PlaceCrudController.php b/src/Controller/Admin/PlaceCrudController.php index 74be36f..58c22c3 100755 --- a/src/Controller/Admin/PlaceCrudController.php +++ b/src/Controller/Admin/PlaceCrudController.php @@ -45,10 +45,11 @@ final class PlaceCrudController extends AbstractUserCrudController 'loginAt' => $loginAt, 'createdAt' => $createdAt, 'updatedAt' => $updatedAt, + 'groupsCountField' => $groupsCountField, ] = $this->getFields($pageName); if ($pageName === Crud::PAGE_INDEX) { - return [$emailField, $nameField, $enabledField, $emailConfirmedField, $createdAt, $updatedAt, $loginAt]; + return [$emailField, $nameField, $enabledField, $emailConfirmedField, $createdAt, $updatedAt, $loginAt, $groupsCountField]; } if ($pageName === Crud::PAGE_NEW || $pageName === Crud::PAGE_EDIT) { diff --git a/src/Controller/Admin/UserCrudController.php b/src/Controller/Admin/UserCrudController.php index b8ddff9..376aae5 100755 --- a/src/Controller/Admin/UserCrudController.php +++ b/src/Controller/Admin/UserCrudController.php @@ -47,10 +47,11 @@ final class UserCrudController extends AbstractUserCrudController 'smsNotificationsField' => $smsNotificationsField, 'vacationModeField' => $vacationModeField, 'addressField' => $addressField, + 'groupsCountField' => $groupsCountField, ] = $this->getFields($pageName); if ($pageName === Crud::PAGE_INDEX) { - return [$emailField, $firstNameField, $lastNameField, $enabledField, $emailConfirmedField, $avatarField, $createdAt, $updatedAt, $loginAt]; + return [$emailField, $firstNameField, $lastNameField, $enabledField, $emailConfirmedField, $avatarField, $createdAt, $updatedAt, $loginAt, $groupsCountField]; } $panels = $this->getPanels(); diff --git a/translations/admin.fr.xlf b/translations/admin.fr.xlf index 7e4880a..cbd466e 100644 --- a/translations/admin.fr.xlf +++ b/translations/admin.fr.xlf @@ -358,6 +358,11 @@ Retour + + Groups number + Nombre de groupes + + From b1e3a5150c87cb02189567333a5b1c4c8ac79fed Mon Sep 17 00:00:00 2001 From: Sarahshr <51380592+Sarahshr@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:05:46 +0100 Subject: [PATCH 3/3] Feat/disable services 662 (#671) * disable button for services * add button to disable services * fix account services links * fixup! fix account services links --------- Co-authored-by: Sarahshr --- fixtures/prod-boot/configuration.yaml | 2 + src/Entity/Configuration.php | 2 +- templates/pages/account/index.html.twig | 206 ++++++++++++------------ 3 files changed, 108 insertions(+), 102 deletions(-) diff --git a/fixtures/prod-boot/configuration.yaml b/fixtures/prod-boot/configuration.yaml index 5f79014..4866531 100644 --- a/fixtures/prod-boot/configuration.yaml +++ b/fixtures/prod-boot/configuration.yaml @@ -4,6 +4,8 @@ App\Entity\Configuration: features (extends configuration_template): configuration: + services: + servicesEnabled: true notificationsSender: notificationsSenderEmail: info@example.com notificationsSenderName: Contact diff --git a/src/Entity/Configuration.php b/src/Entity/Configuration.php index 86984f6..e60559c 100644 --- a/src/Entity/Configuration.php +++ b/src/Entity/Configuration.php @@ -80,7 +80,7 @@ class Configuration */ public function getServices(): array { - /** @var array $services */ + /** @var array $services */ $services = $this->configuration['services'] ?? []; return $services; diff --git a/templates/pages/account/index.html.twig b/templates/pages/account/index.html.twig index acd5446..442dd0f 100644 --- a/templates/pages/account/index.html.twig +++ b/templates/pages/account/index.html.twig @@ -18,24 +18,26 @@ {% set my_account_links = [ { section: 'Messagerie', + enable: true, links: [ - { - name: 'Mes emprunts', - link: 'app_user_my_loans', - icon: null, - notification: userHasNewLoanMessage - }, - { - name: 'Mes prêts', - link: 'app_user_my_lendings', - icon: null, - notification: userHasNewLendingMessage - }, - ], + { + name: 'Mes emprunts', + link: 'app_user_my_loans', + icon: null, + notification: userHasNewLoanMessage + }, + { + name: 'Mes prêts', + link: 'app_user_my_lendings', + icon: null, + notification: userHasNewLendingMessage + }, + ], icon: 'bi bi-chat-left-text' }, { section: 'Mes objets', + enable: true, links: [ { name: 'Voir mes objets', @@ -53,74 +55,76 @@ }, { section: 'Mes services', - disable: servicesConfig, + enable: servicesConfig, links: [ - { - name: 'Voir mes services', - link: 'app_user_services', - icon: null - }, - { - name: 'Créer un service', - link: 'app_service_new', - icon: null, - needAddress: app.user.address is null ? true : false - }, - ], + { + name: 'Voir mes services', + link: 'app_user_services', + icon: null + }, + { + name: 'Créer un service', + link: 'app_service_new', + icon: null, + needAddress: app.user.address is null ? true : false + }, + ], icon: 'fa-solid fa-shop' }, { section: 'Mes groupes', + enable: true, links: [ - { - name: 'Voir mes groupes', - link: 'app_user_groups', - icon: null - }, - { - name: 'Administrer mes groupes', - link: 'admin', - show: show_my_groups, - icon: 'bi bi-box-arrow-up-right' - }, - { - name: create_group_label, - link: 'app_group_create', - icon: create_group_icon, - canCreateGroup: canCreateGroup, - }, - ], + { + name: 'Voir mes groupes', + link: 'app_user_groups', + icon: null + }, + { + name: 'Administrer mes groupes', + link: 'admin', + show: show_my_groups, + icon: 'bi bi-box-arrow-up-right' + }, + { + name: create_group_label, + link: 'app_group_create', + icon: create_group_icon, + canCreateGroup: canCreateGroup, + }, + ], icon: 'fa-solid fa-user-group' }, { section: 'Compte', + enable: true, links: [ - { - name: 'Mon adresse', - link: 'user_address_step1', - icon: null - }, - { - name: 'Modifier mon profil', - link: 'app_user_edit_profile', - icon: null - }, - { - name: 'Changer mon adresse e-mail', - link: 'app_user_change_login', - icon: null - }, - { - name: 'Changer mon mot de passe', - link: 'app_user_change_password', - icon: null - }, - { - name: vacation_mode_link, - link: 'user_toggle_vacation_mode', - icon: vacation_mode_icon - }, - ], + { + name: 'Mon adresse', + link: 'user_address_step1', + icon: null + }, + { + name: 'Modifier mon profil', + link: 'app_user_edit_profile', + icon: null + }, + { + name: 'Changer mon adresse e-mail', + link: 'app_user_change_login', + icon: null + }, + { + name: 'Changer mon mot de passe', + link: 'app_user_change_password', + icon: null + }, + { + name: vacation_mode_link, + link: 'user_toggle_vacation_mode', + icon: vacation_mode_icon + }, + ], icon: 'fa-solid fa-user fa-xl text-white' }, ] %} @@ -134,7 +138,7 @@ } %}
{% for my_account_link in my_account_links %} - {% if my_account_link.disable is not defined %} + {% if my_account_link.enable %}