diff --git a/phpstan-types.neon b/phpstan-types.neon index 1aae06880..b11cbd153 100644 --- a/phpstan-types.neon +++ b/phpstan-types.neon @@ -340,11 +340,6 @@ parameters: count: 1 path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 3 - path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php - - message: "#^Method Chill\\\\PersonBundle\\\\Search\\\\PersonSearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#" count: 1 diff --git a/src/Bundle/ChillMainBundle/Controller/AbsenceController.php b/src/Bundle/ChillMainBundle/Controller/AbsenceController.php new file mode 100644 index 000000000..5dafa4615 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/AbsenceController.php @@ -0,0 +1,65 @@ +getUser(); + $form = $this->createForm(AbsenceType::class, $user); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->flush(); + + return $this->redirect($this->generateUrl('chill_main_user_absence_index')); + } + + return $this->render('@ChillMain/Menu/absence.html.twig', [ + 'user' => $user, + 'form' => $form->createView(), + ]); + } + + /** + * @Route( + * "/{_locale}/absence/unset", + * name="chill_main_user_absence_unset", + * methods={"GET", "POST"} + * ) + */ + public function unsetAbsence(Request $request) + { + $user = $this->getUser(); + + $user->setAbsenceStart(null); + $em = $this->getDoctrine()->getManager(); + $em->flush(); + + return $this->redirect($this->generateUrl('chill_main_user_absence_index')); + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php index dda6b63e4..a8ef88fbe 100644 --- a/src/Bundle/ChillMainBundle/Entity/User.php +++ b/src/Bundle/ChillMainBundle/Entity/User.php @@ -11,14 +11,15 @@ declare(strict_types=1); namespace Chill\MainBundle\Entity; +use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use RuntimeException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Serializer\Annotation as Serializer; -use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use function in_array; /** @@ -40,6 +41,11 @@ class User implements UserInterface */ protected ?int $id = null; + /** + * @ORM\Column(type="datetime_immutable", nullable=true) + */ + private ?DateTimeImmutable $absenceStart = null; + /** * Array where SAML attributes's data are stored. * @@ -173,6 +179,11 @@ class User implements UserInterface { } + public function getAbsenceStart(): ?DateTimeImmutable + { + return $this->absenceStart; + } + /** * Get attributes. * @@ -291,6 +302,11 @@ class User implements UserInterface return $this->usernameCanonical; } + public function isAbsent(): bool + { + return null !== $this->getAbsenceStart() && $this->getAbsenceStart() <= new DateTimeImmutable('now'); + } + /** * @return bool */ @@ -355,6 +371,11 @@ class User implements UserInterface } } + public function setAbsenceStart(?DateTimeImmutable $absenceStart): void + { + $this->absenceStart = $absenceStart; + } + public function setAttributeByDomain(string $domain, string $key, $value): self { $this->attributes[$domain][$key] = $value; diff --git a/src/Bundle/ChillMainBundle/Form/AbsenceType.php b/src/Bundle/ChillMainBundle/Form/AbsenceType.php new file mode 100644 index 000000000..d0aed640c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/AbsenceType.php @@ -0,0 +1,38 @@ +add('absenceStart', ChillDateType::class, [ + 'required' => true, + 'input' => 'datetime_immutable', + 'label' => 'absence.Absence start', + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Form/UserType.php b/src/Bundle/ChillMainBundle/Form/UserType.php index b738cc8c7..dc2140997 100644 --- a/src/Bundle/ChillMainBundle/Form/UserType.php +++ b/src/Bundle/ChillMainBundle/Form/UserType.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\PickCivilityType; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\EntityRepository; @@ -110,6 +111,11 @@ class UserType extends AbstractType return $qb; }, + ]) + ->add('absenceStart', ChillDateType::class, [ + 'required' => false, + 'input' => 'datetime_immutable', + 'label' => 'absence.Absence start', ]); // @phpstan-ignore-next-line diff --git a/src/Bundle/ChillMainBundle/Resources/public/types.ts b/src/Bundle/ChillMainBundle/Resources/public/types.ts index 142864393..9453c89b0 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/types.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/types.ts @@ -35,6 +35,7 @@ export interface User { id: number; username: string; text: string; + text_without_absence: string; email: string; user_job: Job; label: string; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue index a5e1cf183..a9c2db9b4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue @@ -1,8 +1,7 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/views/Entity/user.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Entity/user.html.twig index 77dc959a2..ad05a5a2c 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Entity/user.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Entity/user.html.twig @@ -6,4 +6,7 @@ {%- if opts['main_scope'] and user.mainScope is not null %} ({{ user.mainScope.name|localize_translatable_string }}) {%- endif -%} + {%- if opts['absence'] and user.isAbsent %} + {{ 'absence.A'|trans }} + {%- endif -%} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Homepage/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Homepage/index.html.twig index aec38f3c3..98af21171 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Homepage/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Homepage/index.html.twig @@ -1,15 +1,15 @@
- {# vue component #}
- + {% include '@ChillMain/Homepage/fast_actions.html.twig' %}
+ {% block css %} {{ encore_entry_link_tags('page_homepage_widget') }} {% endblock %} {% block js %} {{ encore_entry_script_tags('page_homepage_widget') }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Menu/absence.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Menu/absence.html.twig new file mode 100644 index 000000000..3258b5ac1 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Menu/absence.html.twig @@ -0,0 +1,43 @@ +{% extends '@ChillMain/Admin/layout.html.twig' %} + +{% block title %} + {{ 'absence.My absence'|trans }} +{% endblock title %} + +{% block content %} + +
+

{{ 'absence.My absence'|trans }}

+ + {% if user.absenceStart is not null %} +
+

{{ 'absence.You are listed as absent, as of'|trans }} {{ user.absenceStart|format_date('long') }}

+ +
+ {% else %} +
+

{{ 'absence.No absence listed'|trans }}

+
+
+ {{ form_start(form) }} + {{ form_row(form.absenceStart) }} + + + + {{ form_end(form) }} +
+ {% endif %} +
+ +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig index 0f1168113..b57e17c06 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/User/index.html.twig @@ -2,20 +2,21 @@ {% block admin_content %} {% embed '@ChillMain/CRUD/_index.html.twig' %} - + {% block index_header %}

{{"Users"|trans}}

{% endblock %} - + {% block filter_order %}{{ filter_order|chill_render_filter_order_helper }}{% endblock %} - + {% block table_entities_thead_tr %} {{ 'Active'|trans }} + {{ 'absence.Is absent'|trans }} {{ 'Username'|trans }} {{ 'Datas'|trans }} {{ 'Actions'|trans }} {% endblock %} - + {% block table_entities_tbody %} {% for entity in entities %} @@ -26,6 +27,13 @@ {% endif %} + + {% if entity.isAbsent %} + + {% else %} + + {% endif %} + {# {% if entity.civility is not null %} @@ -64,13 +72,13 @@
  • - + {% if allow_change_password is same as(true) %}
  • {% endif %} - + {% if is_granted('ROLE_ALLOWED_TO_SWITCH') %}
  • @@ -81,9 +89,9 @@ {% endfor %} {% endblock %} - + {% block pagination %}{{ chill_pagination(paginator) }}{% endblock %} - + {% block list_actions %} {% endblock list_actions %} - + {% endembed %} {% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig index 319adad81..92454be35 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig @@ -69,18 +69,26 @@ {% block content %} diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php index e0e8a782f..ea5ed2062 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/UserMenuBuilder.php @@ -78,6 +78,15 @@ class UserMenuBuilder implements LocalMenuBuilderInterface $nbNotifications = $this->notificationByUserCounter->countUnreadByUser($user); + //TODO add an icon? How exactly? For example a clock icon... + $menu + ->addChild($this->translator->trans('absence.Set absence date'), [ + 'route' => 'chill_main_user_absence_index', + ]) + ->setExtras([ + 'order' => -8888888, + ]); + $menu ->addChild( $this->translator->trans('notification.My notifications with counter', ['nb' => $nbNotifications]), diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php index a8607cc21..fde3a3d8e 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php @@ -31,6 +31,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware 'id' => '', 'username' => '', 'text' => '', + 'text_without_absent' => '', 'label' => '', 'email' => '', ]; @@ -82,11 +83,13 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware 'id' => $object->getId(), 'username' => $object->getUsername(), 'text' => $this->userRender->renderString($object, []), + 'text_without_absent' => $this->userRender->renderString($object, ['absence' => false]), 'label' => $object->getLabel(), 'email' => (string) $object->getEmail(), 'user_job' => $this->normalizer->normalize($object->getUserJob(), $format, $userJobContext), 'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext), 'main_scope' => $this->normalizer->normalize($object->getMainScope(), $format, $scopeContext), + 'isAbsent' => $object->isAbsent(), ]; if ('docgen' === $format) { diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php b/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php index 141de2649..6780db742 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php @@ -13,8 +13,10 @@ namespace Chill\MainBundle\Templating\Entity; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Templating\TranslatableStringHelper; -use Symfony\Component\Templating\EngineInterface; +use DateTimeImmutable; +use Symfony\Component\Templating\EngineInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use function array_merge; class UserRender implements ChillEntityRenderInterface @@ -22,16 +24,20 @@ class UserRender implements ChillEntityRenderInterface public const DEFAULT_OPTIONS = [ 'main_scope' => true, 'user_job' => true, + 'absence' => true, ]; private EngineInterface $engine; private TranslatableStringHelper $translatableStringHelper; - public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine) + private TranslatorInterface $translator; + + public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine, TranslatorInterface $translator) { $this->translatableStringHelper = $translatableStringHelper; $this->engine = $engine; + $this->translator = $translator; } public function renderBox($entity, array $options): string @@ -63,6 +69,10 @@ class UserRender implements ChillEntityRenderInterface ->localize($entity->getMainScope()->getName()) . ')'; } + if ($entity->isAbsent() && $opts['absence']) { + $str .= ' (' . $this->translator->trans('absence.Absent') . ')'; + } + return $str; } diff --git a/src/Bundle/ChillMainBundle/config/services/form.yaml b/src/Bundle/ChillMainBundle/config/services/form.yaml index 8157c27e9..232e81dd6 100644 --- a/src/Bundle/ChillMainBundle/config/services/form.yaml +++ b/src/Bundle/ChillMainBundle/config/services/form.yaml @@ -138,9 +138,8 @@ services: autowire: true autoconfigure: true - Chill\MainBundle\Form\RegroupmentType: - autowire: true - autoconfigure: true + Chill\MainBundle\Form\AbsenceType: ~ + Chill\MainBundle\Form\RegroupmentType: ~ Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer: ~ Chill\MainBundle\Form\DataTransformer\IdToUserDataTransformer: ~ diff --git a/src/Bundle/ChillMainBundle/migrations/Version20230111160610.php b/src/Bundle/ChillMainBundle/migrations/Version20230111160610.php new file mode 100644 index 000000000..c00a3f521 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20230111160610.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE users DROP absenceStart'); + } + + public function getDescription(): string + { + return 'Add absence property to user'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE users ADD absenceStart TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN users.absenceStart IS \'(DC2Type:datetime_immutable)\''); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 63e93891f..f01976e87 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -584,3 +584,17 @@ saved_export: Export is deleted: L'export est supprimé Saved export is saved!: L'export est enregistré Created on %date%: Créé le %date% + + +absence: + # single letter for absence + A: A + My absence: Mon absence + Unset absence: Supprimer la date d'absence + Set absence date: Indiquer une date d'absence + Absence start: Absent à partir du + Absent: Absent + You are marked as being absent: Vous êtes indiqué absent. + You are listed as absent, as of: Votre absence est indiquée à partir du + No absence listed: Aucune absence indiquée. + Is absent: Absent? diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue index 7e3b45103..dcade7e0f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue @@ -22,7 +22,12 @@ {{ $t('course.open_at') }}{{ $d(accompanyingCourse.openingDate.datetime, 'text') }} - {{ $t('course.referrer') }}: {{ accompanyingCourse.user.text }} + {{ $t('course.referrer') }}:  + {{ accompanyingCourse.user.text }} + @@ -59,13 +64,15 @@ import ToggleFlags from './Banner/ToggleFlags'; import SocialIssue from './Banner/SocialIssue.vue'; import PersonsAssociated from './Banner/PersonsAssociated.vue'; +import UserRenderBoxBadge from 'ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue'; export default { name: 'Banner', components: { ToggleFlags, SocialIssue, - PersonsAssociated + PersonsAssociated, + UserRenderBoxBadge, }, computed: { accompanyingCourse() { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js index 44238b0c7..170e19bf9 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js @@ -29,7 +29,8 @@ const appMessages = { emergency: "urgent", confidential: "confidentiel", regular: "régulier", - occasional: "ponctuel" + occasional: "ponctuel", + absent: "Absent", }, origin: { title: "Origine de la demande", diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue index 7a8b302c7..a549e7970 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue @@ -1,9 +1,7 @@