mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-20 14:43:49 +00:00
Merge conflicts fixed
This commit is contained in:
@@ -31,6 +31,7 @@ use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
||||
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
|
||||
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
@@ -56,6 +57,8 @@ class ChillMainBundle extends Bundle
|
||||
->addTag('chill_main.notification_handler');
|
||||
$container->registerForAutoconfiguration(NotificationCounterInterface::class)
|
||||
->addTag('chill.count_notification.user');
|
||||
$container->registerForAutoconfiguration(EntityWorkflowHandlerInterface::class)
|
||||
->addTag('chill_main.workflow_handler');
|
||||
|
||||
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
||||
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
||||
|
@@ -24,6 +24,7 @@ class LocationTypeApiController extends ApiController
|
||||
$query->andWhere(
|
||||
$query->expr()->andX(
|
||||
$query->expr()->eq('e.availableForUsers', "'TRUE'"),
|
||||
$query->expr()->eq('e.editableByUsers', "'TRUE'"),
|
||||
$query->expr()->eq('e.active', "'TRUE'"),
|
||||
)
|
||||
);
|
||||
|
@@ -13,13 +13,19 @@ namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Repository\NotificationRepository;
|
||||
use Chill\MainBundle\Security\Authorization\NotificationVoter;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
use Chill\MainBundle\Serializer\Model\Counter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
@@ -29,12 +35,26 @@ class NotificationApiController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private NotificationRepository $notificationRepository;
|
||||
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
|
||||
private Security $security;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, Security $security)
|
||||
{
|
||||
private SerializerInterface $serializer;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
NotificationRepository $notificationRepository,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
Security $security,
|
||||
SerializerInterface $serializer
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->notificationRepository = $notificationRepository;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
$this->security = $security;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +73,37 @@ class NotificationApiController
|
||||
return $this->markAs('unread', $notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/my/unread")
|
||||
*/
|
||||
public function myUnreadNotifications(Request $request): JsonResponse
|
||||
{
|
||||
$total = $this->notificationRepository->countUnreadByUser($this->security->getUser());
|
||||
|
||||
if ($request->query->getBoolean('countOnly')) {
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize(new Counter($total), 'json', ['groups' => ['read']]),
|
||||
JsonResponse::HTTP_OK,
|
||||
[],
|
||||
true
|
||||
);
|
||||
}
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
$notifications = $this->notificationRepository->findUnreadByUser(
|
||||
$this->security->getUser(),
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
$collection = new Collection($notifications, $paginator);
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($collection, 'json', ['groups' => ['read']]),
|
||||
JsonResponse::HTTP_OK,
|
||||
[],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private function markAs(string $target, Notification $notification): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted(NotificationVoter::NOTIFICATION_TOGGLE_READ_STATUS, $notification)) {
|
||||
|
118
src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php
Normal file
118
src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?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\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use LogicException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
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\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
class WorkflowApiController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private Security $security;
|
||||
|
||||
public function __construct(Security $security, EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/api/1.0/main/workflow/{id}/subscribe", methods={"POST"})
|
||||
*/
|
||||
public function subscribe(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||
{
|
||||
return $this->handleSubscription($entityWorkflow, $request, 'subscribe');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/api/1.0/main/workflow/{id}/unsubscribe", methods={"POST"})
|
||||
*/
|
||||
public function unsubscribe(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||
{
|
||||
return $this->handleSubscription($entityWorkflow, $request, 'unsubscribe');
|
||||
}
|
||||
|
||||
private function handleSubscription(EntityWorkflow $entityWorkflow, Request $request, string $action): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if (!$request->query->has('subscribe')) {
|
||||
throw new BadRequestHttpException('missing subscribe parameter');
|
||||
}
|
||||
|
||||
$user = $this->security->getUser();
|
||||
|
||||
switch ($request->query->get('subscribe')) {
|
||||
case 'final':
|
||||
switch ($action) {
|
||||
case 'subscribe':
|
||||
$entityWorkflow->addSubscriberToFinal($user);
|
||||
|
||||
break;
|
||||
|
||||
case 'unsubscribe':
|
||||
$entityWorkflow->removeSubscriberToFinal($user);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'step':
|
||||
switch ($action) {
|
||||
case 'subscribe':
|
||||
$entityWorkflow->addSubscriberToStep($user);
|
||||
|
||||
break;
|
||||
|
||||
case 'unsubscribe':
|
||||
$entityWorkflow->removeSubscriberToStep($user);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BadRequestHttpException('subscribe parameter must be equal to "step" or "final"');
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(
|
||||
[
|
||||
'step' => $entityWorkflow->isUserSubscribedToStep($user),
|
||||
'final' => $entityWorkflow->isUserSubscribedToFinal($user),
|
||||
],
|
||||
JsonResponse::HTTP_OK,
|
||||
[],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
268
src/Bundle/ChillMainBundle/Controller/WorkflowController.php
Normal file
268
src/Bundle/ChillMainBundle/Controller/WorkflowController.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?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\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowComment;
|
||||
use Chill\MainBundle\Form\EntityWorkflowCommentType;
|
||||
use Chill\MainBundle\Form\WorkflowStepType;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
||||
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\Validator\StepDestValid;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
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\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\TransitionBlocker;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function count;
|
||||
|
||||
class WorkflowController extends AbstractController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private EntityWorkflowManager $entityWorkflowManager;
|
||||
|
||||
private EntityWorkflowRepository $entityWorkflowRepository;
|
||||
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
|
||||
private Registry $registry;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private ValidatorInterface $validator;
|
||||
|
||||
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator)
|
||||
{
|
||||
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||
$this->entityWorkflowRepository = $entityWorkflowRepository;
|
||||
$this->validator = $validator;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
$this->registry = $registry;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/create", name="chill_main_workflow_create")
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
if (!$request->query->has('entityClass')) {
|
||||
throw new BadRequestHttpException('Missing entityClass parameter');
|
||||
}
|
||||
|
||||
if (!$request->query->has('entityId')) {
|
||||
throw new BadRequestHttpException('missing entityId parameter');
|
||||
}
|
||||
|
||||
if (!$request->query->has('workflow')) {
|
||||
throw new BadRequestHttpException('missing workflow parameter');
|
||||
}
|
||||
|
||||
$entityWorkflow = new EntityWorkflow();
|
||||
$entityWorkflow
|
||||
->setRelatedEntityClass($request->query->get('entityClass'))
|
||||
->setRelatedEntityId($request->query->getInt('entityId'))
|
||||
->setWorkflowName($request->query->get('workflow'));
|
||||
|
||||
$errors = $this->validator->validate($entityWorkflow, null, ['creation']);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
$msg = [];
|
||||
|
||||
foreach ($errors as $error) {
|
||||
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
|
||||
$msg[] = $error->getMessage();
|
||||
}
|
||||
|
||||
return new Response(implode("\n", $msg), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(EntityWorkflowVoter::CREATE, $entityWorkflow);
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entityWorkflow);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest")
|
||||
*/
|
||||
public function myWorkflowsDest(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
|
||||
$total = $this->entityWorkflowRepository->countByDest($this->getUser());
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
$workflows = $this->entityWorkflowRepository->findByDest(
|
||||
$this->getUser(),
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/list.html.twig',
|
||||
[
|
||||
'workflows' => $this->buildHandler($workflows),
|
||||
'paginator' => $paginator,
|
||||
'step' => 'dest',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/list/subscribed", name="chill_main_workflow_list_subscribed")
|
||||
*/
|
||||
public function myWorkflowsSubscribed(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
|
||||
$total = $this->entityWorkflowRepository->countBySubscriber($this->getUser());
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
$workflows = $this->entityWorkflowRepository->findBySubscriber(
|
||||
$this->getUser(),
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/list.html.twig',
|
||||
[
|
||||
'workflows' => $this->buildHandler($workflows),
|
||||
'paginator' => $paginator,
|
||||
'step' => 'subscribed',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/{id}/show", name="chill_main_workflow_show")
|
||||
*/
|
||||
public function show(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(EntityWorkflowVoter::SEE, $entityWorkflow);
|
||||
|
||||
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
|
||||
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||
|
||||
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
|
||||
// possible transition
|
||||
$transitionForm = $this->createForm(
|
||||
WorkflowStepType::class,
|
||||
$entityWorkflow->getCurrentStep(),
|
||||
['transition' => true, 'entity_workflow' => $entityWorkflow]
|
||||
);
|
||||
|
||||
$transitionForm->handleRequest($request);
|
||||
|
||||
if ($transitionForm->isSubmitted() && $transitionForm->isValid()) {
|
||||
if (!$workflow->can($entityWorkflow, $transition = $transitionForm['transition']->getData()->getName())) {
|
||||
$blockers = $workflow->buildTransitionBlockerList($entityWorkflow, $transition);
|
||||
$msgs = array_map(function (TransitionBlocker $tb) {
|
||||
return $this->translator->trans(
|
||||
$tb->getMessage(),
|
||||
$tb->getParameters()
|
||||
);
|
||||
}, iterator_to_array($blockers));
|
||||
|
||||
throw $this->createAccessDeniedException(
|
||||
sprintf(
|
||||
"not allowed to apply transition {$transition}: %s",
|
||||
implode(', ', $msgs)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$workflow->apply($entityWorkflow, $transition);
|
||||
|
||||
foreach ($transitionForm['future_dest_users']->getData() as $user) {
|
||||
$entityWorkflow->getCurrentStep()->addDestUser($user);
|
||||
}
|
||||
|
||||
$errors = $this->validator->validate(
|
||||
$entityWorkflow->getCurrentStep(),
|
||||
new StepDestValid()
|
||||
);
|
||||
|
||||
if (count($errors) === 0) {
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
}
|
||||
|
||||
return new Response((string) $errors, Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) {
|
||||
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
$commentForm = $this->createForm(EntityWorkflowCommentType::class, $newComment = new EntityWorkflowComment());
|
||||
$commentForm->handleRequest($request);
|
||||
|
||||
if ($commentForm->isSubmitted() && $commentForm->isValid()) {
|
||||
$this->entityManager->persist($newComment);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('workflow.Comment added'));
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
} elseif ($commentForm->isSubmitted() && !$commentForm->isValid()) {
|
||||
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||
}
|
||||
*/
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/index.html.twig',
|
||||
[
|
||||
'handler_template' => $handler->getTemplate($entityWorkflow),
|
||||
'handler_template_title' => $handler->getTemplateTitle($entityWorkflow),
|
||||
'handler_template_data' => $handler->getTemplateData($entityWorkflow),
|
||||
'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null,
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
'transition_form_errors' => $errors ?? [],
|
||||
//'comment_form' => $commentForm->createView(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function buildHandler(array $workflows): array
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
foreach ($workflows as $workflow) {
|
||||
$handler = $this->entityWorkflowManager->getHandler($workflow);
|
||||
$lines[] = [
|
||||
'handler' => $handler,
|
||||
'entity_workflow' => $workflow,
|
||||
];
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
}
|
@@ -168,6 +168,7 @@ class ChillMainExtension extends Extension implements
|
||||
$loader->load('services/timeline.yaml');
|
||||
$loader->load('services/search.yaml');
|
||||
$loader->load('services/serializer.yaml');
|
||||
$loader->load('services/mailer.yaml');
|
||||
|
||||
$this->configureCruds($container, $config['cruds'], $config['apis'], $loader);
|
||||
}
|
||||
@@ -391,6 +392,26 @@ class ChillMainExtension extends Extension implements
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => \Chill\MainBundle\Entity\UserJob::class,
|
||||
'name' => 'user_job',
|
||||
'base_path' => '/api/1.0/main/user-job',
|
||||
'base_role' => 'ROLE_USER',
|
||||
'actions' => [
|
||||
'_index' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
],
|
||||
],
|
||||
'_entity' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'controller' => \Chill\MainBundle\Controller\AddressReferenceAPIController::class,
|
||||
'class' => \Chill\MainBundle\Entity\AddressReference::class,
|
||||
|
@@ -0,0 +1,56 @@
|
||||
<?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\MainBundle\Doctrine\Model;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
trait TrackCreationTrait
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": NULL})
|
||||
*/
|
||||
private ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private ?User $createdBy = null;
|
||||
|
||||
public function getCreatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getCreatedBy(): ?User
|
||||
{
|
||||
return $this->createdBy;
|
||||
}
|
||||
|
||||
public function setCreatedAt(DateTimeInterface $datetime): self
|
||||
{
|
||||
$this->createdAt = $datetime instanceof DateTime ? DateTimeImmutable::createFromMutable($datetime) : $datetime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCreatedBy(User $user): self
|
||||
{
|
||||
$this->createdBy = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
<?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\MainBundle\Doctrine\Model;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
trait TrackUpdateTrait
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": NULL})
|
||||
*/
|
||||
private ?DateTimeImmutable $updatedAt = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private ?User $updatedBy = null;
|
||||
|
||||
public function getUpdatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function getUpdatedBy(): ?User
|
||||
{
|
||||
return $this->updatedBy;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(DateTimeInterface $datetime): self
|
||||
{
|
||||
$this->updatedAt = $datetime instanceof DateTime ? DateTimeImmutable::createFromMutable($datetime) : $datetime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUpdatedBy(User $user): self
|
||||
{
|
||||
$this->updatedBy = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -42,10 +42,16 @@ class Address
|
||||
*/
|
||||
private $buildingName;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private bool $confidential = false;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $corridor;
|
||||
@@ -78,7 +84,7 @@ class Address
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $flat;
|
||||
@@ -86,7 +92,7 @@ class Address
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $floor;
|
||||
@@ -143,7 +149,7 @@ class Address
|
||||
/**
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $steps;
|
||||
@@ -192,6 +198,7 @@ class Address
|
||||
return (new Address())
|
||||
->setAddressReference($original->getAddressReference())
|
||||
->setBuildingName($original->getBuildingName())
|
||||
->setConfidential($original->getConfidential())
|
||||
->setCorridor($original->getCorridor())
|
||||
->setCustoms($original->getCustoms())
|
||||
->setDistribution($original->getDistribution())
|
||||
@@ -229,6 +236,11 @@ class Address
|
||||
return $this->buildingName;
|
||||
}
|
||||
|
||||
public function getConfidential(): bool
|
||||
{
|
||||
return $this->confidential;
|
||||
}
|
||||
|
||||
public function getCorridor(): ?string
|
||||
{
|
||||
return $this->corridor;
|
||||
@@ -369,6 +381,13 @@ class Address
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setConfidential(bool $confidential): self
|
||||
{
|
||||
$this->confidential = $confidential;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCorridor(?string $corridor): self
|
||||
{
|
||||
$this->corridor = $corridor;
|
||||
|
@@ -20,10 +20,9 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
class CommentEmbeddable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
private $comment;
|
||||
private ?string $comment = null;
|
||||
|
||||
/**
|
||||
* @var DateTime
|
||||
@@ -39,10 +38,7 @@ class CommentEmbeddable
|
||||
*/
|
||||
private $userId;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
@@ -68,9 +64,6 @@ class CommentEmbeddable
|
||||
return empty($this->getComment());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setComment(?string $comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
@@ -64,7 +64,7 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
* @Serializer\Groups({"read", "write", "docgen:read"})
|
||||
*/
|
||||
private ?string $email = null;
|
||||
|
||||
|
@@ -67,6 +67,12 @@ class LocationType
|
||||
*/
|
||||
private ?string $defaultFor = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private bool $editableByUsers = true;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
@@ -107,6 +113,11 @@ class LocationType
|
||||
return $this->defaultFor;
|
||||
}
|
||||
|
||||
public function getEditableByUsers(): ?bool
|
||||
{
|
||||
return $this->editableByUsers;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -152,6 +163,13 @@ class LocationType
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEditableByUsers(bool $editableByUsers): self
|
||||
{
|
||||
$this->editableByUsers = $editableByUsers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTitle(array $title): self
|
||||
{
|
||||
$this->title = $title;
|
||||
|
@@ -17,11 +17,15 @@ use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(
|
||||
* name="chill_main_notification",
|
||||
* indexes={
|
||||
* @ORM\Index(name="chill_main_notification_related_entity_idx", columns={"relatedentityclass", "relatedentityid"})
|
||||
* }
|
||||
* )
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*/
|
||||
@@ -32,6 +36,7 @@ class Notification implements TrackUpdateInterface
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=User::class)
|
||||
* @ORM\JoinTable(name="chill_main_notification_addresses_user")
|
||||
* @Assert\Count(min="1", minMessage="notification.At least one addressee")
|
||||
*/
|
||||
private Collection $addressees;
|
||||
|
||||
@@ -80,6 +85,7 @@ class Notification implements TrackUpdateInterface
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", options={"default": ""})
|
||||
* @Assert\NotBlank(message="notification.Title must be defined")
|
||||
*/
|
||||
private string $title = '';
|
||||
|
||||
@@ -286,9 +292,9 @@ class Notification implements TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMessage(string $message): self
|
||||
public function setMessage(?string $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->message = (string) $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -314,9 +320,9 @@ class Notification implements TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): Notification
|
||||
public function setTitle(?string $title): Notification
|
||||
{
|
||||
$this->title = $title;
|
||||
$this->title = (string) $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
166
src/Bundle/ChillMainBundle/Entity/ResidentialAddress.php
Normal file
166
src/Bundle/ChillMainBundle/Entity/ResidentialAddress.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?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\MainBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||
use Chill\MainBundle\Repository\ResidentialAddressRepository;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=ResidentialAddressRepository::class)
|
||||
* @ORM\Table(name="chill_main_residential_address")
|
||||
*/
|
||||
class ResidentialAddress
|
||||
{
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Address::class)
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private ?Address $address = null;
|
||||
|
||||
/**
|
||||
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="residentialAddressComment_")
|
||||
*/
|
||||
private CommentEmbeddable $comment;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true)
|
||||
*/
|
||||
private ?DateTimeImmutable $endDate = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Person::class)
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private ?Person $hostPerson = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private ?ThirdParty $hostThirdParty = null;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Person::class)
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
*/
|
||||
private Person $person;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable")
|
||||
*/
|
||||
private ?DateTimeImmutable $startDate = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comment = new CommentEmbeddable();
|
||||
}
|
||||
|
||||
public function getAddress(): ?Address
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function getComment(): CommentEmbeddable
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function getEndDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getHostPerson(): ?Person
|
||||
{
|
||||
return $this->hostPerson;
|
||||
}
|
||||
|
||||
public function getHostThirdParty(): ?ThirdParty
|
||||
{
|
||||
return $this->hostThirdParty;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getPerson(): ?Person
|
||||
{
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
public function getStartDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function setAddress(?Address $address): self
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setComment(CommentEmbeddable $comment): self
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEndDate(?DateTimeImmutable $endDate): self
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHostPerson(?Person $hostPerson): self
|
||||
{
|
||||
$this->hostPerson = $hostPerson;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHostThirdParty(?ThirdParty $hostThirdParty): self
|
||||
{
|
||||
$this->hostThirdParty = $hostThirdParty;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPerson(?Person $person): self
|
||||
{
|
||||
$this->person = $person;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStartDate(DateTimeImmutable $startDate): self
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -97,6 +97,11 @@ class User implements AdvancedUserInterface
|
||||
*/
|
||||
private ?Center $mainCenter = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Location::class)
|
||||
*/
|
||||
private ?Location $mainLocation = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Scope::class)
|
||||
*/
|
||||
@@ -228,6 +233,11 @@ class User implements AdvancedUserInterface
|
||||
return $this->mainCenter;
|
||||
}
|
||||
|
||||
public function getMainLocation(): ?Location
|
||||
{
|
||||
return $this->mainLocation;
|
||||
}
|
||||
|
||||
public function getMainScope(): ?Scope
|
||||
{
|
||||
return $this->mainScope;
|
||||
@@ -405,6 +415,13 @@ class User implements AdvancedUserInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMainLocation(?Location $mainLocation): User
|
||||
{
|
||||
$this->mainLocation = $mainLocation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMainScope(?Scope $mainScope): User
|
||||
{
|
||||
$this->mainScope = $mainScope;
|
||||
|
461
src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php
Normal file
461
src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php
Normal file
@@ -0,0 +1,461 @@
|
||||
<?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\MainBundle\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Iterator;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use function count;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table("chill_main_workflow_entity")
|
||||
* @EntityWorkflowCreation(groups={"creation"})
|
||||
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "entity_workflow": EntityWorkflow::class
|
||||
* })
|
||||
*/
|
||||
class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
use TrackUpdateTrait;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=EntityWorkflowComment::class, mappedBy="entityWorkflow", orphanRemoval=true)
|
||||
*
|
||||
* @var Collection|EntityWorkflowComment[]
|
||||
*/
|
||||
private Collection $comments;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
private string $relatedEntityClass = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private int $relatedEntityId;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=EntityWorkflowStep::class, mappedBy="entityWorkflow", orphanRemoval=true, cascade={"persist"})
|
||||
* @ORM\OrderBy({"transitionAt": "ASC", "id": "ASC"})
|
||||
*
|
||||
* @var Collection|EntityWorkflowStep[]
|
||||
*/
|
||||
private Collection $steps;
|
||||
|
||||
/**
|
||||
* @var null|array|EntityWorkflowStep[]
|
||||
*/
|
||||
private ?array $stepsChainedCache = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=User::class)
|
||||
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_final")
|
||||
*
|
||||
* @var Collection|User[]
|
||||
*/
|
||||
private Collection $subscriberToFinal;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=User::class)
|
||||
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_step")
|
||||
*
|
||||
* @var Collection|User[]
|
||||
*/
|
||||
private Collection $subscriberToStep;
|
||||
|
||||
/**
|
||||
* a step which will store all the transition data.
|
||||
*/
|
||||
private ?EntityWorkflowStep $transitionningStep = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text")
|
||||
*/
|
||||
private string $workflowName;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->subscriberToFinal = new ArrayCollection();
|
||||
$this->subscriberToStep = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
$this->steps = new ArrayCollection();
|
||||
|
||||
$initialStep = new EntityWorkflowStep();
|
||||
$initialStep
|
||||
->setCurrentStep('initial');
|
||||
$this->addStep($initialStep);
|
||||
}
|
||||
|
||||
public function addComment(EntityWorkflowComment $comment): self
|
||||
{
|
||||
if (!$this->comments->contains($comment)) {
|
||||
$this->comments[] = $comment;
|
||||
$comment->setEntityWorkflow($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal You should prepare a step and run a workflow transition instead of manually adding a step
|
||||
*/
|
||||
public function addStep(EntityWorkflowStep $step): self
|
||||
{
|
||||
if (!$this->steps->contains($step)) {
|
||||
$this->steps[] = $step;
|
||||
$step->setEntityWorkflow($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addSubscriberToFinal(User $user): self
|
||||
{
|
||||
if (!$this->subscriberToFinal->contains($user)) {
|
||||
$this->subscriberToFinal[] = $user;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addSubscriberToStep(User $user): self
|
||||
{
|
||||
if (!$this->subscriberToStep->contains($user)) {
|
||||
$this->subscriberToStep[] = $user;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getComments(): Collection
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function getCurrentStep(): ?EntityWorkflowStep
|
||||
{
|
||||
$step = $this->steps->last();
|
||||
|
||||
if (false !== $step) {
|
||||
return $step;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getCurrentStepChained(): ?EntityWorkflowStep
|
||||
{
|
||||
$steps = $this->getStepsChained();
|
||||
$currentStep = $this->getCurrentStep();
|
||||
|
||||
foreach ($steps as $step) {
|
||||
if ($step === $currentStep) {
|
||||
return $step;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getCurrentStepCreatedAt(): ?DateTimeInterface
|
||||
{
|
||||
if (null !== $previous = $this->getPreviousStepIfAny()) {
|
||||
return $previous->getTransitionAt();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getCurrentStepCreatedBy(): ?User
|
||||
{
|
||||
if (null !== $previous = $this->getPreviousStepIfAny()) {
|
||||
return $previous->getTransitionBy();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getRelatedEntityClass(): string
|
||||
{
|
||||
return $this->relatedEntityClass;
|
||||
}
|
||||
|
||||
public function getRelatedEntityId(): int
|
||||
{
|
||||
return $this->relatedEntityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used by MarkingStore.
|
||||
*
|
||||
* get a string representation of the step
|
||||
*/
|
||||
public function getStep(): string
|
||||
{
|
||||
return $this->getCurrentStep()->getCurrentStep();
|
||||
}
|
||||
|
||||
public function getStepAfter(EntityWorkflowStep $step): ?EntityWorkflowStep
|
||||
{
|
||||
$iterator = $this->steps->getIterator();
|
||||
|
||||
if ($iterator instanceof Iterator) {
|
||||
$iterator->rewind();
|
||||
|
||||
while ($iterator->valid()) {
|
||||
$curStep = $iterator->current();
|
||||
|
||||
if ($curStep === $step) {
|
||||
$iterator->next();
|
||||
|
||||
if ($iterator->valid()) {
|
||||
return $iterator->current();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|Collection
|
||||
*/
|
||||
public function getSteps()
|
||||
{
|
||||
return $this->steps;
|
||||
}
|
||||
|
||||
public function getStepsChained(): array
|
||||
{
|
||||
if (is_array($this->stepsChainedCache)) {
|
||||
return $this->stepsChainedCache;
|
||||
}
|
||||
|
||||
$iterator = $this->steps->getIterator();
|
||||
$current = null;
|
||||
$steps = [];
|
||||
|
||||
$iterator->rewind();
|
||||
|
||||
do {
|
||||
$previous = $current;
|
||||
$current = $iterator->current();
|
||||
$steps[] = $current;
|
||||
|
||||
$current->setPrevious($previous);
|
||||
|
||||
$iterator->next();
|
||||
|
||||
if ($iterator->valid()) {
|
||||
$current->setNext($iterator->current());
|
||||
} else {
|
||||
$current->setNext(null);
|
||||
}
|
||||
} while ($iterator->valid());
|
||||
|
||||
$this->stepsChainedCache = $steps;
|
||||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|Collection
|
||||
*/
|
||||
public function getSubscriberToFinal()
|
||||
{
|
||||
return $this->subscriberToFinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|Collection
|
||||
*/
|
||||
public function getSubscriberToStep()
|
||||
{
|
||||
return $this->subscriberToStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the step which is transitionning. Should be called only by event which will
|
||||
* concern the transition.
|
||||
*/
|
||||
public function getTransitionningStep(): ?EntityWorkflowStep
|
||||
{
|
||||
return $this->transitionningStep;
|
||||
}
|
||||
|
||||
public function getWorkflowName(): string
|
||||
{
|
||||
return $this->workflowName;
|
||||
}
|
||||
|
||||
public function isFinal(): bool
|
||||
{
|
||||
$steps = $this->getStepsChained();
|
||||
|
||||
if (1 === count($steps)) {
|
||||
// the initial step cannot be finalized
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var EntityWorkflowStep $last */
|
||||
$last = end($steps);
|
||||
|
||||
return $last->isFinal();
|
||||
}
|
||||
|
||||
public function isFreeze(): bool
|
||||
{
|
||||
$steps = $this->getStepsChained();
|
||||
|
||||
if (1 === count($steps)) {
|
||||
// the initial step cannot be finalized
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var EntityWorkflowStep $last */
|
||||
$last = end($steps);
|
||||
|
||||
return $last->getPrevious()->isFreezeAfter();
|
||||
}
|
||||
|
||||
public function isUserSubscribedToFinal(User $user): bool
|
||||
{
|
||||
return $this->subscriberToFinal->contains($user);
|
||||
}
|
||||
|
||||
public function isUserSubscribedToStep(User $user): bool
|
||||
{
|
||||
return $this->subscriberToStep->contains($user);
|
||||
}
|
||||
|
||||
public function prepareStepBeforeTransition(EntityWorkflowStep $step): self
|
||||
{
|
||||
$this->transitionningStep = $step;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeComment(EntityWorkflowComment $comment): self
|
||||
{
|
||||
if ($this->comments->removeElement($comment)) {
|
||||
$comment->setEntityWorkflow(null);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeStep(EntityWorkflowStep $step): self
|
||||
{
|
||||
if ($this->steps->removeElement($step)) {
|
||||
$step->setEntityWorkflow(null);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeSubscriberToFinal(User $user): self
|
||||
{
|
||||
$this->subscriberToFinal->removeElement($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeSubscriberToStep(User $user): self
|
||||
{
|
||||
$this->subscriberToStep->removeElement($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRelatedEntityClass(string $relatedEntityClass): EntityWorkflow
|
||||
{
|
||||
$this->relatedEntityClass = $relatedEntityClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRelatedEntityId(int $relatedEntityId): EntityWorkflow
|
||||
{
|
||||
$this->relatedEntityId = $relatedEntityId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method use by marking store.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setStep(string $step): self
|
||||
{
|
||||
$newStep = new EntityWorkflowStep();
|
||||
$newStep->setCurrentStep($step);
|
||||
|
||||
// copy the freeze
|
||||
if ($this->getCurrentStep()->isFreezeAfter()) {
|
||||
$newStep->setFreezeAfter(true);
|
||||
}
|
||||
|
||||
$this->addStep($newStep);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setWorkflowName(string $workflowName): EntityWorkflow
|
||||
{
|
||||
$this->workflowName = $workflowName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getPreviousStepIfAny(): ?EntityWorkflowStep
|
||||
{
|
||||
if (1 === count($this->steps)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->steps->get($this->steps->count() - 2);
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
<?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\MainBundle\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table("chill_main_workflow_entity_comment")
|
||||
*/
|
||||
class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
use TrackUpdateTrait;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", options={"default": ""})
|
||||
*/
|
||||
private string $comment = '';
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="comments")
|
||||
*/
|
||||
private ?EntityWorkflow $entityWorkflow = null;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
public function getComment(): string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function getEntityWorkflow(): ?EntityWorkflow
|
||||
{
|
||||
return $this->entityWorkflow;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setComment(string $comment): self
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal use @see{EntityWorkflow::addComment}
|
||||
*/
|
||||
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): self
|
||||
{
|
||||
$this->entityWorkflow = $entityWorkflow;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -0,0 +1,336 @@
|
||||
<?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\MainBundle\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use function count;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table("chill_main_workflow_entity_step")
|
||||
*/
|
||||
class EntityWorkflowStep
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="text", options={"default": ""})
|
||||
*/
|
||||
private string $comment = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text")
|
||||
*/
|
||||
private ?string $currentStep = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private array $destEmail = [];
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=User::class)
|
||||
* @ORM\JoinTable(name="chill_main_workflow_entity_step_user")
|
||||
*/
|
||||
private Collection $destUser;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="steps")
|
||||
*/
|
||||
private ?EntityWorkflow $entityWorkflow = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean", options={"default": false})
|
||||
*/
|
||||
private bool $freezeAfter = false;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean", options={"default": false})
|
||||
*/
|
||||
private bool $isFinal = false;
|
||||
|
||||
/**
|
||||
* filled by @see{EntityWorkflow::getStepsChained}.
|
||||
*/
|
||||
private ?EntityWorkflowStep $next = null;
|
||||
|
||||
/**
|
||||
* filled by @see{EntityWorkflow::getStepsChained}.
|
||||
*/
|
||||
private ?EntityWorkflowStep $previous = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true, options={"default": null})
|
||||
*/
|
||||
private ?string $transitionAfter = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable")
|
||||
*/
|
||||
private ?DateTimeImmutable $transitionAt = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private ?User $transitionBy = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
private ?string $transitionByEmail = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->destUser = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function addDestEmail(string $email): self
|
||||
{
|
||||
if (!in_array($email, $this->destEmail, true)) {
|
||||
$this->destEmail[] = $email;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDestUser(User $user): self
|
||||
{
|
||||
if (!$this->destUser->contains($user)) {
|
||||
$this->destUser[] = $user;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getComment(): string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function getCurrentStep(): ?string
|
||||
{
|
||||
return $this->currentStep;
|
||||
}
|
||||
|
||||
public function getDestEmail(): array
|
||||
{
|
||||
return $this->destEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|Collection
|
||||
*/
|
||||
public function getDestUser()
|
||||
{
|
||||
return $this->destUser;
|
||||
}
|
||||
|
||||
public function getEntityWorkflow(): ?EntityWorkflow
|
||||
{
|
||||
return $this->entityWorkflow;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getNext(): ?EntityWorkflowStep
|
||||
{
|
||||
return $this->next;
|
||||
}
|
||||
|
||||
public function getPrevious(): ?EntityWorkflowStep
|
||||
{
|
||||
return $this->previous;
|
||||
}
|
||||
|
||||
public function getTransitionAfter(): ?string
|
||||
{
|
||||
return $this->transitionAfter;
|
||||
}
|
||||
|
||||
public function getTransitionAt(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->transitionAt;
|
||||
}
|
||||
|
||||
public function getTransitionBy(): ?User
|
||||
{
|
||||
return $this->transitionBy;
|
||||
}
|
||||
|
||||
public function getTransitionByEmail(): ?string
|
||||
{
|
||||
return $this->transitionByEmail;
|
||||
}
|
||||
|
||||
public function isFinal(): bool
|
||||
{
|
||||
return $this->isFinal;
|
||||
}
|
||||
|
||||
public function isFreezeAfter(): bool
|
||||
{
|
||||
return $this->freezeAfter;
|
||||
}
|
||||
|
||||
public function removeDestEmail(string $email): self
|
||||
{
|
||||
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {
|
||||
return $email !== $existing;
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDestUser(User $user): self
|
||||
{
|
||||
$this->destUser->removeElement($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setComment(?string $comment): EntityWorkflowStep
|
||||
{
|
||||
$this->comment = (string) $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCurrentStep(?string $currentStep): EntityWorkflowStep
|
||||
{
|
||||
$this->currentStep = $currentStep;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDestEmail(array $destEmail): EntityWorkflowStep
|
||||
{
|
||||
$this->destEmail = $destEmail;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal use @see(EntityWorkflow::addStep} instead
|
||||
*/
|
||||
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): EntityWorkflowStep
|
||||
{
|
||||
$this->entityWorkflow = $entityWorkflow;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep
|
||||
{
|
||||
$this->freezeAfter = $freezeAfter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setIsFinal(bool $isFinal): EntityWorkflowStep
|
||||
{
|
||||
$this->isFinal = $isFinal;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityWorkflowStep
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function setNext(?EntityWorkflowStep $next): self
|
||||
{
|
||||
$this->next = $next;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityWorkflowStep
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function setPrevious(?EntityWorkflowStep $previous): self
|
||||
{
|
||||
$this->previous = $previous;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTransitionAfter(?string $transitionAfter): EntityWorkflowStep
|
||||
{
|
||||
$this->transitionAfter = $transitionAfter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTransitionAt(?DateTimeImmutable $transitionAt): EntityWorkflowStep
|
||||
{
|
||||
$this->transitionAt = $transitionAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTransitionBy(?User $transitionBy): EntityWorkflowStep
|
||||
{
|
||||
$this->transitionBy = $transitionBy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTransitionByEmail(?string $transitionByEmail): EntityWorkflowStep
|
||||
{
|
||||
$this->transitionByEmail = $transitionByEmail;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Assert\Callback
|
||||
*
|
||||
* @param mixed $payload
|
||||
*/
|
||||
public function validateOnCreation(ExecutionContextInterface $context, $payload): void
|
||||
{
|
||||
return;
|
||||
|
||||
if ($this->isFinalizeAfter()) {
|
||||
if (0 !== count($this->getDestUser())) {
|
||||
$context->buildViolation('workflow.No dest users when the workflow is finalized')
|
||||
->atPath('finalizeAfter')
|
||||
->addViolation();
|
||||
}
|
||||
} else {
|
||||
if (0 === count($this->getDestUser())) {
|
||||
$context->buildViolation('workflow.The next step must count at least one dest')
|
||||
->atPath('finalizeAfter')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?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\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class EntityWorkflowCommentType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('comment', ChillTextareaType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -54,7 +54,6 @@ final class LocationFormType extends AbstractType
|
||||
'label' => 'Address',
|
||||
'use_valid_from' => false,
|
||||
'use_valid_to' => false,
|
||||
'mapped' => false,
|
||||
])
|
||||
->add(
|
||||
'active',
|
||||
|
@@ -39,6 +39,17 @@ final class LocationTypeType extends AbstractType
|
||||
'expanded' => true,
|
||||
]
|
||||
)
|
||||
->add(
|
||||
'editableByUsers',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => [
|
||||
'Yes' => true,
|
||||
'No' => false,
|
||||
],
|
||||
'expanded' => true,
|
||||
]
|
||||
)
|
||||
->add(
|
||||
'addressRequired',
|
||||
ChoiceType::class,
|
||||
|
@@ -12,14 +12,18 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Form\Type\DataTransformer;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function array_key_exists;
|
||||
|
||||
class UserToJsonTransformer implements DataTransformerInterface
|
||||
class EntityToJsonTransformer implements DataTransformerInterface
|
||||
{
|
||||
private DenormalizerInterface $denormalizer;
|
||||
|
||||
@@ -27,23 +31,36 @@ class UserToJsonTransformer implements DataTransformerInterface
|
||||
|
||||
private SerializerInterface $serializer;
|
||||
|
||||
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, bool $multiple)
|
||||
private string $type;
|
||||
|
||||
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, bool $multiple, string $type)
|
||||
{
|
||||
$this->denormalizer = $denormalizer;
|
||||
$this->serializer = $serializer;
|
||||
$this->multiple = $multiple;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function reverseTransform($value)
|
||||
{
|
||||
$denormalized = json_decode($value, true);
|
||||
|
||||
if ($this->multiple) {
|
||||
if (null === $denormalized) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ($item) { return $this->denormalizeOne($item); },
|
||||
json_decode($value, true)
|
||||
$denormalized
|
||||
);
|
||||
}
|
||||
|
||||
return $this->denormalizeOne(json_decode($value, true));
|
||||
if ('' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->denormalizeOne($denormalized);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +77,7 @@ class UserToJsonTransformer implements DataTransformerInterface
|
||||
]);
|
||||
}
|
||||
|
||||
private function denormalizeOne(array $item): User
|
||||
private function denormalizeOne(array $item)
|
||||
{
|
||||
if (!array_key_exists('type', $item)) {
|
||||
throw new TransformationFailedException('the key "type" is missing on element');
|
||||
@@ -70,10 +87,30 @@ class UserToJsonTransformer implements DataTransformerInterface
|
||||
throw new TransformationFailedException('the key "id" is missing on element');
|
||||
}
|
||||
|
||||
switch ($this->type) {
|
||||
case 'user':
|
||||
$class = User::class;
|
||||
|
||||
break;
|
||||
|
||||
case 'person':
|
||||
$class = Person::class;
|
||||
|
||||
break;
|
||||
|
||||
case 'thirdparty':
|
||||
$class = ThirdParty::class;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnexpectedValueException('This type is not supported');
|
||||
}
|
||||
|
||||
return
|
||||
$this->denormalizer->denormalize(
|
||||
['type' => $item['type'], 'id' => $item['id']],
|
||||
User::class,
|
||||
$class,
|
||||
'json',
|
||||
[AbstractNormalizer::GROUPS => ['read']],
|
||||
);
|
@@ -12,7 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Form\Type\DataTransformer\UserToJsonTransformer;
|
||||
use Chill\MainBundle\Form\Type\DataTransformer\EntityToJsonTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
@@ -38,7 +38,7 @@ class PickUserDynamicType extends AbstractType
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->addViewTransformer(new UserToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple']));
|
||||
$builder->addViewTransformer(new EntityToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple'], 'user'));
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
@@ -58,6 +58,6 @@ class PickUserDynamicType extends AbstractType
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'pick_user_dynamic';
|
||||
return 'pick_entity_dynamic';
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,73 @@
|
||||
<?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\MainBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Entity\ResidentialAddress;
|
||||
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
|
||||
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ResidentialAddressType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('startDate', DateType::class, [
|
||||
'required' => true,
|
||||
'input' => 'datetime_immutable',
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('endDate', DateType::class, [
|
||||
'required' => false,
|
||||
'input' => 'datetime_immutable',
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('comment', CommentType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
if ('person' === $options['kind']) {
|
||||
$builder
|
||||
->add('hostPerson', PickPersonDynamicType::class, [
|
||||
'label' => 'Person',
|
||||
]);
|
||||
}
|
||||
|
||||
if ('thirdparty' === $options['kind']) {
|
||||
$builder
|
||||
->add('hostThirdParty', PickThirdpartyDynamicType::class, [
|
||||
'label' => 'Third party',
|
||||
]);
|
||||
}
|
||||
|
||||
if ('address' === $options['kind']) {
|
||||
$builder
|
||||
->add('address', PickAddressType::class, [
|
||||
'required' => false,
|
||||
'label' => 'Address',
|
||||
'use_valid_from' => false,
|
||||
'use_valid_to' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ResidentialAddress::class,
|
||||
'kind' => null,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\UserJob;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
@@ -75,6 +76,22 @@ class UserType extends AbstractType
|
||||
'choice_label' => function (UserJob $c) {
|
||||
return $this->translatableStringHelper->localize($c->getLabel());
|
||||
},
|
||||
])
|
||||
->add('mainLocation', EntityType::class, [
|
||||
'label' => 'Main location',
|
||||
'required' => false,
|
||||
'placeholder' => 'choose a location',
|
||||
'class' => Location::class,
|
||||
'choice_label' => function (Location $l) {
|
||||
return $this->translatableStringHelper->localize($l->getLocationType()->getTitle()) . ' - ' . $l->getName();
|
||||
},
|
||||
'query_builder' => static function (EntityRepository $er) {
|
||||
$qb = $er->createQueryBuilder('l');
|
||||
$qb->orderBy('l.locationType');
|
||||
$qb->where('l.availableForUsers = TRUE');
|
||||
|
||||
return $qb;
|
||||
},
|
||||
]);
|
||||
|
||||
if ($options['is_creation']) {
|
||||
|
180
src/Bundle/ChillMainBundle/Form/WorkflowStepType.php
Normal file
180
src/Bundle/ChillMainBundle/Form/WorkflowStepType.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?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\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use LogicException;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\Transition;
|
||||
use function array_key_exists;
|
||||
|
||||
class WorkflowStepType extends AbstractType
|
||||
{
|
||||
private EntityWorkflowManager $entityWorkflowManager;
|
||||
|
||||
private Registry $registry;
|
||||
|
||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||
|
||||
public function __construct(EntityWorkflowManager $entityWorkflowManager, Registry $registry, TranslatableStringHelperInterface $translatableStringHelper)
|
||||
{
|
||||
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||
$this->registry = $registry;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
/** @var \Chill\MainBundle\Entity\Workflow\EntityWorkflow $entityWorkflow */
|
||||
$entityWorkflow = $options['entity_workflow'];
|
||||
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
|
||||
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||
$place = $workflow->getMarking($entityWorkflow);
|
||||
$placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata(array_keys($place->getPlaces())[0]);
|
||||
|
||||
if (true === $options['transition']) {
|
||||
if (null === $options['entity_workflow']) {
|
||||
throw new LogicException('if transition is true, entity_workflow should be defined');
|
||||
}
|
||||
|
||||
$transitions = $this->registry
|
||||
->get($options['entity_workflow'], $entityWorkflow->getWorkflowName())
|
||||
->getEnabledTransitions($entityWorkflow);
|
||||
|
||||
$choices = array_combine(
|
||||
array_map(
|
||||
static function (Transition $transition) {
|
||||
return $transition->getName();
|
||||
},
|
||||
$transitions
|
||||
),
|
||||
$transitions
|
||||
);
|
||||
|
||||
if (array_key_exists('validationFilterInputLabels', $placeMetadata)) {
|
||||
$inputLabels = $placeMetadata['validationFilterInputLabels'];
|
||||
|
||||
$builder->add('transitionFilter', ChoiceType::class, [
|
||||
'multiple' => false,
|
||||
'label' => 'workflow.My decision',
|
||||
'choices' => [
|
||||
'forward' => 'forward',
|
||||
'backward' => 'backward',
|
||||
'neutral' => 'neutral',
|
||||
],
|
||||
'choice_label' => function (string $key) use ($inputLabels) {
|
||||
return $this->translatableStringHelper->localize($inputLabels[$key]);
|
||||
},
|
||||
'choice_attr' => static function (string $key) {
|
||||
return [
|
||||
$key => $key,
|
||||
];
|
||||
},
|
||||
'mapped' => false,
|
||||
'expanded' => true,
|
||||
'data' => 'forward',
|
||||
]);
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('transition', ChoiceType::class, [
|
||||
'label' => 'workflow.Next step',
|
||||
'mapped' => false,
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'choices' => $choices,
|
||||
'choice_label' => function (Transition $transition) use ($workflow) {
|
||||
$meta = $workflow->getMetadataStore()->getTransitionMetadata($transition);
|
||||
|
||||
if (array_key_exists('label', $meta)) {
|
||||
return $this->translatableStringHelper->localize($meta['label']);
|
||||
}
|
||||
|
||||
return $transition->getName();
|
||||
},
|
||||
'choice_attr' => static function (Transition $transition) use ($workflow) {
|
||||
$toFinal = true;
|
||||
$isForward = 'neutral';
|
||||
|
||||
$metadata = $workflow->getMetadataStore()->getTransitionMetadata($transition);
|
||||
|
||||
if (array_key_exists('isForward', $metadata)) {
|
||||
if ($metadata['isForward']) {
|
||||
$isForward = 'forward';
|
||||
} else {
|
||||
$isForward = 'backward';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($transition->getTos() as $to) {
|
||||
$meta = $workflow->getMetadataStore()->getPlaceMetadata($to);
|
||||
|
||||
if (
|
||||
!array_key_exists('isFinal', $meta) || false === $meta['isFinal']
|
||||
) {
|
||||
$toFinal = false;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'data-is-transition' => 'data-is-transition',
|
||||
'data-to-final' => $toFinal ? '1' : '0',
|
||||
'data-is-forward' => $isForward,
|
||||
];
|
||||
},
|
||||
])
|
||||
->add('future_dest_users', PickUserDynamicType::class, [
|
||||
'label' => 'workflow.dest for next steps',
|
||||
'multiple' => true,
|
||||
'mapped' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
if (
|
||||
$handler->supportsFreeze($entityWorkflow)
|
||||
&& !$entityWorkflow->isFreeze()
|
||||
) {
|
||||
$builder
|
||||
->add('freezeAfter', CheckboxType::class, [
|
||||
'required' => false,
|
||||
'label' => 'workflow.Freeze',
|
||||
'help' => 'workflow.The associated element will be freezed',
|
||||
]);
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('comment', ChillTextareaType::class, [
|
||||
'required' => false,
|
||||
'label' => 'Comment',
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefined('class', EntityWorkflowStep::class)
|
||||
->setRequired('transition')
|
||||
->setAllowedTypes('transition', 'bool')
|
||||
->setRequired('entity_workflow')
|
||||
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
|
||||
}
|
||||
}
|
36
src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php
Normal file
36
src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class WorkflowTransitionType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('current_step', WorkflowStepType::class, [
|
||||
'transition' => true,
|
||||
'entity_workflow' => $options['entity_workflow'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('entity_workflow')
|
||||
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
|
||||
}
|
||||
}
|
@@ -50,7 +50,7 @@ class NotificationMailer
|
||||
$email = new TemplatedEmail();
|
||||
$email
|
||||
->to($dest->getEmail())
|
||||
->subject('Re: [Chill] ' . $comment->getNotification()->getTitle())
|
||||
->subject('Re: ' . $comment->getNotification()->getTitle())
|
||||
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig')
|
||||
->context([
|
||||
'comment' => $comment,
|
||||
@@ -79,11 +79,13 @@ class NotificationMailer
|
||||
continue;
|
||||
}
|
||||
|
||||
$email = new Email();
|
||||
$email
|
||||
->subject($notification->getTitle());
|
||||
|
||||
if ($notification->isSystem()) {
|
||||
$email = new Email();
|
||||
$email
|
||||
->text($notification->getMessage())
|
||||
->subject('[Chill] ' . $notification->getTitle());
|
||||
->text($notification->getMessage());
|
||||
} else {
|
||||
$email = new TemplatedEmail();
|
||||
$email
|
||||
|
@@ -15,12 +15,15 @@ use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\NotificationRepository;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
* Helps to find if a notification exist for a given entity.
|
||||
*/
|
||||
class NotificationPresence
|
||||
{
|
||||
private array $cache = [];
|
||||
|
||||
private NotificationRepository $notificationRepository;
|
||||
|
||||
private Security $security;
|
||||
@@ -31,6 +34,29 @@ class NotificationPresence
|
||||
$this->notificationRepository = $notificationRepository;
|
||||
}
|
||||
|
||||
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array
|
||||
{
|
||||
if (array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) {
|
||||
return $this->cache[$relatedEntityClass][$relatedEntityId];
|
||||
}
|
||||
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if ($user instanceof User) {
|
||||
$counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated(
|
||||
$relatedEntityClass,
|
||||
$relatedEntityId,
|
||||
$user
|
||||
);
|
||||
|
||||
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
|
||||
|
||||
return $counter;
|
||||
}
|
||||
|
||||
return ['unread' => 0, 'read' => 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Notification[]
|
||||
*/
|
||||
|
@@ -23,6 +23,13 @@ class NotificationTwigExtension extends AbstractExtension
|
||||
'needs_environment' => true,
|
||||
'is_safe' => ['html'],
|
||||
]),
|
||||
new TwigFunction('chill_count_notifications', [NotificationTwigExtensionRuntime::class, 'countNotificationsFor'], [
|
||||
'is_safe' => [],
|
||||
]),
|
||||
new TwigFunction('chill_counter_notifications', [NotificationTwigExtensionRuntime::class, 'counterNotificationFor'], [
|
||||
'needs_environment' => true,
|
||||
'is_safe' => ['html'],
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -11,17 +11,42 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Notification\Templating;
|
||||
|
||||
use Chill\MainBundle\Entity\NotificationComment;
|
||||
use Chill\MainBundle\Form\NotificationCommentType;
|
||||
use Chill\MainBundle\Notification\NotificationPresence;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\RuntimeExtensionInterface;
|
||||
|
||||
class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
private FormFactoryInterface $formFactory;
|
||||
|
||||
private NotificationPresence $notificationPresence;
|
||||
|
||||
public function __construct(NotificationPresence $notificationPresence)
|
||||
private UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
public function __construct(FormFactoryInterface $formFactory, NotificationPresence $notificationPresence, UrlGeneratorInterface $urlGenerator)
|
||||
{
|
||||
$this->formFactory = $formFactory;
|
||||
$this->notificationPresence = $notificationPresence;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
||||
{
|
||||
return $environment->render(
|
||||
'@ChillMain/Notification/extension_counter_notifications_for.html.twig',
|
||||
[
|
||||
'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $options = []): array
|
||||
{
|
||||
return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
|
||||
}
|
||||
|
||||
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
||||
@@ -32,8 +57,24 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
return '';
|
||||
}
|
||||
|
||||
$appendCommentForms = [];
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
$appendComment = new NotificationComment();
|
||||
$appendCommentForms[$notification->getId()] = $this->formFactory->create(
|
||||
NotificationCommentType::class,
|
||||
$appendComment,
|
||||
[
|
||||
'action' => $this->urlGenerator->generate(
|
||||
'chill_main_notification_show',
|
||||
['id' => $notification->getId()]
|
||||
),
|
||||
]
|
||||
)->createView();
|
||||
}
|
||||
|
||||
return $environment->render('@ChillMain/Notification/extension_list_notifications_for.html.twig', [
|
||||
'notifications' => $notifications,
|
||||
'notifications' => $notifications, 'appendCommentForms' => $appendCommentForms,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\DBAL\Statement;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
@@ -24,6 +25,8 @@ final class NotificationRepository implements ObjectRepository
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private ?Statement $notificationByRelatedEntityAndUserAssociatedStatement = null;
|
||||
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
@@ -48,6 +51,30 @@ final class NotificationRepository implements ObjectRepository
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
|
||||
{
|
||||
if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) {
|
||||
$sql =
|
||||
'SELECT
|
||||
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = 1812 and cmnau.notification_id = cmn.id))::int) AS unread,
|
||||
SUM((cmn.sender_id = 1812)::int) AS sent,
|
||||
COUNT(cmn.*) AS total
|
||||
FROM chill_main_notification cmn
|
||||
WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
|
||||
$this->notificationByRelatedEntityAndUserAssociatedStatement =
|
||||
$this->em->getConnection()->prepare($sql);
|
||||
}
|
||||
|
||||
$results = $this->notificationByRelatedEntityAndUserAssociatedStatement
|
||||
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId]);
|
||||
|
||||
$result = $results->fetchAssociative();
|
||||
|
||||
$results->free();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function countUnreadByUser(User $user): int
|
||||
{
|
||||
$sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId';
|
||||
@@ -153,11 +180,52 @@ final class NotificationRepository implements ObjectRepository
|
||||
* @return array|Notification[]
|
||||
*/
|
||||
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
|
||||
{
|
||||
return
|
||||
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user)
|
||||
->select('n')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
|
||||
{
|
||||
return $this->repository->findOneBy($criteria, $orderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Notification[]
|
||||
*/
|
||||
public function findUnreadByUser(User $user, int $limit = 20, int $offset = 0): array
|
||||
{
|
||||
$rsm = new Query\ResultSetMappingBuilder($this->em);
|
||||
$rsm->addRootEntityFromClassMetadata(Notification::class, 'cmn');
|
||||
|
||||
$sql = 'SELECT ' . $rsm->generateSelectClause(['cmn' => 'cmn']) . ' ' .
|
||||
'FROM chill_main_notification cmn ' .
|
||||
'WHERE ' .
|
||||
'EXISTS (select 1 FROM chill_main_notification_addresses_unread cmnau WHERE cmnau.user_id = :userId and cmnau.notification_id = cmn.id) ' .
|
||||
'ORDER BY cmn.date DESC ' .
|
||||
'LIMIT :limit OFFSET :offset';
|
||||
|
||||
$nq = $this->em->createNativeQuery($sql, $rsm)
|
||||
->setParameter('userId', $user->getId())
|
||||
->setParameter('limit', $limit)
|
||||
->setParameter('offset', $offset);
|
||||
|
||||
return $nq->getResult();
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return Notification::class;
|
||||
}
|
||||
|
||||
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('n');
|
||||
|
||||
$qb
|
||||
->select('n')
|
||||
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'))
|
||||
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId'))
|
||||
->andWhere($qb->expr()->isNotNull('n.sender'))
|
||||
@@ -171,17 +239,7 @@ final class NotificationRepository implements ObjectRepository
|
||||
->setParameter('relatedEntityId', $relatedEntityId)
|
||||
->setParameter('user', $user);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
|
||||
{
|
||||
return $this->repository->findOneBy($criteria, $orderBy);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return Notification::class;
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder
|
||||
|
@@ -0,0 +1,59 @@
|
||||
<?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\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\ResidentialAddress;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @method ResidentialAddress|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method ResidentialAddress|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method ResidentialAddress[] findAll()
|
||||
* @method ResidentialAddress[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class ResidentialAddressRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, ResidentialAddress::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return ResidentialAddress[] Returns an array of ResidentialAddress objects
|
||||
// */
|
||||
/*
|
||||
public function findByExampleField($value)
|
||||
{
|
||||
return $this->createQueryBuilder('r')
|
||||
->andWhere('r.exampleField = :val')
|
||||
->setParameter('val', $value)
|
||||
->orderBy('r.id', 'ASC')
|
||||
->setMaxResults(10)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public function findOneBySomeField($value): ?ResidentialAddress
|
||||
{
|
||||
return $this->createQueryBuilder('r')
|
||||
->andWhere('r.exampleField = :val')
|
||||
->setParameter('val', $value)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
}
|
||||
*/
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
<?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\MainBundle\Repository\Workflow;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
class EntityWorkflowRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(EntityWorkflow::class);
|
||||
}
|
||||
|
||||
public function countByDest(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryByDest($user)->select('count(ew)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countBySubscriber(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryBySubscriber($user)->select('count(ew)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function find($id): ?EntityWorkflow
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|EntityWorkflow[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|mixed $limit
|
||||
* @param null|mixed $offset
|
||||
*
|
||||
* @return array|EntityWorkflow[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findByDest(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryByDest($user)->select('ew');
|
||||
|
||||
foreach ($orderBy as $key => $sort) {
|
||||
$qb->addOrderBy('ew.' . $key, $sort);
|
||||
}
|
||||
|
||||
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findBySubscriber(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryBySubscriber($user)->select('ew');
|
||||
|
||||
foreach ($orderBy as $key => $sort) {
|
||||
$qb->addOrderBy('ew.' . $key, $sort);
|
||||
}
|
||||
|
||||
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?EntityWorkflow
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return EntityWorkflow::class;
|
||||
}
|
||||
|
||||
private function buildQueryByDest(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
||||
$qb->join('ew.steps', 'step');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->isMemberOf(':user', 'step.destUser'),
|
||||
$qb->expr()->isNull('step.transitionAfter'),
|
||||
$qb->expr()->eq('step.isFinal', "'FALSE'")
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function buildQueryBySubscriber(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->isMemberOf(':user', 'ew.subscriberToStep'),
|
||||
$qb->expr()->isMemberOf(':user', 'ew.subscriberToFinal'),
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?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\MainBundle\Repository\Workflow;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
class EntityWorkflowStepRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(EntityWorkflowStep::class);
|
||||
}
|
||||
|
||||
public function countUnreadByUser(User $user, ?array $orderBy = null, $limit = null, $offset = null): int
|
||||
{
|
||||
$qb = $this->buildQueryByUser($user)->select('count(e)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function find($id): ?EntityWorkflowStep
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?EntityWorkflowStep
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return EntityWorkflow::class;
|
||||
}
|
||||
|
||||
private function buildQueryByUser(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('e');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->isMemberOf(':user', 'e.destUser'),
|
||||
$qb->expr()->isNull('e.transitionAt'),
|
||||
$qb->expr()->eq('e.isFinal', ':bool'),
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
$qb->setParameter('bool', false);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
// Access to Bootstrap variables and mixins
|
||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
||||
@import 'ChillMainAssets/module/bootstrap/shared';
|
||||
|
||||
// Chill variables
|
||||
@import './scss/chill_variables';
|
||||
@@ -240,6 +240,10 @@ table.table-bordered {
|
||||
color: $gray-800;
|
||||
font-size: 90%;
|
||||
|
||||
p {
|
||||
margin-bottom: 0.75rem !important;
|
||||
}
|
||||
|
||||
// test a bottom right decoration (to be confirmed)
|
||||
&.test {
|
||||
position: relative;
|
||||
@@ -273,11 +277,17 @@ table.table-bordered {
|
||||
}
|
||||
}
|
||||
|
||||
/// meta-data
|
||||
div.updatedBy,
|
||||
div.metadata {
|
||||
span.user, span.date {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
}
|
||||
div.metadata {
|
||||
font-size: smaller;
|
||||
color: $gray-600;
|
||||
span.user, span.date {
|
||||
text-decoration: underline dotted;
|
||||
&:hover {
|
||||
color: $gray-700;
|
||||
}
|
||||
@@ -318,6 +328,7 @@ dl.definition-inline {
|
||||
.custom_field_no_data,
|
||||
.chill-no-data-statement {
|
||||
font-style: italic;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/// flash
|
||||
@@ -418,3 +429,65 @@ span.item-key {
|
||||
background-color: #0000000a;
|
||||
//text-decoration: dotted underline;
|
||||
}
|
||||
|
||||
/// Workflows
|
||||
div.workflow {
|
||||
section.step {
|
||||
border: 1px solid $chill-l-gray;
|
||||
padding: 1em 2em;
|
||||
div.flex-table {
|
||||
margin: 1.5em -2em;
|
||||
}
|
||||
}
|
||||
div.to-decision,
|
||||
div.decided {
|
||||
font-variant: all-small-caps;
|
||||
margin-left: 1em;
|
||||
}
|
||||
div.to-decision {
|
||||
font-weight: 300;
|
||||
}
|
||||
div.decided {
|
||||
font-weight: 600;
|
||||
}
|
||||
div.breadcrumb {
|
||||
display: initial;
|
||||
margin-bottom: 0;
|
||||
padding-right: 0.5em;
|
||||
background-color: tint-color($chill-yellow, 90%);
|
||||
border: 1px solid $chill-yellow;
|
||||
color: $primary;
|
||||
border-radius: 1.5em;
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
font-variant: small-caps;
|
||||
span, a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override bootstrap popover styles
|
||||
div.popover {
|
||||
box-shadow: 0 0 10px -5px $dark;
|
||||
z-index: 9999;
|
||||
.popover-arrow {}
|
||||
.popover-header {}
|
||||
.popover-body {}
|
||||
|
||||
// Specific worflow breadcrumb popover
|
||||
&.workflow-transition {
|
||||
.popover-header {
|
||||
font-variant: small-caps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// increase toast message z-index (above all modals)
|
||||
div.v-toast {
|
||||
z-index: 10000!important;
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ import { chill } from './js/chill.js';
|
||||
global.chill = chill;
|
||||
|
||||
require('./js/date.js');
|
||||
require('./js/counter.js');
|
||||
|
||||
/// Load fonts
|
||||
require('./fonts/OpenSans/OpenSans.scss')
|
||||
|
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
*
|
||||
* This script search for span.counter elements like
|
||||
* <span class="counter">Il y a 4 notifications</span>
|
||||
* and return
|
||||
* <span class="counter">Il y a <span>4</span> notifications</span>
|
||||
*
|
||||
*/
|
||||
const isNum = (v) => !isNaN(v);
|
||||
|
||||
const parseCounter = () => {
|
||||
document.querySelectorAll('span.counter')
|
||||
.forEach(el => {
|
||||
let r = [];
|
||||
el.innerText
|
||||
.trim()
|
||||
.split(' ')
|
||||
.forEach(w => {
|
||||
if (isNum(w)) {
|
||||
r.push(`<span>${w}</span>`);
|
||||
} else {
|
||||
r.push(w);
|
||||
}
|
||||
})
|
||||
;
|
||||
el.innerHTML = r.join(' ');
|
||||
})
|
||||
;
|
||||
};
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
parseCounter();
|
||||
});
|
||||
|
||||
export { parseCounter };
|
@@ -12,6 +12,7 @@
|
||||
display: block;
|
||||
top: calc(50% - 7px);
|
||||
right: 10px;
|
||||
line-height: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +63,19 @@ ul.list-suggest {
|
||||
& span:hover {
|
||||
color: $chill-l-gray;
|
||||
}
|
||||
.person-text {
|
||||
span {
|
||||
padding-left: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.remove-items {
|
||||
li {
|
||||
position: relative;
|
||||
span {
|
||||
& > span {
|
||||
display: block;
|
||||
padding-right: .75rem;
|
||||
padding-right: 1.75rem;
|
||||
@include remove_link;
|
||||
}
|
||||
}
|
||||
|
@@ -18,9 +18,11 @@ $chill-theme-buttons: (
|
||||
"show": $chill-blue,
|
||||
"view": $chill-blue,
|
||||
"misc": $gray-300,
|
||||
"download": $gray-300,
|
||||
"cancel": $gray-300,
|
||||
"choose": $gray-300,
|
||||
"notify": $gray-300,
|
||||
"notify": $chill-blue,
|
||||
"search": $gray-300,
|
||||
"unlink": $chill-red,
|
||||
"tpchild": $chill-pink,
|
||||
);
|
||||
@@ -78,6 +80,8 @@ $chill-theme-buttons: (
|
||||
&.btn-choose::before,
|
||||
&.btn-notify::before,
|
||||
&.btn-tpchild::before,
|
||||
&.btn-download::before,
|
||||
&.btn-search::before,
|
||||
&.btn-cancel::before {
|
||||
font: normal normal normal 14px/1 ForkAwesome;
|
||||
margin-right: 0.5em;
|
||||
@@ -105,6 +109,8 @@ $chill-theme-buttons: (
|
||||
&.btn-unlink::before { content: "\f127"; } // fa-chain-broken
|
||||
&.btn-notify::before { content: "\f1d8"; } // fa-paper-plane
|
||||
&.btn-tpchild::before { content: "\f007"; } // fa-user
|
||||
&.btn-download::before { content: "\f019"; } // fa-download
|
||||
&.btn-search::before { content: "\f002"; } // fa-search
|
||||
}
|
||||
|
||||
|
||||
@@ -130,3 +136,18 @@ $chill-theme-buttons: (
|
||||
.btn-sm, .btn-group-sm > .btn {
|
||||
min-width: 36px;
|
||||
}
|
||||
|
||||
// Homepage special fast action buttons
|
||||
div.sticky-buttons {
|
||||
position: fixed;
|
||||
bottom: 3em;
|
||||
right: 2em;
|
||||
.btn-circle {
|
||||
width: 50px; height: 50px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
padding: 0.45rem 0.7rem;
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
@@ -41,6 +41,15 @@ div.flex-table {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
div.item-meta {
|
||||
flex-grow: 1 !important;
|
||||
flex-shrink: 1 !important;
|
||||
width: unset !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -36,27 +36,65 @@ div.notification {
|
||||
div.notification-list,
|
||||
div.notification-show {
|
||||
div.item-bloc {
|
||||
div.item-row.header {
|
||||
|
||||
div.item-col {
|
||||
&:first-child {
|
||||
flex-grow: 1;
|
||||
div.item-row {
|
||||
&.notification-header {
|
||||
div.item-col {
|
||||
&:first-child {
|
||||
flex-grow: 1;
|
||||
|
||||
}
|
||||
&:last-child {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
flex-grow: 0;
|
||||
ul.small_in_title {
|
||||
list-style-type: circle;
|
||||
li {
|
||||
span.item-key {
|
||||
display: inline-block;
|
||||
width: 3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul.small_in_title {
|
||||
list-style-type: circle;
|
||||
li {
|
||||
span.item-key {
|
||||
display: inline-block;
|
||||
width: 3em;
|
||||
}
|
||||
div.notification-content {
|
||||
margin: 1.5rem;
|
||||
p {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override bootstrap accordion
|
||||
div#workflow-fold,
|
||||
div#notification-fold {
|
||||
.accordion-button {
|
||||
padding: 0;
|
||||
background-color: unset;
|
||||
&:not(.collapsed) {
|
||||
background-color: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Counter
|
||||
div.notification-counter {
|
||||
span.counter {
|
||||
&:not(:first-child) {
|
||||
&::before {
|
||||
content: '/ ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
span.counter {
|
||||
& > span {
|
||||
font-weight: bold;
|
||||
background-color: $chill-ll-gray;
|
||||
padding: 0 0.4rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
@@ -106,6 +106,8 @@ section.chill-entity {
|
||||
// used for comment-embeddable
|
||||
&.entity-comment-embeddable {
|
||||
width: 100%;
|
||||
|
||||
/* already defined !!
|
||||
div.metadata {
|
||||
font-size: smaller;
|
||||
color: $gray-600;
|
||||
@@ -116,5 +118,6 @@ section.chill-entity {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@@ -85,7 +85,9 @@ const fetchScopes = () => {
|
||||
const ValidationException = (response) => {
|
||||
const error = {};
|
||||
error.name = 'ValidationException';
|
||||
error.violations = response.violations.map((violation) => `${violation.title}`);
|
||||
error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`);
|
||||
error.titles = response.violations.map((violation) => violation.title);
|
||||
error.propertyPaths = response.violations.map((violation) => violation.propertyPath);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
@@ -0,0 +1,12 @@
|
||||
const buildLinkCreate = function(workflowName, relatedEntityClass, relatedEntityId) {
|
||||
let params = new URLSearchParams();
|
||||
params.set('entityClass', relatedEntityClass);
|
||||
params.set('entityId', relatedEntityId);
|
||||
params.set('workflow', workflowName);
|
||||
|
||||
return `/fr/main/workflow/create?`+params.toString();
|
||||
};
|
||||
|
||||
export {
|
||||
buildLinkCreate,
|
||||
};
|
@@ -1,19 +1,19 @@
|
||||
/**
|
||||
* Create a control to show or hide values
|
||||
*
|
||||
*
|
||||
* Possible options are:
|
||||
*
|
||||
*
|
||||
* - froms: an Element, an Array of Element, or a NodeList. A
|
||||
* listener will be attached to **all** input of those elements
|
||||
* and will trigger the check on changes
|
||||
* - test: a function which will test the element and will return true
|
||||
* - test: a function which will test the element and will return true
|
||||
* if the content must be shown, false if it must be hidden.
|
||||
* The function will receive the `froms` as first argument, and the
|
||||
* The function will receive the `froms` as first argument, and the
|
||||
* event as second argument.
|
||||
* - container: an Element, an Array of Element, or a Node List. The
|
||||
* - container: an Element, an Array of Element, or a Node List. The
|
||||
* child nodes will be hidden / shown inside this container
|
||||
* - event_name: the name of the event to listen to. `'change'` by default.
|
||||
*
|
||||
*
|
||||
* @param object options
|
||||
*/
|
||||
var ShowHide = function(options) {
|
||||
@@ -26,8 +26,10 @@ var ShowHide = function(options) {
|
||||
container_content = [],
|
||||
debug = 'debug' in options ? options.debug : false,
|
||||
load_event = 'load_event' in options ? options.load_event : 'load',
|
||||
id = 'uid' in options ? options.id : Math.random();
|
||||
|
||||
id = 'uid' in options ? options.id : Math.random(),
|
||||
toggle_callback = 'toggle_callback' in options ? options.toggle_callback : null
|
||||
;
|
||||
|
||||
var bootstrap = function(event) {
|
||||
if (debug) {
|
||||
console.log('debug is activated on this show-hide', this);
|
||||
@@ -43,10 +45,10 @@ var ShowHide = function(options) {
|
||||
|
||||
// attach the listener on each input
|
||||
for (let f of froms.values()) {
|
||||
let
|
||||
inputs = f.querySelectorAll('input'),
|
||||
let
|
||||
inputs = f.querySelectorAll('input'),
|
||||
selects = f.querySelectorAll('select');
|
||||
|
||||
|
||||
for (let input of inputs.values()) {
|
||||
if (debug) {
|
||||
console.log('attaching event to input', input);
|
||||
@@ -66,10 +68,10 @@ var ShowHide = function(options) {
|
||||
}
|
||||
|
||||
// first launch of the show/hide
|
||||
onChange(event);
|
||||
onChange(event);
|
||||
};
|
||||
|
||||
|
||||
|
||||
var onChange = function (event) {
|
||||
var result = test(froms, event), me;
|
||||
|
||||
@@ -88,45 +90,53 @@ var ShowHide = function(options) {
|
||||
} else {
|
||||
throw "the result of test is not a boolean";
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
var forceHide = function() {
|
||||
if (debug) {
|
||||
console.log('force hide');
|
||||
}
|
||||
for (let contents of container_content.values()) {
|
||||
for (let el of contents.values()) {
|
||||
el.remove();
|
||||
if (toggle_callback !== null) {
|
||||
toggle_callback(container, 'hide');
|
||||
} else {
|
||||
for (let contents of container_content.values()) {
|
||||
for (let el of contents.values()) {
|
||||
el.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
is_shown = false;
|
||||
};
|
||||
|
||||
|
||||
var forceShow = function() {
|
||||
if (debug) {
|
||||
console.log('show');
|
||||
}
|
||||
for (let i of container_content.keys()) {
|
||||
var contents = container_content[i];
|
||||
for (let el of contents.values()) {
|
||||
container[i].appendChild(el);
|
||||
if (toggle_callback !== null) {
|
||||
toggle_callback(container, 'show');
|
||||
} else {
|
||||
for (let i of container_content.keys()) {
|
||||
var contents = container_content[i];
|
||||
for (let el of contents.values()) {
|
||||
container[i].appendChild(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
is_shown = true;
|
||||
};
|
||||
|
||||
|
||||
var forceCompute = function(event) {
|
||||
onChange(event);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
if (load_event !== null) {
|
||||
window.addEventListener('load', bootstrap);
|
||||
} else {
|
||||
bootstrap(null);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
forceHide: forceHide,
|
||||
forceShow: forceShow,
|
||||
|
@@ -1,21 +1,19 @@
|
||||
require('./blur.scss');
|
||||
|
||||
var toggleBlur = function(e){
|
||||
|
||||
var btn = e.target;
|
||||
|
||||
btn.previousElementSibling.classList.toggle("blur");
|
||||
btn.classList.toggle("fa-eye");
|
||||
btn.classList.toggle("fa-eye-slash");
|
||||
|
||||
}
|
||||
|
||||
var infos = document.getElementsByClassName("confidential");
|
||||
for(var i=0; i < infos.length; i++){
|
||||
infos[i].insertAdjacentHTML('beforeend', '<i class="fa fa-eye toggle" aria-hidden="true"></i>');
|
||||
}
|
||||
|
||||
var toggles = document.getElementsByClassName("toggle");
|
||||
for(var i=0; i < toggles.length; i++){
|
||||
toggles[i].addEventListener("click", toggleBlur);
|
||||
}
|
||||
document.querySelectorAll('.confidential').forEach(function (el) {
|
||||
let i = document.createElement('i');
|
||||
const classes = ['fa', 'fa-eye', 'toggle'];
|
||||
i.classList.add(...classes);
|
||||
el.appendChild(i);
|
||||
const toggleBlur = function(e) {
|
||||
for (let child of el.children) {
|
||||
if (!child.classList.contains('toggle')) {
|
||||
child.classList.toggle('blur');
|
||||
}
|
||||
}
|
||||
i.classList.toggle('fa-eye');
|
||||
i.classList.toggle('fa-eye-slash');
|
||||
}
|
||||
i.addEventListener('click', toggleBlur);
|
||||
toggleBlur();
|
||||
});
|
||||
|
@@ -9,9 +9,10 @@ import Dropdown from 'bootstrap/js/src/dropdown';
|
||||
import Modal from 'bootstrap/js/dist/modal';
|
||||
import Collapse from 'bootstrap/js/src/collapse';
|
||||
import Carousel from 'bootstrap/js/src/carousel';
|
||||
import Popover from 'bootstrap/js/src/popover';
|
||||
|
||||
//
|
||||
// ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section
|
||||
// Carousel: ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section
|
||||
// Initialize options, and show/hide controls in first/last slides
|
||||
//
|
||||
let ACHeaderSlider = document.querySelector('#ACHeaderSlider');
|
||||
@@ -48,3 +49,14 @@ if (ACHeaderSlider) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Popover: used in workflow breadcrumb,
|
||||
// (expected in: contextual help, notification-box, workflow-box )
|
||||
//
|
||||
const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||
const popoverList = triggerList.map(function (el) {
|
||||
return new Popover(el, {
|
||||
html: true,
|
||||
});
|
||||
});
|
@@ -0,0 +1,34 @@
|
||||
import { createApp } from "vue";
|
||||
import ListWorkflowModalVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
|
||||
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||
|
||||
const i18n = _createI18n({});
|
||||
|
||||
// list workflow
|
||||
document.querySelectorAll('[data-list-workflows]')
|
||||
.forEach(function (el) {
|
||||
const app = {
|
||||
components: {
|
||||
ListWorkflowModalVue,
|
||||
},
|
||||
template:
|
||||
'<list-workflow-modal-vue ' +
|
||||
':workflows="workflows" ' +
|
||||
':allowCreate="allowCreate" ' +
|
||||
':relatedEntityClass="relatedEntityClass" ' +
|
||||
':relatedEntityId="relatedEntityId" ' +
|
||||
':workflowsAvailables="workflowsAvailables" ' +
|
||||
'></list-workflow-modal-vue>',
|
||||
data() {
|
||||
return {
|
||||
workflows: JSON.parse(el.dataset.workflows),
|
||||
allowCreate: el.dataset.allowCreate === "1",
|
||||
relatedEntityClass: el.dataset.relatedEntityClass,
|
||||
relatedEntityId: Number.parseInt(el.dataset.relatedEntityId),
|
||||
workflowsAvailables: JSON.parse(el.dataset.workflowsAvailables),
|
||||
}
|
||||
}
|
||||
};
|
||||
createApp(app).use(i18n).mount(el);
|
||||
})
|
||||
;
|
@@ -0,0 +1,32 @@
|
||||
import {createApp} from "vue";
|
||||
import EntityWorkflowVueSubscriber from 'ChillMainAssets/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
||||
import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n';
|
||||
|
||||
const i18n = _createI18n(appMessages);
|
||||
|
||||
let containers = document.querySelectorAll('[data-entity-workflow-subscribe]');
|
||||
|
||||
containers.forEach(container => {
|
||||
let app = {
|
||||
components: {
|
||||
EntityWorkflowVueSubscriber,
|
||||
},
|
||||
template: '<entity-workflow-vue-subscriber :entityWorkflowId="this.entityWorkflowId" :subscriberStep="this.subscriberStep" :subscriberFinal="this.subscriberFinal" @subscriptionUpdated="onUpdate"></entity-workflow-vue-subscriber>',
|
||||
data() {
|
||||
return {
|
||||
entityWorkflowId: Number.parseInt(container.dataset.entityWorkflowId),
|
||||
subscriberStep: container.dataset.subscribeStep === "1",
|
||||
subscriberFinal: container.dataset.subscribeFinal === "1",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onUpdate(status) {
|
||||
this.subscriberStep = status.step;
|
||||
this.subscriberFinal = status.final;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createApp(app).use(i18n).mount(container);
|
||||
})
|
@@ -6,7 +6,7 @@ const i18n = _createI18n({});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
document.querySelectorAll('.notification_toggle_read_status')
|
||||
.forEach(function (el) {
|
||||
.forEach(function (el, i) {
|
||||
createApp({
|
||||
template: '<notification-read-toggle ' +
|
||||
':notificationId="notificationId" ' +
|
||||
@@ -26,13 +26,25 @@ window.addEventListener('DOMContentLoaded', function (e) {
|
||||
buttonNoText: 'false' === el.dataset.buttonText,
|
||||
showUrl: el.dataset.showButtonUrl,
|
||||
isRead: 1 === +el.dataset.notificationCurrentIsRead,
|
||||
container: el.dataset.container
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getContainer() {
|
||||
return document.querySelectorAll('div.' + this.container);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMarkRead() {
|
||||
if (typeof this.getContainer[i] !== 'undefined') {
|
||||
this.getContainer[i].classList.replace('read', 'unread');
|
||||
} else { throw 'data-container attribute is missing' }
|
||||
this.isRead = false;
|
||||
},
|
||||
onMarkUnread() {
|
||||
if (typeof this.getContainer[i] !== 'undefined') {
|
||||
this.getContainer[i].classList.replace('unread', 'read');
|
||||
} else { throw 'data-container attribute is missing' }
|
||||
this.isRead = true;
|
||||
},
|
||||
}
|
||||
@@ -40,4 +52,4 @@ window.addEventListener('DOMContentLoaded', function (e) {
|
||||
.use(i18n)
|
||||
.mount(el);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@@ -5,18 +5,27 @@ import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n';
|
||||
|
||||
const i18n = _createI18n(appMessages);
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function(e) {
|
||||
let appsOnPage = new Map();
|
||||
|
||||
let apps = document.querySelectorAll('[data-module="pick-dynamic"]');
|
||||
function loadDynamicPicker(element) {
|
||||
|
||||
let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
|
||||
|
||||
apps.forEach(function(el) {
|
||||
|
||||
const
|
||||
isMultiple = parseInt(el.dataset.multiple) === 1,
|
||||
input = document.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'),
|
||||
picked = isMultiple ? JSON.parse(input.value) : [JSON.parse(input.value)];
|
||||
uniqId = el.dataset.uniqid,
|
||||
input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'),
|
||||
picked = (isMultiple) ? (JSON.parse(input.value)) : ((input.value === '[]') ? (null) : ([JSON.parse(input.value)]));
|
||||
|
||||
createApp({
|
||||
if (!isMultiple) {
|
||||
if (input.value === '[]'){
|
||||
input.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
template: '<pick-entity ' +
|
||||
':multiple="multiple" ' +
|
||||
':types="types" ' +
|
||||
@@ -31,7 +40,7 @@ window.addEventListener('DOMContentLoaded', function(e) {
|
||||
return {
|
||||
multiple: isMultiple,
|
||||
types: JSON.parse(el.dataset.types),
|
||||
picked,
|
||||
picked: picked === null ? [] : picked,
|
||||
uniqid: el.dataset.uniqid,
|
||||
}
|
||||
},
|
||||
@@ -65,5 +74,36 @@ window.addEventListener('DOMContentLoaded', function(e) {
|
||||
})
|
||||
.use(i18n)
|
||||
.mount(el);
|
||||
|
||||
appsOnPage.set(uniqId, app);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('show-hide-show', function(e) {
|
||||
console.log('creation event caught')
|
||||
loadDynamicPicker(e.detail.container)
|
||||
})
|
||||
|
||||
document.addEventListener('show-hide-hide', function(e) {
|
||||
console.log('hiding event caught')
|
||||
e.detail.container.querySelectorAll('[data-module="pick-dynamic"]').forEach((el) => {
|
||||
let uniqId = el.dataset.uniqid;
|
||||
console.log(uniqId);
|
||||
if (appsOnPage.has(uniqId)) {
|
||||
appsOnPage.get(uniqId).unmount();
|
||||
console.log('App has been unmounted')
|
||||
appsOnPage.delete(uniqId);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(e) {
|
||||
console.log('loaded event', e)
|
||||
loadDynamicPicker(document)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,29 @@
|
||||
import { createApp } from 'vue';
|
||||
import OpenWopiLink from 'ChillMainAssets/vuejs/_components/OpenWopiLink';
|
||||
import {_createI18n} from "ChillMainAssets/vuejs/_js/i18n";
|
||||
|
||||
const i18n = _createI18n({});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
document.querySelectorAll('span[data-module="wopi-link"]')
|
||||
.forEach(function (el) {
|
||||
createApp({
|
||||
template: '<open-wopi-link :wopiUrl="wopiUrl" :title="title" :type="type" :button="button"></open-wopi-link>',
|
||||
components: {
|
||||
OpenWopiLink
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
wopiUrl: el.dataset.wopiUrl,
|
||||
title: el.dataset.docTitle,
|
||||
type: el.dataset.docType,
|
||||
button: el.dataset.button ? JSON.parse(el.dataset.button) : {}
|
||||
}
|
||||
}
|
||||
})
|
||||
.use(i18n)
|
||||
.mount(el)
|
||||
;
|
||||
})
|
||||
;
|
||||
});
|
@@ -0,0 +1,16 @@
|
||||
import { createApp } from 'vue';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
||||
import { appMessages } from 'ChillMainAssets/vuejs/HomepageWidget/js/i18n';
|
||||
import { store } from 'ChillMainAssets/vuejs/HomepageWidget/js/store';
|
||||
import App from 'ChillMainAssets/vuejs/HomepageWidget/App';
|
||||
|
||||
const i18n = _createI18n(appMessages);
|
||||
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
})
|
||||
.use(store)
|
||||
.use(i18n)
|
||||
.component('app', App)
|
||||
.mount('#homepage_widget')
|
||||
;
|
@@ -0,0 +1,90 @@
|
||||
import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js';
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
let
|
||||
divTransitions = document.querySelector('#transitions'),
|
||||
futureDestUsersContainer = document.querySelector('#futureDestUsers')
|
||||
;
|
||||
|
||||
if (null !== divTransitions) {
|
||||
new ShowHide({
|
||||
load_event: null,
|
||||
froms: [divTransitions],
|
||||
container: [futureDestUsersContainer],
|
||||
test: function(divs, arg2, arg3) {
|
||||
for (let div of divs) {
|
||||
for (let input of div.querySelectorAll('input')) {
|
||||
if (input.checked) {
|
||||
if (input.dataset.toFinal === "1") {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let
|
||||
transitionFilterContainer = document.querySelector('#transitionFilter'),
|
||||
transitions = document.querySelector('#transitions')
|
||||
;
|
||||
|
||||
if (null !== transitionFilterContainer) {
|
||||
transitions.querySelectorAll('.form-check').forEach(function(row) {
|
||||
|
||||
const isForward = row.querySelector('input').dataset.isForward;
|
||||
|
||||
new ShowHide({
|
||||
load_event: null,
|
||||
froms: [transitionFilterContainer],
|
||||
container: row,
|
||||
test: function (containers) {
|
||||
for (let container of containers) {
|
||||
for (let input of container.querySelectorAll('input')) {
|
||||
if (input.checked) {
|
||||
return isForward === input.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
toggle_callback: function (c, dir) {
|
||||
for (let div of c) {
|
||||
let input = div.querySelector('input');
|
||||
if ('hide' === dir) {
|
||||
input.checked = false;
|
||||
input.disabled = true;
|
||||
} else {
|
||||
input.disabled = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// validate form
|
||||
let form = document.querySelector('form[name="workflow_step"]');
|
||||
|
||||
if (form === null) {
|
||||
console.error('form to validate not found');
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
const datas = new FormData(event.target);
|
||||
|
||||
if (datas.has('workflow_step[future_dest_users]')) {
|
||||
const dests = JSON.parse(datas.get('workflow_step[future_dest_users]'));
|
||||
if (dests === null || (dests instanceof Array && dests.length === 0)) {
|
||||
event.preventDefault();
|
||||
console.log('no users!');
|
||||
window.alert('Indiquez un utilisateur pour traiter la prochaine étape.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@@ -98,6 +98,8 @@
|
||||
v-bind:defaultz="this.defaultz"
|
||||
v-bind:entity="this.entity"
|
||||
v-bind:flag="this.flag"
|
||||
v-bind:errors="this.errors"
|
||||
v-bind:checkErrors="this.checkErrors"
|
||||
@getCities="getCities"
|
||||
@getReferenceAddresses="getReferenceAddresses">
|
||||
</edit-pane>
|
||||
@@ -123,6 +125,8 @@
|
||||
v-bind:defaultz="this.defaultz"
|
||||
v-bind:entity="this.entity"
|
||||
v-bind:flag="this.flag"
|
||||
v-bind:errors="this.errors"
|
||||
v-bind:checkErrors="this.checkErrors"
|
||||
v-bind:insideModal="false"
|
||||
@getCities="getCities"
|
||||
@getReferenceAddresses="getReferenceAddresses">
|
||||
@@ -256,8 +260,10 @@ export default {
|
||||
editPane: false,
|
||||
datePane: false,
|
||||
loading: false,
|
||||
success: false
|
||||
success: false,
|
||||
dirty: false
|
||||
},
|
||||
errors: [],
|
||||
defaultz: {
|
||||
button: {
|
||||
text: { create: 'add_an_address_title', edit: 'edit_address' },
|
||||
@@ -529,6 +535,23 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
checkErrors() {
|
||||
this.errors = [];
|
||||
if (this.flag.dirty) {
|
||||
if (this.entity.selected.country === null) {
|
||||
this.errors.push("Un pays doit être sélectionné.");
|
||||
}
|
||||
if (Object.keys(this.entity.selected.city).length === 0) {
|
||||
this.errors.push("Une ville doit être sélectionnée.");
|
||||
}
|
||||
if (!this.entity.selected.isNoAddress) {
|
||||
if (this.entity.selected.address.street === null || this.entity.selected.address.streetNumber === null) {
|
||||
this.errors.push("Une adresse doit être sélectionnée.");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Make form ready for new changes
|
||||
*/
|
||||
@@ -539,6 +562,7 @@ export default {
|
||||
this.entity.loaded.cities = [];
|
||||
this.entity.loaded.countries = [];
|
||||
|
||||
this.entity.selected.confidential = this.context.edit ? this.entity.address.confidential : false;
|
||||
this.entity.selected.isNoAddress = (this.context.edit && this.entity.address.text === '') ? true : false;
|
||||
|
||||
this.entity.selected.country = this.context.edit ? this.entity.address.country : {};
|
||||
@@ -570,6 +594,7 @@ export default {
|
||||
{
|
||||
console.log('apply changes');
|
||||
let newAddress = {
|
||||
'confidential': this.entity.selected.confidential,
|
||||
'isNoAddress': this.entity.selected.isNoAddress,
|
||||
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
|
||||
'streetNumber': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.streetNumber,
|
||||
|
@@ -6,7 +6,6 @@
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="floor"
|
||||
maxlength=16
|
||||
:placeholder="$t('floor')"
|
||||
v-model="floor"/>
|
||||
<label for="floor">{{ $t('floor') }}</label>
|
||||
@@ -15,7 +14,6 @@
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="corridor"
|
||||
maxlength=16
|
||||
:placeholder="$t('corridor')"
|
||||
v-model="corridor"/>
|
||||
<label for="corridor">{{ $t('corridor') }}</label>
|
||||
@@ -24,7 +22,6 @@
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="steps"
|
||||
maxlength=16
|
||||
:placeholder="$t('steps')"
|
||||
v-model="steps"/>
|
||||
<label for="steps">{{ $t('steps') }}</label>
|
||||
@@ -33,7 +30,6 @@
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="flat"
|
||||
maxlength=16
|
||||
:placeholder="$t('flat')"
|
||||
v-model="flat"/>
|
||||
<label for="flat">{{ $t('flat') }}</label>
|
||||
|
@@ -10,8 +10,10 @@
|
||||
:deselect-label="$t('create_address')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@search-change="listenInputSearch"
|
||||
:internal-search="false"
|
||||
ref="addressSelector"
|
||||
@select="selectAddress"
|
||||
@remove="remove"
|
||||
name="field"
|
||||
track-by="id"
|
||||
label="value"
|
||||
@@ -56,7 +58,7 @@ import { searchReferenceAddresses, fetchReferenceAddresses } from '../../api.js'
|
||||
export default {
|
||||
name: 'AddressSelection',
|
||||
components: { VueMultiselect },
|
||||
props: ['entity', 'context', 'updateMapCenter'],
|
||||
props: ['entity', 'context', 'updateMapCenter', 'flag', 'checkErrors'],
|
||||
data() {
|
||||
return {
|
||||
value: this.context.edit ? this.entity.address.addressReference : null,
|
||||
@@ -109,6 +111,13 @@ export default {
|
||||
this.entity.selected.address.streetNumber = value.streetNumber;
|
||||
this.entity.selected.writeNew.address = false;
|
||||
this.updateMapCenter(value.point);
|
||||
this.flag.dirty = true;
|
||||
this.checkErrors();
|
||||
},
|
||||
remove() {
|
||||
this.flag.dirty = true;
|
||||
this.entity.selected.address = {};
|
||||
this.checkErrors();
|
||||
},
|
||||
listenInputSearch(query) {
|
||||
//console.log('listenInputSearch', query, this.isAddressSelectorOpen);
|
||||
@@ -149,6 +158,8 @@ export default {
|
||||
this.entity.selected.address.street = addr.street;
|
||||
this.entity.selected.address.streetNumber = addr.number;
|
||||
this.entity.selected.writeNew.address = true;
|
||||
this.flag.dirty = true;
|
||||
this.checkErrors();
|
||||
}
|
||||
},
|
||||
splitAddress(address) {
|
||||
|
@@ -7,6 +7,7 @@
|
||||
@search-change="listenInputSearch"
|
||||
ref="citySelector"
|
||||
@select="selectCity"
|
||||
@remove="remove"
|
||||
name="field"
|
||||
track-by="id"
|
||||
label="value"
|
||||
@@ -17,6 +18,7 @@
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
:taggable="true"
|
||||
:multiple="false"
|
||||
:internal-search="false"
|
||||
@tag="addPostcode"
|
||||
:tagPlaceholder="$t('create_postal_code')"
|
||||
:loading="isLoading"
|
||||
@@ -55,12 +57,12 @@ import { searchCities, fetchCities } from '../../api.js';
|
||||
export default {
|
||||
name: 'CitySelection',
|
||||
components: { VueMultiselect },
|
||||
props: ['entity', 'context', 'focusOnAddress', 'updateMapCenter'],
|
||||
props: ['entity', 'context', 'focusOnAddress', 'updateMapCenter', 'flag', 'checkErrors'],
|
||||
emits: ['getReferenceAddresses'],
|
||||
data() {
|
||||
return {
|
||||
value: this.context.edit ? this.entity.address.postcode : null,
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -123,6 +125,13 @@ export default {
|
||||
if (value.center) {
|
||||
this.updateMapCenter(value.center);
|
||||
}
|
||||
this.flag.dirty = true;
|
||||
this.checkErrors();
|
||||
},
|
||||
remove() {
|
||||
this.flag.dirty = true;
|
||||
this.entity.selected.city = {};
|
||||
this.checkErrors();
|
||||
},
|
||||
listenInputSearch(query) {
|
||||
if (query.length > 2) {
|
||||
|
@@ -12,7 +12,9 @@
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@select="selectCountry">
|
||||
@select="selectCountry"
|
||||
@remove="remove"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
</template>
|
||||
@@ -23,7 +25,7 @@ import VueMultiselect from 'vue-multiselect';
|
||||
export default {
|
||||
name: 'CountrySelection',
|
||||
components: { VueMultiselect },
|
||||
props: ['context', 'entity'],
|
||||
props: ['context', 'entity', 'flag', 'checkErrors'],
|
||||
emits: ['getCities'],
|
||||
data() {
|
||||
return {
|
||||
@@ -34,14 +36,13 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
sortedCountries() {
|
||||
//console.log('sorted countries');
|
||||
const countries = this.entity.loaded.countries;
|
||||
let sortedCountries = [];
|
||||
sortedCountries.push(...countries.filter(c => c.countryCode === 'FR'))
|
||||
sortedCountries.push(...countries.filter(c => c.countryCode === 'BE'))
|
||||
sortedCountries.push(...countries.filter(c => c.countryCode !== 'FR').filter(c => c.countryCode !== 'BE'))
|
||||
return sortedCountries;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
@@ -50,6 +51,7 @@ export default {
|
||||
init() {
|
||||
if (this.value !== undefined) {
|
||||
this.selectCountry(this.value);
|
||||
this.flag.dirty = false;
|
||||
}
|
||||
},
|
||||
selectCountryByCode(countryCode) {
|
||||
@@ -62,7 +64,13 @@ export default {
|
||||
//console.log('select country', value);
|
||||
this.entity.selected.country = value;
|
||||
this.$emit('getCities', value);
|
||||
}
|
||||
this.checkErrors();
|
||||
},
|
||||
remove() {
|
||||
this.flag.dirty = true;
|
||||
this.entity.selected.country = null;
|
||||
this.checkErrors();
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
@@ -7,16 +7,32 @@
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
|
||||
<div v-if="errors.length" class="alert alert-warning" >
|
||||
<ul>
|
||||
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4 class="h3">{{ $t('select_an_address_title') }}</h4>
|
||||
<div class="row my-3">
|
||||
<div class="col-lg-6">
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
id="isConfidential"
|
||||
v-model="isConfidential"
|
||||
:value="valueConfidential" />
|
||||
<label class="form-check-label" for="isConfidential">
|
||||
{{ $t('isConfidential') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
id="isNoAddress"
|
||||
v-model="isNoAddress"
|
||||
v-bind:value="value" />
|
||||
:value="value" />
|
||||
<label class="form-check-label" for="isNoAddress">
|
||||
{{ $t('isNoAddress') }}
|
||||
</label>
|
||||
@@ -25,6 +41,8 @@
|
||||
<country-selection
|
||||
v-bind:context="context"
|
||||
v-bind:entity="entity"
|
||||
v-bind:flag="flag"
|
||||
v-bind:checkErrors="checkErrors"
|
||||
@getCities="$emit('getCities', selected.country)">
|
||||
</country-selection>
|
||||
|
||||
@@ -33,13 +51,17 @@
|
||||
v-bind:context="context"
|
||||
v-bind:focusOnAddress="focusOnAddress"
|
||||
v-bind:updateMapCenter="updateMapCenter"
|
||||
v-bind:flag="flag"
|
||||
v-bind:checkErrors="checkErrors"
|
||||
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
|
||||
</city-selection>
|
||||
|
||||
<address-selection v-if="!isNoAddress"
|
||||
v-bind:entity="entity"
|
||||
v-bind:context="context"
|
||||
v-bind:updateMapCenter="updateMapCenter">
|
||||
v-bind:updateMapCenter="updateMapCenter"
|
||||
v-bind:flag="flag"
|
||||
v-bind:checkErrors="checkErrors">
|
||||
</address-selection>
|
||||
|
||||
</div>
|
||||
@@ -99,12 +121,15 @@ export default {
|
||||
'flag',
|
||||
'entity',
|
||||
'errorMsg',
|
||||
'insideModal'
|
||||
'insideModal',
|
||||
'errors',
|
||||
'checkErrors',
|
||||
],
|
||||
emits: ['getCities', 'getReferenceAddresses'],
|
||||
data() {
|
||||
return {
|
||||
value: false
|
||||
value: false,
|
||||
valueConfidential: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -120,6 +145,14 @@ export default {
|
||||
addressMap() {
|
||||
return this.entity.addressMap;
|
||||
},
|
||||
isConfidential: {
|
||||
set(value) {
|
||||
this.entity.selected.confidential = value;
|
||||
},
|
||||
get() {
|
||||
return this.entity.selected.confidential;
|
||||
}
|
||||
},
|
||||
isNoAddress: {
|
||||
set(value) {
|
||||
console.log('isNoAddress value', value);
|
||||
@@ -128,7 +161,7 @@ export default {
|
||||
get() {
|
||||
return this.entity.selected.isNoAddress;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
focusOnAddress() {
|
||||
|
@@ -18,6 +18,7 @@ const addressMessages = {
|
||||
other_address: 'Autre adresse',
|
||||
create_address: 'Adresse inconnue. Cliquez ici pour créer une nouvelle adresse',
|
||||
isNoAddress: 'Pas d\'adresse complète',
|
||||
isConfidential: 'Adresse confidentielle',
|
||||
street: 'Nom de rue',
|
||||
streetNumber: 'Numéro',
|
||||
floor: 'Étage',
|
||||
|
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
|
||||
<h2>{{ $t('main_title') }}</h2>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
:class="{'active': activeTab === 'MyCustoms'}"
|
||||
@click="selectTab('MyCustoms')">
|
||||
<i class="fa fa-dashboard"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
:class="{'active': activeTab === 'MyNotifications'}"
|
||||
@click="selectTab('MyNotifications')">
|
||||
{{ $t('my_notifications.tab') }}
|
||||
<tab-counter :count="state.notifications.count"></tab-counter>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
:class="{'active': activeTab === 'MyAccompanyingCourses'}"
|
||||
@click="selectTab('MyAccompanyingCourses')">
|
||||
{{ $t('my_accompanying_courses.tab') }}
|
||||
<tab-counter :count="state.accompanyingCourses.count"></tab-counter>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
:class="{'active': activeTab === 'MyWorks'}"
|
||||
@click="selectTab('MyWorks')">
|
||||
{{ $t('my_works.tab') }}
|
||||
<tab-counter :count="state.works.count"></tab-counter>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
:class="{'active': activeTab === 'MyEvaluations'}"
|
||||
@click="selectTab('MyEvaluations')">
|
||||
{{ $t('my_evaluations.tab') }}
|
||||
<tab-counter :count="state.evaluations.count"></tab-counter>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
:class="{'active': activeTab === 'MyTasks'}"
|
||||
@click="selectTab('MyTasks')">
|
||||
{{ $t('my_tasks.tab') }}
|
||||
<tab-counter :count="state.tasks.warning.count"></tab-counter>
|
||||
<tab-counter :count="state.tasks.alert.count"></tab-counter>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item loading ms-auto py-2" v-if="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-lg text-chill-gray" :title="$t('loading')"></i>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="my-4">
|
||||
<my-customs
|
||||
v-if="activeTab === 'MyCustoms'">
|
||||
</my-customs>
|
||||
<my-works
|
||||
v-else-if="activeTab === 'MyWorks'">
|
||||
</my-works>
|
||||
<my-evaluations
|
||||
v-else-if="activeTab === 'MyEvaluations'">
|
||||
</my-evaluations>
|
||||
<my-tasks
|
||||
v-else-if="activeTab === 'MyTasks'">
|
||||
</my-tasks>
|
||||
<my-accompanying-courses
|
||||
v-else-if="activeTab === 'MyAccompanyingCourses'">
|
||||
</my-accompanying-courses>
|
||||
<my-notifications
|
||||
v-else-if="activeTab === 'MyNotifications'">
|
||||
</my-notifications>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MyCustoms from './MyCustoms';
|
||||
import MyWorks from './MyWorks';
|
||||
import MyEvaluations from './MyEvaluations';
|
||||
import MyTasks from './MyTasks';
|
||||
import MyAccompanyingCourses from './MyAccompanyingCourses';
|
||||
import MyNotifications from './MyNotifications';
|
||||
import TabCounter from './TabCounter';
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
MyCustoms,
|
||||
MyWorks,
|
||||
MyEvaluations,
|
||||
MyTasks,
|
||||
MyAccompanyingCourses,
|
||||
MyNotifications,
|
||||
TabCounter,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'MyCustoms'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'loading',
|
||||
]),
|
||||
// just to see all in devtool :
|
||||
...mapState({
|
||||
state: (state) => state,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
selectTab(tab) {
|
||||
this.$store.dispatch('getByTab', { tab: tab });
|
||||
this.activeTab = tab;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
for (const m of [
|
||||
'MyNotifications',
|
||||
'MyAccompanyingCourses',
|
||||
'MyWorks',
|
||||
'MyEvaluations',
|
||||
'MyTasks',
|
||||
]) {
|
||||
this.$store.dispatch('getByTab', { tab: m, param: "countOnly=1" });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
a.nav-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="alert alert-light">{{ $t('my_accompanying_courses.description') }}</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">{{ $t('opening_date') }}</th>
|
||||
<th scope="col">{{ $t('social_issues') }}</th>
|
||||
<th scope="col">{{ $t('concerned_persons') }}</th>
|
||||
<th scope="col"></th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`">
|
||||
<td>{{ $d(c.openingDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
<span v-for="i in c.socialIssues"
|
||||
class="chill-entity entity-social-issue">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ i.title.fr }}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="p in c.participations" class="me-1" :key="p.person.id">
|
||||
<on-the-fly
|
||||
:type="p.person.type"
|
||||
:id="p.person.id"
|
||||
:buttonText="p.person.textAge"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show">
|
||||
</on-the-fly>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="c.emergency" class="badge rounded-pill bg-danger">{{ $t('emergency') }}</span>
|
||||
<span v-if="c.confidential" class="badge rounded-pill bg-danger">{{ $t('confidential') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(c)">
|
||||
{{ $t('show_entity', { entity: $t('the_course') }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tab-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
|
||||
|
||||
export default {
|
||||
name: "MyAccompanyingCourses",
|
||||
components: {
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'accompanyingCourses',
|
||||
]),
|
||||
...mapGetters([
|
||||
'isAccompanyingCoursesLoaded',
|
||||
]),
|
||||
noResults() {
|
||||
if (!this.isAccompanyingCoursesLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return this.accompanyingCourses.count === 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getUrl(c) {
|
||||
return `/fr/parcours/${c.id}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_dashboard') }}</span>
|
||||
<div v-else id="dashboards" class="row g-3" data-masonry='{"percentPosition": true }'>
|
||||
|
||||
<div class="mbloc col col-sm-6 col-lg-4">
|
||||
<div class="custom1">
|
||||
<ul class="list-unstyled">
|
||||
<li v-if="counter.notifications > 0">
|
||||
<i18n-t keypath="counter.unread_notifications" tag="span" :class="counterClass" :plural="counter.notifications">
|
||||
<template v-slot:n><span>{{ counter.notifications }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.accompanyingCourses > 0">
|
||||
<i18n-t keypath="counter.assignated_courses" tag="span" :class="counterClass" :plural="counter.accompanyingCourses">
|
||||
<template v-slot:n><span>{{ counter.accompanyingCourses }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.works > 0">
|
||||
<i18n-t keypath="counter.assignated_actions" tag="span" :class="counterClass" :plural="counter.works">
|
||||
<template v-slot:n><span>{{ counter.works }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.evaluations > 0">
|
||||
<i18n-t keypath="counter.assignated_evaluations" tag="span" :class="counterClass" :plural="counter.evaluations">
|
||||
<template v-slot:n><span>{{ counter.evaluations }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.tasksAlert > 0">
|
||||
<i18n-t keypath="counter.alert_tasks" tag="span" :class="counterClass" :plural="counter.tasksAlert">
|
||||
<template v-slot:n><span>{{ counter.tasksAlert }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.tasksWarning > 0">
|
||||
<i18n-t keypath="counter.warning_tasks" tag="span" :class="counterClass" :plural="counter.tasksWarning">
|
||||
<template v-slot:n><span>{{ counter.tasksWarning }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="mbloc col col-sm-6 col-lg-4">
|
||||
<div class="custom2">
|
||||
Mon dashboard personnalisé
|
||||
</div>
|
||||
</div>
|
||||
<div class="mbloc col col-sm-6 col-lg-4">
|
||||
<div class="custom3">
|
||||
Mon dashboard personnalisé
|
||||
</div>
|
||||
</div>
|
||||
<div class="mbloc col col-sm-6 col-lg-4">
|
||||
<div class="custom4">
|
||||
Mon dashboard personnalisé
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import Masonry from 'masonry-layout/masonry';
|
||||
|
||||
export default {
|
||||
name: "MyCustoms",
|
||||
data() {
|
||||
return {
|
||||
counterClass: {
|
||||
counter: true //hack to pass class 'counter' in i18n-t
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['counter']),
|
||||
noResults() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const elem = document.querySelector('#dashboards');
|
||||
const masonry = new Masonry(elem, {});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
div.custom4,
|
||||
div.custom3,
|
||||
div.custom2 {
|
||||
font-style: italic;
|
||||
color: var(--bs-chill-gray);
|
||||
}
|
||||
span.counter {
|
||||
& > span {
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="accompanying_course_work">
|
||||
<div class="alert alert-light">{{ $t('my_evaluations.description') }}</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">{{ $t('max_date') }}</th>
|
||||
<th scope="col">{{ $t('evaluation') }}</th>
|
||||
<th scope="col">{{ $t('SocialAction') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`">
|
||||
<td>{{ $d(e.maxDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
{{ e.evaluation.title.fr }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="chill-entity entity-social-issue">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ e.accompanyingPeriodWork.socialAction.issue.text }}
|
||||
</span>
|
||||
</span>
|
||||
<h4 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ e.accompanyingPeriodWork.socialAction.text }}
|
||||
</span>
|
||||
</h4>
|
||||
<span v-for="person in e.accompanyingPeriodWork.persons" class="me-1" :key="person.id">
|
||||
<on-the-fly
|
||||
:type="person.type"
|
||||
:id="person.id"
|
||||
:buttonText="person.textAge"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show">
|
||||
</on-the-fly>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical" role="group" aria-label="Actions">
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(e)">
|
||||
{{ $t('show_entity', { entity: $t('the_evaluation') }) }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-update" :href="getUrl(e.accompanyingPeriodWork)">
|
||||
{{ $t('show_entity', { entity: $t('the_action') }) }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(e.accompanyingPeriodWork.accompanyingPeriod)">
|
||||
{{ $t('show_entity', { entity: $t('the_course') }) }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tab-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
|
||||
|
||||
export default {
|
||||
name: "MyEvaluations",
|
||||
components: {
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'evaluations',
|
||||
]),
|
||||
...mapGetters([
|
||||
'isEvaluationsLoaded',
|
||||
]),
|
||||
noResults() {
|
||||
if (!this.isEvaluationsLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return this.evaluations.count === 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUrl(e) {
|
||||
switch (e.type) {
|
||||
case 'accompanying_period_work_evaluation':
|
||||
let anchor = '#evaluations';
|
||||
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork.id}/edit${anchor}`;
|
||||
case 'accompanying_period_work':
|
||||
return `/fr/person/accompanying-period/work/${e.id}/edit`
|
||||
case 'accompanying_period':
|
||||
return `/fr/parcours/${e.id}`
|
||||
default:
|
||||
throw 'entity type unknown';
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="alert alert-light">{{ $t('my_notifications.description') }}</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">{{ $t('Date') }}</th>
|
||||
<th scope="col">{{ $t('Subject') }}</th>
|
||||
<th scope="col">{{ $t('From') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(n, i) in notifications.results" :key="`notify-${i}`">
|
||||
<td>{{ $d(n.date.datetime, 'long') }}</td>
|
||||
<td>
|
||||
<span class="unread">
|
||||
<i class="fa fa-envelope-o"></i>
|
||||
<a :href="getNotificationUrl(n)">{{ n.title }}</a>
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ n.sender.text }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show"
|
||||
:href="getEntityUrl(n)">
|
||||
{{ $t('show_entity', { entity: getEntityName(n) }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tab-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import { appMessages } from 'ChillMainAssets/vuejs/HomepageWidget/js/i18n';
|
||||
|
||||
|
||||
export default {
|
||||
name: "MyNotifications",
|
||||
components: {
|
||||
TabTable
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'notifications',
|
||||
]),
|
||||
...mapGetters([
|
||||
'isNotificationsLoaded',
|
||||
]),
|
||||
noResults() {
|
||||
if (!this.isNotificationsLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return this.notifications.count === 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getNotificationUrl(n) {
|
||||
return `/fr/notification/${n.id}/show`
|
||||
},
|
||||
getEntityName(n) {
|
||||
switch (n.relatedEntityClass) {
|
||||
case 'Chill\\ActivityBundle\\Entity\\Activity':
|
||||
return appMessages.fr.the_activity;
|
||||
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
|
||||
return appMessages.fr.the_course;
|
||||
default:
|
||||
throw 'notification type unknown';
|
||||
}
|
||||
},
|
||||
getEntityUrl(n) {
|
||||
switch (n.relatedEntityClass) {
|
||||
case 'Chill\\ActivityBundle\\Entity\\Activity':
|
||||
return `/fr/activity/${n.relatedEntityId}/show`
|
||||
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
|
||||
return `/fr/parcours/${n.relatedEntityId}`
|
||||
default:
|
||||
throw 'notification type unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
span.unread {
|
||||
font-weight: bold;
|
||||
i {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
a {
|
||||
text-decoration: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
|
||||
<div class="alert alert-light">{{ $t('my_tasks.description_warning') }}</div>
|
||||
<span v-if="noResultsAlert" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">{{ $t('warning_date') }}</th>
|
||||
<th scope="col">{{ $t('max_date') }}</th>
|
||||
<th scope="col">{{ $t('task') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
|
||||
<td>{{ $d(t.warningDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
<span class="outdated">{{ $d(t.endDate.datetime, 'short') }}</span>
|
||||
</td>
|
||||
<td>{{ t.title }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(t)">
|
||||
{{ $t('show_entity', { entity: $t('the_task') }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tab-table>
|
||||
|
||||
<div class="alert alert-light">{{ $t('my_tasks.description_alert') }}</div>
|
||||
<span v-if="noResultsWarning" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">{{ $t('warning_date') }}</th>
|
||||
<th scope="col">{{ $t('max_date') }}</th>
|
||||
<th scope="col">{{ $t('task') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`">
|
||||
<td>
|
||||
<span class="outdated">{{ $d(t.warningDate.datetime, 'short') }}</span>
|
||||
</td>
|
||||
<td>{{ $d(t.endDate.datetime, 'short') }}</td>
|
||||
<td>{{ t.title }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(t)">
|
||||
{{ $t('show_entity', { entity: $t('the_task') }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tab-table>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
|
||||
export default {
|
||||
name: "MyTasks",
|
||||
components: {
|
||||
TabTable
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'tasks',
|
||||
]),
|
||||
...mapGetters([
|
||||
'isTasksWarningLoaded',
|
||||
'isTasksAlertLoaded',
|
||||
]),
|
||||
noResultsAlert() {
|
||||
if (!this.isTasksAlertLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return this.tasks.alert.count === 0;
|
||||
}
|
||||
},
|
||||
noResultsWarning() {
|
||||
if (!this.isTasksWarningLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return this.tasks.warning.count === 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUrl(t) {
|
||||
return `/fr/task/single-task/${t.id}/show`
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
span.outdated {
|
||||
font-weight: bold;
|
||||
color: var(--bs-warning);
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="accompanying_course_work">
|
||||
<div class="alert alert-light">{{ $t('my_works.description') }}</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">{{ $t('StartDate') }}</th>
|
||||
<th scope="col">{{ $t('SocialAction') }}</th>
|
||||
<th scope="col">{{ $t('concerned_persons') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(w, i) in works.results" :key="`works-${i}`">
|
||||
<td>{{ $d(w.startDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
<span class="chill-entity entity-social-issue">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ w.socialAction.issue.text }}
|
||||
</span>
|
||||
</span>
|
||||
<h4 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ w.socialAction.text }}
|
||||
</span>
|
||||
</h4>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="person in w.persons" class="me-1" :key="person.id">
|
||||
<on-the-fly
|
||||
:type="person.type"
|
||||
:id="person.id"
|
||||
:buttonText="person.textAge"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show">
|
||||
</on-the-fly>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical" role="group" aria-label="Actions">
|
||||
<a class="btn btn-sm btn-update" :href="getUrl(w)">
|
||||
{{ $t('show_entity', { entity: $t('the_action') }) }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(w.accompanyingPeriod)">
|
||||
{{ $t('show_entity', { entity: $t('the_course') }) }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tab-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
|
||||
|
||||
export default {
|
||||
name: "MyWorks",
|
||||
components: {
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'works',
|
||||
]),
|
||||
...mapGetters([
|
||||
'isWorksLoaded',
|
||||
]),
|
||||
noResults() {
|
||||
if (!this.isWorksLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return this.works.count === 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUrl(e) {
|
||||
switch (e.type) {
|
||||
case 'accompanying_period_work':
|
||||
return `/fr/person/accompanying-period/work/${e.id}/edit`
|
||||
case 'accompanying_period':
|
||||
return `/fr/parcours/${e.id}`
|
||||
default:
|
||||
throw 'entity type unknown';
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<span v-if="isCounterAvailable"
|
||||
class="badge rounded-pill bg-danger">
|
||||
{{ count }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TabCounter",
|
||||
props: ['count'],
|
||||
computed: {
|
||||
isCounterAvailable() {
|
||||
return (typeof this.count !== 'undefined' && this.count > 0 )
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<slot name="thead"></slot>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<slot name="tbody"></slot>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TabTable",
|
||||
props: []
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
@@ -0,0 +1,61 @@
|
||||
const appMessages = {
|
||||
fr: {
|
||||
main_title: "Vue d'ensemble",
|
||||
my_works: {
|
||||
tab: "Mes actions",
|
||||
description: "Liste des actions d'accompagnement dont je suis référent et qui arrivent à échéance.",
|
||||
},
|
||||
my_evaluations: {
|
||||
tab: "Mes évaluations",
|
||||
description: "Liste des évaluations dont je suis référent et qui arrivent à échéance.",
|
||||
},
|
||||
my_tasks: {
|
||||
tab: "Mes tâches",
|
||||
description_alert: "Liste des tâches auxquelles je suis assigné et dont la date de rappel est dépassée.",
|
||||
description_warning: "Liste des tâches auxquelles je suis assigné et dont la date d'échéance est dépassée.",
|
||||
},
|
||||
my_accompanying_courses: {
|
||||
tab: "Mes parcours",
|
||||
description: "Liste des parcours d'accompagnement que l'on vient de m'attribuer.",
|
||||
},
|
||||
my_notifications: {
|
||||
tab: "Mes notifications",
|
||||
description: "Liste des notifications reçues et non lues.",
|
||||
},
|
||||
opening_date: "Date d'ouverture",
|
||||
social_issues: "Problématiques sociales",
|
||||
concerned_persons: "Usagers concernés",
|
||||
max_date: "Date d'échéance",
|
||||
warning_date: "Date de rappel",
|
||||
evaluation: "Évaluation",
|
||||
task: "Tâche",
|
||||
Date: "Date",
|
||||
From: "Expéditeur",
|
||||
Subject: "Objet",
|
||||
Entity: "Associé à",
|
||||
show_entity: "Voir {entity}",
|
||||
the_activity: "l'échange",
|
||||
the_course: "le parcours",
|
||||
the_action: "l'action",
|
||||
the_evaluation: "l'évaluation",
|
||||
the_task: "la tâche",
|
||||
StartDate: "Date d'ouverture",
|
||||
SocialAction: "Action d'accompagnement",
|
||||
no_data: "Aucun résultats",
|
||||
no_dashboard: "Pas de tableaux de bord",
|
||||
counter: {
|
||||
unread_notifications: "{n} notification non lue | {n} notifications non lues",
|
||||
assignated_courses: "{n} parcours récent assigné | {n} parcours récents assignés",
|
||||
assignated_actions: "{n} action assignée | {n} actions assignées",
|
||||
assignated_evaluations: "{n} évaluation assignée | {n} évaluations assignées",
|
||||
alert_tasks: "{n} tâche en rappel | {n} tâches en rappel",
|
||||
warning_tasks: "{n} tâche à échéance | {n} tâches à échéance",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.assign(appMessages.fr);
|
||||
|
||||
export {
|
||||
appMessages
|
||||
};
|
@@ -0,0 +1,200 @@
|
||||
import 'es6-promise/auto';
|
||||
import { createStore } from 'vuex';
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import MyCustoms from "../MyCustoms";
|
||||
import MyWorks from "../MyWorks";
|
||||
import MyEvaluations from "../MyEvaluations";
|
||||
import MyTasks from "../MyTasks";
|
||||
import MyAccompanyingCourses from "../MyAccompanyingCourses";
|
||||
import MyNotifications from "../MyNotifications";
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
|
||||
const isEmpty = (obj) => {
|
||||
return obj
|
||||
&& Object.keys(obj).length <= 1
|
||||
&& Object.getPrototypeOf(obj) === Object.prototype;
|
||||
};
|
||||
|
||||
const store = createStore({
|
||||
strict: debug,
|
||||
state: {
|
||||
works: {},
|
||||
evaluations: {},
|
||||
tasks: {
|
||||
warning: {},
|
||||
alert: {}
|
||||
},
|
||||
accompanyingCourses: {},
|
||||
notifications: {},
|
||||
errorMsg: [],
|
||||
loading: false
|
||||
},
|
||||
getters: {
|
||||
isWorksLoaded(state) {
|
||||
return !isEmpty(state.works);
|
||||
},
|
||||
isEvaluationsLoaded(state) {
|
||||
return !isEmpty(state.evaluations);
|
||||
},
|
||||
isTasksWarningLoaded(state) {
|
||||
return !isEmpty(state.tasks.warning);
|
||||
},
|
||||
isTasksAlertLoaded(state) {
|
||||
return !isEmpty(state.tasks.alert);
|
||||
},
|
||||
isAccompanyingCoursesLoaded(state) {
|
||||
return !isEmpty(state.accompanyingCourses);
|
||||
},
|
||||
isNotificationsLoaded(state) {
|
||||
return !isEmpty(state.notifications);
|
||||
},
|
||||
counter(state) {
|
||||
return {
|
||||
works: state.works.count,
|
||||
evaluations: state.evaluations.count,
|
||||
tasksWarning: state.tasks.warning.count,
|
||||
tasksAlert: state.tasks.alert.count,
|
||||
accompanyingCourses: state.accompanyingCourses.count,
|
||||
notifications: state.notifications.count,
|
||||
}
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
addWorks(state, works) {
|
||||
//console.log('addWorks', works);
|
||||
state.works = works;
|
||||
},
|
||||
addEvaluations(state, evaluations) {
|
||||
//console.log('addEvaluations', evaluations);
|
||||
state.evaluations = evaluations;
|
||||
},
|
||||
addTasksWarning(state, tasks) {
|
||||
//console.log('addTasksWarning', tasks);
|
||||
state.tasks.warning = tasks;
|
||||
},
|
||||
addTasksAlert(state, tasks) {
|
||||
//console.log('addTasksAlert', tasks);
|
||||
state.tasks.alert = tasks;
|
||||
},
|
||||
addCourses(state, courses) {
|
||||
//console.log('addCourses', courses);
|
||||
state.accompanyingCourses = courses;
|
||||
},
|
||||
addNotifications(state, notifications) {
|
||||
//console.log('addNotifications', notifications);
|
||||
state.notifications = notifications;
|
||||
},
|
||||
setLoading(state, bool) {
|
||||
state.loading = bool;
|
||||
},
|
||||
catchError(state, error) {
|
||||
state.errorMsg.push(error);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
getByTab({ commit, getters }, { tab, param }) {
|
||||
switch (tab) {
|
||||
case 'MyCustoms':
|
||||
break;
|
||||
case 'MyWorks':
|
||||
if (!getters.isWorksLoaded) {
|
||||
commit('setLoading', true);
|
||||
const url = `/api/1.0/person/accompanying-period/work/my-near-end${'?'+ param}`;
|
||||
makeFetch('GET', url)
|
||||
.then((response) => {
|
||||
commit('addWorks', response);
|
||||
commit('setLoading', false);
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
throw error;
|
||||
})
|
||||
;
|
||||
}
|
||||
break;
|
||||
case 'MyEvaluations':
|
||||
if (!getters.isEvaluationsLoaded) {
|
||||
commit('setLoading', true);
|
||||
const url = `/api/1.0/person/accompanying-period/work/evaluation/my-near-end${'?'+ param}`;
|
||||
makeFetch('GET', url)
|
||||
.then((response) => {
|
||||
commit('addEvaluations', response);
|
||||
commit('setLoading', false);
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
throw error;
|
||||
})
|
||||
;
|
||||
}
|
||||
break;
|
||||
case 'MyTasks':
|
||||
if (!(getters.isTasksWarningLoaded && getters.isTasksAlertLoaded)) {
|
||||
commit('setLoading', true);
|
||||
const
|
||||
urlWarning = `/api/1.0/task/single-task/list/my?f[q]=&f[checkboxes][status][]=warning&f[checkboxes][states][]=new&f[checkboxes][states][]=in_progress${'&'+ param}`,
|
||||
urlAlert = `/api/1.0/task/single-task/list/my?f[q]=&f[checkboxes][status][]=alert&f[checkboxes][states][]=new&f[checkboxes][states][]=in_progress${'&'+ param}`
|
||||
;
|
||||
makeFetch('GET', urlWarning)
|
||||
.then((response) => {
|
||||
commit('addTasksWarning', response);
|
||||
commit('setLoading', false);
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
throw error;
|
||||
})
|
||||
;
|
||||
makeFetch('GET', urlAlert)
|
||||
.then((response) => {
|
||||
commit('addTasksAlert', response);
|
||||
commit('setLoading', false);
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
throw error;
|
||||
})
|
||||
;
|
||||
}
|
||||
break;
|
||||
case 'MyAccompanyingCourses':
|
||||
if (!getters.isAccompanyingCoursesLoaded) {
|
||||
commit('setLoading', true);
|
||||
const url = `/api/1.0/person/accompanying-course/list/by-recent-attributions${'?'+ param}`;
|
||||
makeFetch('GET', url)
|
||||
.then((response) => {
|
||||
commit('addCourses', response);
|
||||
commit('setLoading', false);
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
throw error;
|
||||
})
|
||||
;
|
||||
}
|
||||
break;
|
||||
case 'MyNotifications':
|
||||
if (!getters.isNotificationsLoaded) {
|
||||
commit('setLoading', true);
|
||||
const url = `/api/1.0/main/notification/my/unread${'?'+ param}`;
|
||||
makeFetch('GET', url)
|
||||
.then((response) => {
|
||||
commit('addNotifications', response);
|
||||
commit('setLoading', false);
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
throw error;
|
||||
})
|
||||
;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw 'tab '+ tab;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { store };
|
@@ -5,6 +5,7 @@
|
||||
:action="context.action"
|
||||
:buttonText="options.buttonText"
|
||||
:displayBadge="options.displayBadge === 'true'"
|
||||
:isDead="options.isDead"
|
||||
:parent="options.parent"
|
||||
@saveFormOnTheFly="saveFormOnTheFly">
|
||||
</on-the-fly>
|
||||
|
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
|
||||
<a v-if="isDisplayBadge" @click="openModal">
|
||||
<span class="chill-entity" :class="badgeType">
|
||||
{{ buttonText }}
|
||||
</span>
|
||||
</a>
|
||||
<a v-else class="btn btn-sm" target="_blank"
|
||||
<a v-if="isDisplayBadge" @click="openModal">
|
||||
<span class="chill-entity" :class="badgeType">
|
||||
{{ buttonText }}<span v-if="isDead"> (‡)</span>
|
||||
</span>
|
||||
</a>
|
||||
<a v-else class="btn btn-sm" target="_blank"
|
||||
:class="classAction"
|
||||
:title="$t(titleAction)"
|
||||
@click="openModal">
|
||||
{{ buttonText }}
|
||||
{{ buttonText }}<span v-if="isDead"> (‡)</span>
|
||||
</a>
|
||||
|
||||
<teleport to="body">
|
||||
@@ -101,7 +101,7 @@ export default {
|
||||
OnTheFlyThirdparty,
|
||||
OnTheFlyCreate
|
||||
},
|
||||
props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'parent'],
|
||||
props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'isDead', 'parent', 'canCloseModal'],
|
||||
emits: ['saveFormOnTheFly'],
|
||||
data() {
|
||||
return {
|
||||
@@ -179,7 +179,20 @@ export default {
|
||||
return 'entity-' + this.type + ' badge-' + this.type;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
canCloseModal: {
|
||||
handler: function(val, oldVal) {
|
||||
if (val) {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.modal.showModal = false;
|
||||
},
|
||||
openModal() {
|
||||
// console.log('## OPEN ON THE FLY MODAL');
|
||||
// console.log('## type:', this.type, ', action:', this.action);
|
||||
@@ -237,8 +250,6 @@ export default {
|
||||
console.log('type', type, 'data', data)
|
||||
// pass datas to parent
|
||||
this.$emit('saveFormOnTheFly', { type: type, data: data });
|
||||
|
||||
this.modal.showModal = false;
|
||||
},
|
||||
buildLocation(id, type) {
|
||||
if (type === 'person') {
|
||||
|
@@ -22,7 +22,8 @@ containers.forEach((container) => {
|
||||
options: {
|
||||
buttonText: container.dataset.buttonText || null,
|
||||
displayBadge: container.dataset.displayBadge || false,
|
||||
parent: JSON.parse(container.dataset.parent) || null,
|
||||
isDead: container.dataset.isDead || false,
|
||||
parent: container.dataset.parent ? JSON.parse(container.dataset.parent) : null,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,4 +34,5 @@ containers.forEach((container) => {
|
||||
.mount(container);
|
||||
|
||||
//console.log('container dataset', container.dataset);
|
||||
//console.log('data-parent', container.dataset.parent);
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ul class="list-suggest remove-items">
|
||||
<ul class="list-suggest remove-items" v-if="picked.length">
|
||||
<li v-for="p in picked" @click="removeEntity(p)" :key="p.type+p.id">
|
||||
<span class="chill_denomination">{{ p.text }}</span>
|
||||
</li>
|
||||
@@ -79,11 +79,15 @@ export default {
|
||||
);
|
||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||
modal.showModal = false;
|
||||
console.log(this.picked)
|
||||
},
|
||||
removeEntity(entity) {
|
||||
console.log('remove entity', entity);
|
||||
this.$emit('removeEntity', entity);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.picked);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -24,6 +24,11 @@
|
||||
{{ $t('user')}}
|
||||
</span>
|
||||
|
||||
<span v-if="entity.type === 'household'" class="badge rounded-pill bg-user">
|
||||
{{ $t('household')}}
|
||||
</span>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -40,7 +45,8 @@ export default {
|
||||
company: "Personne morale",
|
||||
contact: "Personne physique",
|
||||
},
|
||||
user: 'TMS'
|
||||
user: 'TMS',
|
||||
household: 'Ménage',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,26 @@
|
||||
<template>
|
||||
<div class="confidential" v-on:click="toggleBlur">
|
||||
<div class="confidential-content blur">
|
||||
<div :class="classes">
|
||||
<div class="confidential-content" :class="{ 'blur': isBlurred }">
|
||||
<slot name="confidential-content"></slot>
|
||||
</div>
|
||||
<i class="fa fa-eye toggle" aria-hidden="true"></i>
|
||||
<div>
|
||||
<i class="fa fa-eye toggle" aria-hidden="true" @click="toggleBlur"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Confidential",
|
||||
data() {
|
||||
return {
|
||||
isBlurred: true,
|
||||
};
|
||||
},
|
||||
methods : {
|
||||
toggleBlur: function(e){
|
||||
if(e.target.matches('.toggle')){
|
||||
e.target.previousElementSibling.classList.toggle("blur");
|
||||
e.target.classList.toggle("fa-eye");
|
||||
e.target.classList.toggle("fa-eye-slash");
|
||||
}
|
||||
toggleBlur() {
|
||||
console.log('toggle blur');
|
||||
this.isBlurred = !this.isBlurred;
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -39,4 +43,4 @@ export default {
|
||||
-moz-filter: blur(5px);
|
||||
filter: blur(5px);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@@ -1,26 +1,58 @@
|
||||
<template>
|
||||
|
||||
<component :is="component" class="chill-entity entity-address my-3">
|
||||
|
||||
<component :is="component" class="address" :class="multiline">
|
||||
<div v-if="isMultiline === true">
|
||||
<p v-for="(l, i) in address.lines" :key="`line-${i}`">
|
||||
{{ l }}
|
||||
</p>
|
||||
|
||||
<div v-if="isConfidential">
|
||||
<confidential>
|
||||
<template v-slot:confidential-content>
|
||||
<div v-if="isMultiline === true">
|
||||
<p v-for="(l, i) in address.lines" :key="`line-${i}`">
|
||||
{{ l }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p v-if="address.text"
|
||||
class="street">
|
||||
{{ address.text }}
|
||||
</p>
|
||||
<p v-if="address.postcode"
|
||||
class="postcode">
|
||||
{{ address.postcode.code }} {{ address.postcode.name }}
|
||||
</p>
|
||||
<p v-if="address.country"
|
||||
class="country">
|
||||
{{ address.country.name.fr }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</confidential>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p v-if="address.text"
|
||||
class="street">
|
||||
{{ address.text }}
|
||||
</p>
|
||||
<p v-if="address.postcode"
|
||||
class="postcode">
|
||||
{{ address.postcode.code }} {{ address.postcode.name }}
|
||||
</p>
|
||||
<p v-if="address.country"
|
||||
class="country">
|
||||
{{ address.country.name.fr }}
|
||||
</p>
|
||||
|
||||
<div v-if="!isConfidential">
|
||||
<div v-if="isMultiline === true">
|
||||
<p v-for="(l, i) in address.lines" :key="`line-${i}`">
|
||||
{{ l }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p v-if="address.text"
|
||||
class="street">
|
||||
{{ address.text }}
|
||||
</p>
|
||||
<p v-if="address.postcode"
|
||||
class="postcode">
|
||||
{{ address.postcode.code }} {{ address.postcode.name }}
|
||||
</p>
|
||||
<p v-if="address.country"
|
||||
class="country">
|
||||
{{ address.country.name.fr }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</component>
|
||||
|
||||
<!-- <div v-if="isMultiline === true" class="address-more">
|
||||
@@ -78,8 +110,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
|
||||
|
||||
export default {
|
||||
name: 'AddressRenderBox',
|
||||
components: {
|
||||
Confidential
|
||||
},
|
||||
props: {
|
||||
address: {
|
||||
type: Object
|
||||
@@ -100,6 +138,9 @@ export default {
|
||||
multiline() {
|
||||
//console.log(this.isMultiline, typeof this.isMultiline);
|
||||
return this.isMultiline === true ? "multiline" : "";
|
||||
},
|
||||
isConfidential() {
|
||||
return this.address.confidential;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="d-grid gap-2 my-3">
|
||||
<button class="btn btn-misc" type="button" v-if="!subscriberFinal" @click="subscribeTo('subscribe', 'final')">
|
||||
<i class="fa fa-check fa-fw"></i>
|
||||
{{ $t('subscribe_final') }}
|
||||
</button>
|
||||
<button class="btn btn-misc" type="button" v-if="subscriberFinal" @click="subscribeTo('unsubscribe', 'final')">
|
||||
<i class="fa fa-times fa-fw"></i>
|
||||
{{ $t('unsubscribe_final') }}
|
||||
</button>
|
||||
<button class="btn btn-misc" type="button" v-if="!subscriberStep" @click="subscribeTo('subscribe', 'step')">
|
||||
<i class="fa fa-check fa-fw"></i>
|
||||
{{ $t('subscribe_all_steps') }}
|
||||
</button>
|
||||
<button class="btn btn-misc" type="button" v-if="subscriberStep" @click="subscribeTo('unsubscribe', 'step')">
|
||||
<i class="fa fa-times fa-fw"></i>
|
||||
{{ $t('unsubscribe_all_steps') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.js';
|
||||
|
||||
export default {
|
||||
name: "EntityWorkflowVueSubscriber",
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
subscribe_final: "Recevoir une notification à l'étape finale",
|
||||
unsubscribe_final: "Ne plus recevoir de notification à l'étape finale",
|
||||
subscribe_all_steps: "Recevoir une notification à chaque étape du suivi",
|
||||
unsubscribe_all_steps: "Ne plus recevoir de notification à chaque étape du suivi",
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
entityWorkflowId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
subscriberStep: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
subscriberFinal: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['subscriptionUpdated'],
|
||||
methods: {
|
||||
subscribeTo(step, to) {
|
||||
let params = new URLSearchParams();
|
||||
params.set('subscribe', to);
|
||||
|
||||
const url = `/api/1.0/main/workflow/${this.entityWorkflowId}/${step}?` + params.toString();
|
||||
|
||||
makeFetch('POST', url).then(response => {
|
||||
this.$emit('subscriptionUpdated', response);
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
/*
|
||||
* ALTERNATIVES
|
||||
*
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="laststep">
|
||||
<label class="form-check-label" for="laststep">{{ $t('subscribe_final') }}</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="allsteps">
|
||||
<label class="form-check-label" for="allsteps">{{ $t('subscribe_all_steps') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="list-group my-3">
|
||||
<label class="list-group-item">
|
||||
<input class="form-check-input me-1" type="checkbox" value="">
|
||||
{{ $t('subscribe_final') }}
|
||||
</label>
|
||||
<label class="list-group-item">
|
||||
<input class="form-check-input me-1" type="checkbox" value="">
|
||||
{{ $t('subscribe_all_steps') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="btn-group-vertical my-3" role="group">
|
||||
<button type="button" class="btn btn-outline-primary">
|
||||
<i class="fa fa-check fa-fw"></i>
|
||||
{{ $t('subscribe_final') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary">
|
||||
<i class="fa fa-check fa-fw"></i>
|
||||
{{ $t('subscribe_all_steps') }}
|
||||
</button>
|
||||
</div>
|
||||
*/
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="flex-table workflow" id="workflow-list">
|
||||
<div v-for="(w, i) in workflows" :key="`workflow-${i}`"
|
||||
class="item-bloc">
|
||||
|
||||
<div>
|
||||
<div class="item-row col">
|
||||
<h2>Workflow</h2>
|
||||
<div class="flex-grow-1 ms-3 h3">
|
||||
<div class="visually-hidden">
|
||||
{{ w.relatedEntityClass }}
|
||||
{{ w.relatedEntityId }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumb">
|
||||
<template v-for="(step, j) in w.steps" :key="`step-${j}`">
|
||||
<span class="mx-2"
|
||||
tabindex="0"
|
||||
data-bs-trigger="focus hover"
|
||||
data-bs-toggle="popover"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-custom-class="workflow-transition"
|
||||
:title="getPopTitle(step)"
|
||||
:data-bs-content="getPopContent(step)">
|
||||
|
||||
<i v-if="step.currentStep.name === 'initial'"
|
||||
class="fa fa-circle me-1 text-chill-yellow">
|
||||
</i>
|
||||
<i v-if="step.isFreezed"
|
||||
class="fa fa-snowflake-o fa-sm me-1">
|
||||
</i>
|
||||
{{ step.currentStep.text }}
|
||||
</span>
|
||||
<span v-if="j !== Object.keys(w.steps).length - 1">
|
||||
→
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-row">
|
||||
<div class="item-col flex-grow-1">
|
||||
<p v-if="isUserSubscribedToStep(w)">
|
||||
<i class="fa fa-check fa-fw"></i>
|
||||
{{ $t('you_subscribed_to_all_steps') }}
|
||||
</p>
|
||||
<p v-if="isUserSubscribedToFinal(w)">
|
||||
<i class="fa fa-check fa-fw"></i>
|
||||
{{ $t('you_subscribed_to_final_step') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a :href="goToUrl(w)" class="btn btn-sm btn-show" :title="$t('action.show')"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popover from 'bootstrap/js/src/popover';
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
you_subscribed_to_all_steps: "Vous recevrez une notification à chaque étape",
|
||||
you_subscribed_to_final_step: "Vous recevrez une notification à l'étape finale",
|
||||
by: "Par",
|
||||
at: "Le"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "ListWorkflow",
|
||||
i18n: i18n,
|
||||
props: {
|
||||
workflows: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goToUrl(w) {
|
||||
return `/fr/main/workflow/${w.id}/show`;
|
||||
},
|
||||
getPopTitle(step) {
|
||||
if (step.transitionPrevious != null) {
|
||||
//console.log(step.transitionPrevious.text);
|
||||
let freezed = step.isFreezed ? `<i class="fa fa-snowflake-o fa-sm me-1"></i>` : ``;
|
||||
return `${freezed}${step.transitionPrevious.text}`;
|
||||
}
|
||||
},
|
||||
getPopContent(step) {
|
||||
if (step.transitionPrevious != null) {
|
||||
return `<ul class="small_in_title">
|
||||
<li><span class="item-key">${i18n.messages.fr.by} : </span><b>${step.transitionPreviousBy.text}</b></li>
|
||||
<li><span class="item-key">${i18n.messages.fr.at} : </span><b>${this.formatDate(step.transitionPreviousAt.datetime)}</b></li>
|
||||
</ul>`
|
||||
;
|
||||
}
|
||||
},
|
||||
formatDate(datetime) {
|
||||
return datetime.split('T')[0] +' '+ datetime.split('T')[1].substring(0,5)
|
||||
},
|
||||
isUserSubscribedToStep(w) {
|
||||
// todo
|
||||
return false;
|
||||
},
|
||||
isUserSubscribedToFinal(w) {
|
||||
// todo
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||
const popoverList = triggerList.map(function (el) {
|
||||
//console.log('popover', el)
|
||||
return new Popover(el, {
|
||||
html: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
|
||||
<button v-if="hasWorkflow"
|
||||
class="btn btn-primary"
|
||||
@click="openModal">
|
||||
<b>{{ countWorkflows }}</b>
|
||||
<template v-if="countWorkflows > 1">{{ $t('workflows') }}</template>
|
||||
<template v-else>{{ $t('workflow') }}</template>
|
||||
</button>
|
||||
|
||||
<pick-workflow v-else-if="allowCreate"
|
||||
:relatedEntityClass="this.relatedEntityClass"
|
||||
:relatedEntityId="this.relatedEntityId"
|
||||
:workflowsAvailables="workflowsAvailables"
|
||||
@go-to-generate-workflow="goToGenerateWorkflow"
|
||||
></pick-workflow>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<h2 class="modal-title">{{ $t('workflow_list') }}</h2>
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<list-workflow-vue
|
||||
:workflows="workflows"
|
||||
></list-workflow-vue>
|
||||
</template>
|
||||
|
||||
<template v-slot:footer>
|
||||
<pick-workflow v-if="allowCreate"
|
||||
:relatedEntityClass="this.relatedEntityClass"
|
||||
:relatedEntityId="this.relatedEntityId"
|
||||
:workflowsAvailables="workflowsAvailables"
|
||||
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate"
|
||||
@go-to-generate-workflow="this.goToGenerateWorkflow"
|
||||
></pick-workflow>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||
import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
|
||||
import ListWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue';
|
||||
|
||||
export default {
|
||||
name: "ListWorkflowModal",
|
||||
components: {
|
||||
Modal,
|
||||
PickWorkflow,
|
||||
ListWorkflowVue
|
||||
},
|
||||
emits: ['goToGenerateWorkflow'],
|
||||
props: {
|
||||
workflows: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
allowCreate: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
relatedEntityClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
relatedEntityId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
workflowsAvailables: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
preventDefaultMoveToGenerate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countWorkflows() {
|
||||
return this.workflows.length;
|
||||
},
|
||||
hasWorkflow() {
|
||||
return this.countWorkflows > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
goToGenerateWorkflow(data) {
|
||||
console.log('go to generate workflow intercepted');
|
||||
this.$emit('goToGenerateWorkflow', data);
|
||||
}
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
workflow_list: "Liste des workflows associés",
|
||||
workflow: " workflow associé",
|
||||
workflows: " workflows associés",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<template v-if="workflowsAvailables.length >= 1">
|
||||
<div class="dropdown d-grid gap-2">
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" id="createWorkflowButton" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Créer un workflow
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="createWorkflowButton">
|
||||
<li v-for="w in workflowsAvailables" :key="w.name">
|
||||
<a class="dropdown-item" :href="makeLink(w.name)" @click.prevent="goToGenerateWorkflow($event, w.name)">{{ w.text }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
|
||||
|
||||
export default {
|
||||
name: "PickWorkflow",
|
||||
props: {
|
||||
relatedEntityClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
relatedEntityId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
workflowsAvailables: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
preventDefaultMoveToGenerate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['goToGenerateWorkflow'],
|
||||
methods: {
|
||||
makeLink(workflowName) {
|
||||
return buildLinkCreate(workflowName, this.relatedEntityClass, this.relatedEntityId);
|
||||
},
|
||||
goToGenerateWorkflow(event, workflowName) {
|
||||
console.log('goToGenerateWorkflow', event, workflowName);
|
||||
|
||||
if (!this.$props.preventDefaultMoveToGenerate) {
|
||||
console.log('to go generate');
|
||||
window.location.assign(this.makeLink(workflowName));
|
||||
}
|
||||
|
||||
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName)});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -15,7 +15,7 @@
|
||||
<div class="modal-body">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer" v-if="!hideFooter">
|
||||
<button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
@@ -38,7 +38,16 @@
|
||||
*/
|
||||
export default {
|
||||
name: 'Modal',
|
||||
props: ['modalDialogClass'],
|
||||
props: {
|
||||
modalDialogClass: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
emits: ['close']
|
||||
}
|
||||
</script>
|
||||
|
@@ -34,7 +34,7 @@
|
||||
:href="showUrl"
|
||||
:title="$t('action.show')"
|
||||
>
|
||||
<i class="fa fa-sm fa-eye"></i>
|
||||
<i class="fa fa-sm fa-comment-o"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<a v-if="isOpenDocument"
|
||||
class="btn change-icon" :class="[isChangeClass ? button.changeClass : 'btn-edit']"
|
||||
@click="openModal">
|
||||
|
||||
<i class="fa me-2" :class="[isChangeIcon ? button.changeIcon : 'fa-pencil']"></i>
|
||||
|
||||
<span v-if="!noText">
|
||||
{{ $t('Update_document') }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<teleport to="body">
|
||||
<div class="wopi-frame" v-if="isOpenDocument">
|
||||
<modal v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
:hideFooter=true
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<img class="logo" :src="logo" height="45"/>
|
||||
<span class="ms-auto me-3">
|
||||
{{ this.title }}
|
||||
</span>
|
||||
<a class="btn btn-outline-light">
|
||||
<i class="fa fa-save fa-fw"></i>
|
||||
{{ $t('save_and_quit') }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<div v-if="loading" class="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-3x" :title="$t('loading')"></i>
|
||||
</div>
|
||||
<iframe
|
||||
:src="this.wopiUrl"
|
||||
@load="loaded"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</div>
|
||||
<div v-else>
|
||||
<modal v-if="modal.showModal"
|
||||
modalDialogClass="modal-sm"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<h3>{{ $t('invalid_title') }}</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<div class="alert alert-warning">{{ $t('invalid_message') }}</div>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||
import logo from 'ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png';
|
||||
|
||||
export default {
|
||||
name: "OpenWopiLink",
|
||||
components: {
|
||||
Modal
|
||||
},
|
||||
props: {
|
||||
wopiUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
button: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-fullscreen" //modal-dialog-scrollable
|
||||
},
|
||||
logo: logo,
|
||||
loading: false,
|
||||
mime: [
|
||||
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
|
||||
'application/clarisworks',
|
||||
'application/coreldraw',
|
||||
'application/macwriteii',
|
||||
'application/msword',
|
||||
'application/pdf',
|
||||
'application/vnd.lotus-1-2-3',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||
'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||
'application/vnd.ms-excel.template.macroEnabled.12',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
|
||||
'application/vnd.ms-powerpoint.template.macroEnabled.12',
|
||||
'application/vnd.ms-visio.drawing',
|
||||
'application/vnd.ms-word.document.macroEnabled.12',
|
||||
'application/vnd.ms-word.template.macroEnabled.12',
|
||||
'application/vnd.ms-works',
|
||||
'application/vnd.oasis.opendocument.chart',
|
||||
'application/vnd.oasis.opendocument.formula',
|
||||
'application/vnd.oasis.opendocument.graphics',
|
||||
'application/vnd.oasis.opendocument.graphics-flat-xml',
|
||||
'application/vnd.oasis.opendocument.graphics-template',
|
||||
'application/vnd.oasis.opendocument.presentation',
|
||||
'application/vnd.oasis.opendocument.presentation-flat-xml',
|
||||
'application/vnd.oasis.opendocument.presentation-template',
|
||||
'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'application/vnd.oasis.opendocument.spreadsheet-flat-xml',
|
||||
'application/vnd.oasis.opendocument.spreadsheet-template',
|
||||
'application/vnd.oasis.opendocument.text',
|
||||
'application/vnd.oasis.opendocument.text-flat-xml',
|
||||
'application/vnd.oasis.opendocument.text-master',
|
||||
'application/vnd.oasis.opendocument.text-master-template',
|
||||
'application/vnd.oasis.opendocument.text-template',
|
||||
'application/vnd.oasis.opendocument.text-web',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'application/vnd.sun.xml.calc',
|
||||
'application/vnd.sun.xml.calc.template',
|
||||
'application/vnd.sun.xml.chart',
|
||||
'application/vnd.sun.xml.draw',
|
||||
'application/vnd.sun.xml.draw.template',
|
||||
'application/vnd.sun.xml.impress',
|
||||
'application/vnd.sun.xml.impress.template',
|
||||
'application/vnd.sun.xml.math',
|
||||
'application/vnd.sun.xml.writer',
|
||||
'application/vnd.sun.xml.writer.global',
|
||||
'application/vnd.sun.xml.writer.template',
|
||||
'application/vnd.visio',
|
||||
'application/vnd.visio2013',
|
||||
'application/vnd.wordperfect',
|
||||
'application/x-abiword',
|
||||
'application/x-aportisdoc',
|
||||
'application/x-dbase',
|
||||
'application/x-dif-document',
|
||||
'application/x-fictionbook+xml',
|
||||
'application/x-gnumeric',
|
||||
'application/x-hwp',
|
||||
'application/x-iwork-keynote-sffkey',
|
||||
'application/x-iwork-numbers-sffnumbers',
|
||||
'application/x-iwork-pages-sffpages',
|
||||
'application/x-mspublisher',
|
||||
'application/x-mswrite',
|
||||
'application/x-pagemaker',
|
||||
'application/x-sony-bbeb',
|
||||
'application/x-t602',
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isOpenDocument() {
|
||||
if (this.mime.indexOf(this.type) !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
noText() {
|
||||
if (typeof this.button.noText !== 'undefined') {
|
||||
return this.button.noText === true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isChangeIcon() {
|
||||
if (typeof this.button.changeIcon !== 'undefined') {
|
||||
return (!(this.button.changeIcon === null || this.button.changeIcon === ''))
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isChangeClass() {
|
||||
if (typeof this.button.changeClass !== 'undefined') {
|
||||
return (!(this.button.changeClass === null || this.button.changeClass === ''))
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.loading = true;
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
loaded() {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
Update_document: "Modifier le document",
|
||||
save_and_quit: "Enregistrer et quitter",
|
||||
loading: "Chargement de l'éditeur en ligne",
|
||||
invalid_title: "Format incompatible",
|
||||
invalid_message: "Désolé, ce format de document n'est pas éditable en ligne.",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
div.wopi-frame {
|
||||
div.modal-header {
|
||||
border-bottom: 0;
|
||||
background-color: var(--bs-primary);
|
||||
color: white;
|
||||
}
|
||||
div.modal-body {
|
||||
padding: 0;
|
||||
overflow-y: unset !important;
|
||||
iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
div.loading {
|
||||
position: absolute;
|
||||
color: var(--bs-chill-gray);
|
||||
top: calc(50% - 30px);
|
||||
left: calc(50% - 30px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@@ -62,14 +62,15 @@ const messages = {
|
||||
woman: "Née le"
|
||||
},
|
||||
deathdate: "Date de décès",
|
||||
years_old: "ans",
|
||||
household_without_address: "Le ménage de l'usager est sans adresse",
|
||||
no_data: "Aucune information renseignée",
|
||||
type: {
|
||||
thirdparty: "Tiers",
|
||||
person: "Usager"
|
||||
},
|
||||
holder: "Titulaire"
|
||||
holder: "Titulaire",
|
||||
years_old: "an | {n} an | {n} ans",
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{% block navigation_search_bar %}{% endblock %}
|
||||
{% block navigation_section_menu %}
|
||||
{{ chill_menu('admin_section', {
|
||||
'layout': '@ChillMain/Menu/adminSection.html.twig',
|
||||
'layout': '@ChillMain/Menu/admin.html.twig',
|
||||
}) }}
|
||||
{% endblock %}
|
||||
|
||||
|
@@ -23,23 +23,23 @@
|
||||
|
||||
{% if options['metadata'] %}
|
||||
<div class="metadata">
|
||||
{% if user is not empty %}
|
||||
{{ 'Last updated by'| trans }}
|
||||
<span class="user">
|
||||
{{ user|chill_entity_render_box(options['user']) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if comment.date is not empty %}
|
||||
{% if user is empty %}
|
||||
{{ 'Last updated on'|trans ~ ' ' }}
|
||||
{% else %}
|
||||
{{ 'on'|trans ~ ' ' }}
|
||||
{% endif %}
|
||||
{{ 'Last updated on'|trans ~ ' ' }}
|
||||
<span class="date">
|
||||
{{ comment.date|format_datetime("medium", "short") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if user is not empty %}
|
||||
{% if comment.date is empty %}
|
||||
{{ 'Last updated by'| trans }}
|
||||
{% else %}
|
||||
{{ 'by_user'|trans ~ ' ' }}
|
||||
{% endif %}
|
||||
<span class="user">
|
||||
{{ user|chill_entity_render_box(options['user']) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</blockquote>
|
||||
{{ closing_box|raw }}
|
||||
{{ closing_box|raw }}
|
@@ -59,7 +59,7 @@
|
||||
must be shown in such list
|
||||
#}
|
||||
{%- if render == 'list' -%}
|
||||
<li class="chill-entity entity-address">
|
||||
<li class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}">
|
||||
{% if options['with_picto'] %}
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
{% endif %}
|
||||
@@ -68,7 +68,7 @@
|
||||
{%- endif -%}
|
||||
|
||||
{%- if render == 'inline' -%}
|
||||
<span class="chill-entity entity-address">
|
||||
<span class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}">
|
||||
{% if options['with_picto'] %}
|
||||
<i class="fa fa-fw fa-map-marker"></i>
|
||||
{% endif %}
|
||||
@@ -77,7 +77,7 @@
|
||||
{%- endif -%}
|
||||
|
||||
{%- if render == 'bloc' -%}
|
||||
<div class="chill-entity entity-address">
|
||||
<div class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}">
|
||||
{% if options['has_no_address'] == true and address.isNoAddress == true %}
|
||||
{% if address.postCode is not empty %}
|
||||
<div class="address{% if options['multiline'] %} multiline{% endif %}{% if options['with_delimiter'] %} delimiter{% endif %}">
|
||||
|
@@ -216,7 +216,7 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pick_user_dynamic_widget %}
|
||||
{% block pick_entity_dynamic_widget %}
|
||||
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
||||
<div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div>
|
||||
{% endblock %}
|
||||
|
@@ -0,0 +1,3 @@
|
||||
<div class="sticky-buttons">
|
||||
{# Override this file to add fast actions buttons #}
|
||||
</div>
|
@@ -0,0 +1,15 @@
|
||||
<div class="col-10 mt-5">
|
||||
|
||||
{# vue component #}
|
||||
<div id="homepage_widget"></div>
|
||||
|
||||
{% include '@ChillMain/Homepage/fast_actions.html.twig' %}
|
||||
</div>
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('page_homepage_widget') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('page_homepage_widget') }}
|
||||
{% endblock %}
|
@@ -23,7 +23,7 @@
|
||||
<td>{{ entity.email }}</td>
|
||||
<td>
|
||||
{% if entity.address is not null %}
|
||||
{{ entity.address.street}}, {{ entity.address.streetnumber }}
|
||||
{{ entity.address| chill_entity_render_box }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align:center;">
|
||||
|
@@ -8,6 +8,7 @@
|
||||
<tr>
|
||||
<th>{{ 'Title'|trans }}</th>
|
||||
<th>{{ 'Available for users'|trans }}</th>
|
||||
<th>{{ 'Editable by users'|trans }}</th>
|
||||
<th>{{ 'Address required'|trans }}</th>
|
||||
<th>{{ 'Contact data'|trans }}</th>
|
||||
<th>{{ 'Active'|trans }}</th>
|
||||
@@ -25,6 +26,13 @@
|
||||
<i class="fa fa-square-o"></i>
|
||||
{%- endif -%}
|
||||
</td>
|
||||
<td style="text-align:center;">
|
||||
{%- if entity.editableByUsers -%}
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
{%- else -%}
|
||||
<i class="fa fa-square-o"></i>
|
||||
{%- endif -%}
|
||||
</td>
|
||||
<td>{{ entity.addressRequired|trans }}</td>
|
||||
<td>{{ entity.contactData|trans }}</td>
|
||||
<td style="text-align:center;">
|
||||
|
@@ -21,13 +21,13 @@
|
||||
layout ../layoutWithVerticalMenu.html.twig.
|
||||
#}
|
||||
|
||||
<ul class="tab-nav follow-href-path">
|
||||
<li class="title">
|
||||
<div class="list-group vertical-menu {{ 'menu-' ~ menus.name }}">
|
||||
<a class="list-group-item title">
|
||||
{% block v_menu_title %}<!-- title of the verticalMenu is empty -->{% endblock %}
|
||||
</li>
|
||||
</a>
|
||||
{% for menu in menus %}
|
||||
<li class="{% if menu is knp_menu_current %}current {% endif %}">
|
||||
<a href="{{ menu.uri }}" >{{ menu.label|trans }}</a>
|
||||
</li>
|
||||
<a class="list-group-item list-group-item-action" href="{{ menu.uri }}">
|
||||
{{ menu.label|upper }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -0,0 +1,77 @@
|
||||
{% import '@ChillPerson/AccompanyingCourse/Comment/macro_showItem.html.twig' as m %}
|
||||
|
||||
{% macro recordAction(comment) %}
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_COMMENT_EDIT', comment) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_notification_show', {
|
||||
'_fragment': 'comment-' ~ comment.id,
|
||||
'edit': comment.id,
|
||||
'id': comment.notification.id
|
||||
}) }}" class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
||||
></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="notification-comment-list my-5">
|
||||
<h2 class="chill-blue">{{ 'notification.comments_list'|trans }}</h2>
|
||||
|
||||
{% if notification.comments|length > 0 %}
|
||||
<div class="flex-table">
|
||||
{% for comment in notification.comments %}
|
||||
|
||||
{% if editedCommentForm is null or editedCommentId != comment.id %}
|
||||
{{ m.show_comment(comment, {
|
||||
'recordAction': _self.recordAction(comment)
|
||||
}) }}
|
||||
{% else %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row row">
|
||||
<a id="comment-{{ comment.id }}"></a>
|
||||
|
||||
{{ form_start(editedCommentForm) }}
|
||||
{{ form_errors(editedCommentForm) }}
|
||||
{{ form_widget(editedCommentForm.content) }}
|
||||
<input type="hidden" name="form" value="edit" />
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_notification_show', {
|
||||
'_fragment': 'comment-' ~ comment.id,
|
||||
'id': notification.id }) }}" class="btn btn-cancel">
|
||||
{{ 'cancel'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-save" type="submit">{{ 'Save'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(editedCommentForm) }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No comments'|trans }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if appendCommentForm is not null %}
|
||||
<div class="new-comment my-5">
|
||||
<h2 class="chill-blue mb-4">{{ 'Write a new comment'|trans }}</h2>
|
||||
|
||||
{{ form_start(appendCommentForm) }}
|
||||
{{ form_errors(appendCommentForm) }}
|
||||
{{ form_widget(appendCommentForm.content) }}
|
||||
<input type="hidden" name="form" value="append" />
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button class="btn btn-create" type="submit">{{ 'notification.append_comment'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(appendCommentForm) }}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
@@ -1,39 +1,41 @@
|
||||
<div class="item-bloc {% if not notification.isReadBy(app.user) %}unread{% else %}read{% endif %}">
|
||||
{% macro title(c) %}
|
||||
<div class="item-row title">
|
||||
<h2 class="notification-title">
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}">
|
||||
{{ notification.title }}
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">
|
||||
{{ c.notification.title }}
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="item-row mt-2 header">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro header(c) %}
|
||||
<div class="item-row notification-header mt-2">
|
||||
<div class="item-col">
|
||||
<ul class="small_in_title">
|
||||
{% if step is not defined or step == 'inbox' %}
|
||||
{% if c.step is not defined or c.step == 'inbox' %}
|
||||
<li class="notification-from">
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.received_from'|trans }}">
|
||||
{{ 'notification.from'|trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
{% if not notification.isSystem %}
|
||||
{% if not c.notification.isSystem %}
|
||||
<span class="badge-user">
|
||||
{{ notification.sender|chill_entity_render_string }}
|
||||
</span>
|
||||
{{ c.notification.sender|chill_entity_render_string }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if notification.addressees|length > 0 %}
|
||||
{% if c.notification.addressees|length > 0 %}
|
||||
<li class="notification-to">
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.sent_to'|trans }}">
|
||||
{{ 'notification.to'|trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
{% for a in notification.addressees %}
|
||||
{% for a in c.notification.addressees %}
|
||||
<span class="badge-user">
|
||||
{{ a|chill_entity_render_string }}
|
||||
</span>
|
||||
@@ -42,47 +44,102 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
{{ notification.date|format_datetime('long', 'short') }}
|
||||
{{ c.notification.date|format_datetime('long', 'short') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
</div>
|
||||
{% macro content(c) %}
|
||||
<div class="item-row separator">
|
||||
<div class="mx-3 flex-grow-1">
|
||||
{% include data.template with data.template_data %}
|
||||
{% if c.data is defined %}
|
||||
<div class="mx-3 flex-grow-1">
|
||||
{% include c.data.template with c.data.template_data %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="notification-content">
|
||||
{% if c.full_content is defined and c.full_content == true %}
|
||||
{{ c.notification.message|chill_markdown_to_html }}
|
||||
{% else %}
|
||||
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
||||
<p class="read-more"><a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">{{ 'Read more'|trans }}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row row">
|
||||
<div>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
{% if action_button is not defined or action_button != 'false' %}
|
||||
{% if c.action_button is not defined or c.action_button != false %}
|
||||
<div class="item-row separator">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{# Vue component #}
|
||||
<span class="notification_toggle_read_status"
|
||||
data-notification-id="{{ notification.id }}"
|
||||
data-notification-current-is-read="{{ notification.isReadBy(app.user) }}"
|
||||
></span>
|
||||
</li>
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', notification) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': notification.id}) }}"
|
||||
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
|
||||
</li>
|
||||
<div class="item-col item-meta">
|
||||
|
||||
{% if c.notification.comments|length > 0 %}
|
||||
<div class="comment-counter">
|
||||
<span class="counter">
|
||||
{{ 'notification.counter comments'|trans({'nb': c.notification.comments|length }) }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', notification) %}
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': notification.id}) }}"
|
||||
class="btn btn-show" title="{{ 'Show'|trans }}"></a>
|
||||
{# Vue component #}
|
||||
<span class="notification_toggle_read_status"
|
||||
data-notification-id="{{ c.notification.id }}"
|
||||
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
|
||||
data-container="notification-status"
|
||||
></span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': c.notification.id}) }}"
|
||||
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
|
||||
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}">
|
||||
{% if not c.notification.isSystem() %}
|
||||
<i class="fa fa-comment"></i>
|
||||
{% else %}
|
||||
{{ 'Read more'|trans }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
|
||||
|
||||
{% if fold_item is defined and fold_item != false %}
|
||||
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
|
||||
<button type="button" class="accordion-button collapsed"
|
||||
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
|
||||
aria-expanded="false" aria-controls="flush-collapse-{{ notification.id }}">
|
||||
|
||||
{{ _self.title(_context) }}
|
||||
</button>
|
||||
{{ _self.header(_context) }}
|
||||
|
||||
</div>
|
||||
<div id="flush-collapse-{{ notification.id }}"
|
||||
class="accordion-collapse collapse"
|
||||
aria-labelledby="flush-heading-{{ notification.id }}"
|
||||
data-bs-parent="#notification-fold">
|
||||
|
||||
{{ _self.content(_context) }}
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
{{ _self.title(_context) }}
|
||||
{{ _self.header(_context) }}
|
||||
{{ _self.content(_context) }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
{{ form_row(form.title, { 'label': 'notification.subject'|trans }) }}
|
||||
{{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }}
|
||||
|
||||
|
||||
{% include handler.template(notification) with handler.templateData(notification) %}
|
||||
|
||||
<div class="mb-3 row">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user