mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-25 16:14:59 +00:00
Compare commits
1 Commits
ticket/64-
...
385-invita
Author | SHA1 | Date | |
---|---|---|---|
95d9a75e46 |
@@ -1,6 +0,0 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
|
||||
time: 2024-05-30T16:00:03.440767606+02:00
|
||||
custom:
|
||||
Issue: ""
|
@@ -1,6 +0,0 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade CKEditor and refactor configuration with use of typescript
|
||||
time: 2024-05-31T19:02:42.776662753+02:00
|
||||
custom:
|
||||
Issue: ""
|
6
.changes/unreleased/Feature-20250808-120802.yaml
Normal file
6
.changes/unreleased/Feature-20250808-120802.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Create invitation list in user menu
|
||||
time: 2025-08-08T12:08:02.446361367+02:00
|
||||
custom:
|
||||
Issue: "385"
|
||||
SchemaChange: No schema change
|
@@ -1,6 +0,0 @@
|
||||
kind: Feature
|
||||
body: Add a command to generate a list of permissions
|
||||
time: 2025-09-04T18:10:32.334524026+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
@@ -19,11 +19,11 @@ max_line_length = 80
|
||||
[COMMIT_EDITMSG]
|
||||
max_line_length = 0
|
||||
|
||||
[*.{js,vue,ts}]
|
||||
[*.{js, vue, ts}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[*.rst]
|
||||
indent_size = 3
|
||||
indent_style = space
|
||||
[.rst]
|
||||
ident_size = 3
|
||||
ident_style = space
|
||||
|
||||
|
@@ -234,16 +234,20 @@ This must be a decision made by a human, not by an AI. Every AI task must abort
|
||||
|
||||
#### Running Tests
|
||||
|
||||
The tests are run from the project's root (not from the bundle's root: so, do not change the directory to any bundle directory before running tests).
|
||||
|
||||
Tests must be run using the `symfony` command:
|
||||
The tests are run from the project's root (not from the bundle's root).
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
vendor/bin/phpunit
|
||||
|
||||
# Run tests for a specific bundle
|
||||
vendor/bin/phpunit --testsuite NameBundle
|
||||
|
||||
# Run a specific test file
|
||||
symfony composer exec phpunit -- path/to/TestFile.php
|
||||
vendor/bin/phpunit path/to/TestFile.php
|
||||
|
||||
# Run a specific test method
|
||||
symfony composer exec phpunit -- --filter methodName path/to/TestFile.php
|
||||
vendor/bin/phpunit --filter methodName path/to/TestFile.php
|
||||
```
|
||||
|
||||
#### Test Structure
|
||||
|
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
30
.vscode/launch.json
vendored
30
.vscode/launch.json
vendored
@@ -1,30 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Chill Debug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9000,
|
||||
"pathMappings": {
|
||||
"/var/www/html": "${workspaceFolder}"
|
||||
},
|
||||
"preLaunchTask": "symfony"
|
||||
},
|
||||
{
|
||||
"name": "Yarn Encore Dev (Watch)",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "yarn encore dev --watch",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Chill Debug + Yarn Encore Dev (Watch)",
|
||||
"configurations": ["Chill Debug", "Yarn Encore Dev (Watch)"]
|
||||
}
|
||||
]
|
||||
}
|
23
.vscode/tasks.json
vendored
23
.vscode/tasks.json
vendored
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "symfony",
|
||||
"args": [
|
||||
"server:start",
|
||||
"--allow-http",
|
||||
"--no-tls",
|
||||
"--port=8000",
|
||||
"--allow-all-ip",
|
||||
"-d"
|
||||
],
|
||||
"label": "symfony"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn",
|
||||
"args": ["encore", "dev", "--watch"],
|
||||
"label": "webpack"
|
||||
}
|
||||
]
|
||||
}
|
@@ -54,7 +54,7 @@ Arborescence:
|
||||
- person
|
||||
- personvendee
|
||||
- household_edit_metadata
|
||||
- index.ts
|
||||
- index.js
|
||||
```
|
||||
|
||||
## Organisation des feuilles de styles
|
||||
|
@@ -133,7 +133,6 @@
|
||||
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
|
||||
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
|
||||
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
|
||||
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
|
||||
"Chill\\Utils\\Rector\\": "utils/rector/src"
|
||||
}
|
||||
},
|
||||
|
@@ -35,7 +35,6 @@ return [
|
||||
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
|
||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||
Chill\TicketBundle\ChillTicketBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
];
|
||||
|
@@ -1,5 +1,5 @@
|
||||
chill_doc_store:
|
||||
use_driver: local_storage
|
||||
use_driver: openstack
|
||||
local_storage:
|
||||
storage_path: '%kernel.project_dir%/var/storage'
|
||||
openstack:
|
||||
|
@@ -1,5 +0,0 @@
|
||||
chill_ticket:
|
||||
ticket:
|
||||
person_per_ticket: one # One of "one"; "many"
|
||||
response_time_exceeded_delay: PT12H
|
||||
|
@@ -14,7 +14,6 @@ doctrine_migrations:
|
||||
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
|
||||
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
|
||||
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
|
||||
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
|
||||
all_or_nothing:
|
||||
true
|
||||
|
||||
|
@@ -1,2 +0,0 @@
|
||||
chill_ticket_bundle:
|
||||
resource: '@ChillTicketBundle/config/routes.yaml'
|
@@ -11,94 +11,24 @@
|
||||
Create a new bundle
|
||||
*******************
|
||||
|
||||
Create your own bundle is not a trivial task.
|
||||
|
||||
The easiest way to achieve this is seems to be :
|
||||
|
||||
1. Prepare a fresh installation of the chill project, in a new directory
|
||||
2. Create a new bundle in this project, in the src directory
|
||||
3. Initialize a git repository **at the root bundle**, and create your initial commit.
|
||||
4. Register the bundle with composer/packagist. If you do not plan to distribute your bundle with packagist, you may use a custom repository for achieve this [#f1]_
|
||||
5. Move to a development installation, made as described in the :ref:`installation-for-development` section, and add your new repository to the composer.json file
|
||||
6. Work as :ref:`usual <editing-code-and-commiting>`
|
||||
|
||||
.. warning::
|
||||
|
||||
This part of the doc is not yet tested
|
||||
|
||||
Create a new directory with Bundle class
|
||||
----------------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir -p src/Bundle/ChillSomeBundle/src/config
|
||||
mkdir -p src/Bundle/ChillSomeBundle/src/Controller
|
||||
|
||||
Add a bundle file
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\SomeBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillSomeBundle extends Bundle {}
|
||||
|
||||
And a route file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
chill_ticket_controller:
|
||||
resource: '@ChillTicketBundle/Controller/'
|
||||
type: annotation
|
||||
|
||||
Register the new psr-4 namespace
|
||||
--------------------------------
|
||||
|
||||
In composer.json, add the new psr4 namespace
|
||||
|
||||
.. code-block:: diff
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
+ "Chill\\SomeBundle\\": "src/Bundle/ChillSomeBundle/src",
|
||||
}
|
||||
}
|
||||
}
|
||||
TODO
|
||||
|
||||
|
||||
Register the bundle
|
||||
-------------------
|
||||
|
||||
Register in the file :code:`config/bundles.php`:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
Vendor\Bundle\YourBundle\YourBundle::class => ['all' => true],
|
||||
|
||||
And import routes in :code:`config/routes/chill_some_bundle.yaml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
chill_ticket_bundle:
|
||||
resource: '@ChillSomeBundle/config/routes.yaml'
|
||||
|
||||
Add the doctrine_migration namespace
|
||||
------------------------------------
|
||||
|
||||
Add the namespace to :code:`config/packages/doctrine_migrations_chill.yaml`
|
||||
|
||||
.. code-block:: diff
|
||||
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
+ 'Chill\Some\Ticket': '@ChillSomeBundle/migrations'
|
||||
|
||||
Dump autoloading
|
||||
----------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
symfony composer dump-autoload
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software.
|
||||
|
@@ -79,12 +79,12 @@
|
||||
"dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build": "encore production --progress",
|
||||
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
||||
"specs-create-dir": "mkdir -p templates/api",
|
||||
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||
"version": "node --version",
|
||||
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
@@ -58,10 +58,6 @@
|
||||
<!-- temporarily removed, the time to find a fix -->
|
||||
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="TicketBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
|
||||
</testsuite>
|
||||
<!--
|
||||
<testsuite name="ReportBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
||||
|
@@ -10,7 +10,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"
|
||||
v-if="
|
||||
getContext === 'accompanyingCourse' &&
|
||||
suggestedEntities.length > 0
|
||||
"
|
||||
>
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li
|
||||
|
@@ -39,11 +39,17 @@
|
||||
<option selected disabled value="">
|
||||
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||
</option>
|
||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
||||
<option
|
||||
v-for="t in locationTypes"
|
||||
:value="t"
|
||||
:key="t.id"
|
||||
>
|
||||
{{ localizeString(t.title) }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
|
||||
<label>{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
|
||||
}}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
@@ -102,7 +108,10 @@
|
||||
</form>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button class="btn btn-save" @click.prevent="saveNewLocation">
|
||||
<button
|
||||
class="btn btn-save"
|
||||
@click.prevent="saveNewLocation"
|
||||
>
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
@@ -235,7 +244,8 @@ export default {
|
||||
},
|
||||
hasPhonenumber1() {
|
||||
return (
|
||||
this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""
|
||||
this.selected.phonenumber1 !== null &&
|
||||
this.selected.phonenumber1 !== ""
|
||||
);
|
||||
},
|
||||
showAddAddress() {
|
||||
|
@@ -49,7 +49,9 @@
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div v-if="actionIsLoading === true">
|
||||
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
|
||||
<i
|
||||
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<span
|
||||
@@ -62,7 +64,8 @@
|
||||
<template
|
||||
v-else-if="
|
||||
socialActionsList.length > 0 &&
|
||||
(socialIssuesSelected.length || socialActionsSelected.length)
|
||||
(socialIssuesSelected.length ||
|
||||
socialActionsSelected.length)
|
||||
"
|
||||
>
|
||||
<div
|
||||
@@ -85,7 +88,9 @@
|
||||
</template>
|
||||
|
||||
<span
|
||||
v-else-if="actionAreLoaded && socialActionsList.length === 0"
|
||||
v-else-if="
|
||||
actionAreLoaded && socialActionsList.length === 0
|
||||
"
|
||||
class="inline-choice chill-no-data-statement mt-3"
|
||||
>
|
||||
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
||||
@@ -164,7 +169,8 @@ export default {
|
||||
/* Add in list the issues already associated (if not yet listed) */
|
||||
this.socialIssuesSelected.forEach((issue) => {
|
||||
if (
|
||||
this.socialIssuesList.filter((i) => i.id === issue.id).length !== 1
|
||||
this.socialIssuesList.filter((i) => i.id === issue.id)
|
||||
.length !== 1
|
||||
) {
|
||||
this.$store.commit("addIssueInList", issue);
|
||||
}
|
||||
|
@@ -10,7 +10,9 @@
|
||||
:value="issue"
|
||||
/>
|
||||
<label class="form-check-label" :for="issue.id">
|
||||
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
|
||||
<span class="badge bg-chill-l-gray text-dark">{{
|
||||
issue.text
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -266,7 +266,7 @@ class CalendarController extends AbstractController
|
||||
}
|
||||
|
||||
if (!$this->getUser() instanceof User) {
|
||||
throw new UnauthorizedHttpException('you are not an user');
|
||||
throw new UnauthorizedHttpException('you are not a user');
|
||||
}
|
||||
|
||||
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
|
||||
|
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\CalendarBundle\Controller;
|
||||
|
||||
use Chill\CalendarBundle\Repository\InviteACLAwareRepository;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class MyInvitationsController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly InviteACLAwareRepository $inviteACLAwareRepository, private readonly PaginatorFactory $paginator) {}
|
||||
|
||||
/**
|
||||
* @throws NonUniqueResultException
|
||||
* @throws NoResultException
|
||||
*/
|
||||
#[Route(path: '/{_locale}/calendar/invitations/my', name: 'chill_calendar_invitations_list_my')]
|
||||
public function myInvitations(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new UnauthorizedHttpException('you are not a user');
|
||||
}
|
||||
|
||||
$total = $this->inviteACLAwareRepository->countByUser($user);
|
||||
$paginator = $this->paginator->create($total);
|
||||
$invitations = $this->inviteACLAwareRepository->findByUser(
|
||||
$user,
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage()
|
||||
);
|
||||
|
||||
dump($invitations);
|
||||
|
||||
$view = '@ChillCalendar/Invitations/listByUser.html.twig';
|
||||
|
||||
return $this->render($view, [
|
||||
'invitations' => $invitations,
|
||||
'paginator' => $paginator,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -30,6 +30,13 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
'order' => 9,
|
||||
'icon' => 'tasks',
|
||||
]);
|
||||
$menu->addChild('My invitations list', [
|
||||
'route' => 'chill_calendar_invitations_list_my',
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 9,
|
||||
'icon' => 'tasks',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\CalendarBundle\Repository;
|
||||
|
||||
use Chill\CalendarBundle\Entity\Invite;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
readonly class InviteACLAwareRepository implements InviteACLAwareRepositoryInterface
|
||||
{
|
||||
public function __construct(private EntityManagerInterface $em) {}
|
||||
|
||||
/**
|
||||
* @throws NonUniqueResultException
|
||||
* @throws NoResultException
|
||||
*/
|
||||
public function countByUser(User $user): int
|
||||
{
|
||||
return $this->buildQueryByUser($user)
|
||||
->select('COUNT(i)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findByUser(User $user, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array
|
||||
{
|
||||
$qb = $this->buildQueryByUser($user)
|
||||
->select('i');
|
||||
|
||||
foreach ($orderBy as $sort => $order) {
|
||||
$qb->addOrderBy('i.'.$sort, $order);
|
||||
}
|
||||
|
||||
if (null !== $offset) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
|
||||
if (null !== $limit) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function buildQueryByUser(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->em->createQueryBuilder()
|
||||
->from(Invite::class, 'i');
|
||||
|
||||
$qb->where('i.user = :user');
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\CalendarBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
|
||||
interface InviteACLAwareRepositoryInterface
|
||||
{
|
||||
public function countByUser(User $user): int;
|
||||
|
||||
public function findByUser(User $user, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array;
|
||||
}
|
@@ -68,7 +68,9 @@ export type EventInputCalendarRange = EventInput & {
|
||||
export function isEventInputCalendarRange(
|
||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||
): toBeDetermined is EventInputCalendarRange {
|
||||
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
|
||||
return (
|
||||
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
|
||||
);
|
||||
}
|
||||
|
||||
export {};
|
||||
|
@@ -61,14 +61,22 @@
|
||||
<label class="input-group-text" for="slotDuration"
|
||||
>Durée des créneaux</label
|
||||
>
|
||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
||||
<select
|
||||
v-model="slotDuration"
|
||||
id="slotDuration"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:05:00">5 minutes</option>
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
||||
<select
|
||||
v-model="slotMinTime"
|
||||
id="slotMinTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:00:00">0h</option>
|
||||
<option value="01:00:00">1h</option>
|
||||
<option value="02:00:00">2h</option>
|
||||
@@ -84,7 +92,11 @@
|
||||
<option value="12:00:00">12h</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
||||
<select
|
||||
v-model="slotMaxTime"
|
||||
id="slotMaxTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="12:00:00">12h</option>
|
||||
<option value="13:00:00">13h</option>
|
||||
<option value="14:00:00">14h</option>
|
||||
@@ -112,7 +124,9 @@
|
||||
v-model="hideWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label for="showHideWE" class="form-check-label input-group-text"
|
||||
<label
|
||||
for="showHideWE"
|
||||
class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
@@ -128,7 +142,9 @@
|
||||
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
||||
>{{ arg.timeText }}
|
||||
{{ arg.event.extendedProps.locationName }}
|
||||
<small>{{ arg.event.extendedProps.userLabel }}</small></b
|
||||
<small>{{
|
||||
arg.event.extendedProps.userLabel
|
||||
}}</small></b
|
||||
>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'current'"
|
||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||
@@ -136,7 +152,9 @@
|
||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<b v-else>{{ arg.timeText }} {{ $t("current_selected") }} </b>
|
||||
<b v-else
|
||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||
</b>
|
||||
</span>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
@@ -250,7 +268,9 @@ export default {
|
||||
this.$store.state.activity.endDate !== null)
|
||||
) {
|
||||
if (
|
||||
!window.confirm(this.$t("change_main_user_will_reset_event_data"))
|
||||
!window.confirm(
|
||||
this.$t("change_main_user_will_reset_event_data"),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -258,9 +278,13 @@ export default {
|
||||
|
||||
// add the previous user, if any, in the previous user list (in use for suggestion)
|
||||
if (null !== this.$store.getters.getMainUser) {
|
||||
const suggestedUids = new Set(this.$data.previousUser.map((u) => u.id));
|
||||
const suggestedUids = new Set(
|
||||
this.$data.previousUser.map((u) => u.id),
|
||||
);
|
||||
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
||||
this.$data.previousUser.push(this.$store.getters.getMainUser);
|
||||
this.$data.previousUser.push(
|
||||
this.$store.getters.getMainUser,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +314,8 @@ export default {
|
||||
// show an alert if changing mainUser
|
||||
if (
|
||||
(this.$store.getters.getMainUser !== null &&
|
||||
this.$store.state.me.id !== this.$store.getters.getMainUser.id) ||
|
||||
this.$store.state.me.id !==
|
||||
this.$store.getters.getMainUser.id) ||
|
||||
this.$store.getters.getMainUser === null
|
||||
) {
|
||||
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
||||
@@ -334,7 +359,9 @@ export default {
|
||||
this.$store.getters.getMainUser.id
|
||||
) {
|
||||
if (
|
||||
!window.confirm(this.$t("this_calendar_range_will_change_main_user"))
|
||||
!window.confirm(
|
||||
this.$t("this_calendar_range_will_change_main_user"),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@@ -4,9 +4,18 @@
|
||||
{{ user.text }}
|
||||
<template v-if="invite !== null">
|
||||
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||
<i v-else-if="invite.status === 'declined'" class="fa fa-times" />
|
||||
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
|
||||
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
|
||||
<i
|
||||
v-else-if="invite.status === 'declined'"
|
||||
class="fa fa-times"
|
||||
/>
|
||||
<i
|
||||
v-else-if="invite.status === 'pending'"
|
||||
class="fa fa-question-o"
|
||||
/>
|
||||
<i
|
||||
v-else-if="invite.status === 'tentative'"
|
||||
class="fa fa-question"
|
||||
/>
|
||||
<span v-else="">{{ invite.status }}</span>
|
||||
</template>
|
||||
</span>
|
||||
@@ -60,7 +69,8 @@ export default {
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
|
||||
backgroundColor: this.$store.getters.getUserData(this.user)
|
||||
.mainColor,
|
||||
};
|
||||
},
|
||||
rangeShow: {
|
||||
@@ -71,7 +81,9 @@ export default {
|
||||
});
|
||||
},
|
||||
get() {
|
||||
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
|
||||
return this.$store.getters.isRangeShownOnCalendarForUser(
|
||||
this.user,
|
||||
);
|
||||
},
|
||||
},
|
||||
remoteShow: {
|
||||
@@ -82,7 +94,9 @@ export default {
|
||||
});
|
||||
},
|
||||
get() {
|
||||
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
|
||||
return this.$store.getters.isRemoteShownOnCalendarForUser(
|
||||
this.user,
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -22,25 +22,33 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
<li v-if="status !== Statuses.ACCEPTED">
|
||||
<a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)"
|
||||
><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="changeStatus(Statuses.ACCEPTED)"
|
||||
><i class="fa fa-check" aria-hidden="true"></i>
|
||||
{{ $t("Accept") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.DECLINED">
|
||||
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)"
|
||||
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="changeStatus(Statuses.DECLINED)"
|
||||
><i class="fa fa-times" aria-hidden="true"></i>
|
||||
{{ $t("Decline") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
|
||||
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a
|
||||
><i class="fa fa-question"></i>
|
||||
{{ $t("Tentatively_accept") }}</a
|
||||
>
|
||||
</li>
|
||||
<li v-if="status !== Statuses.PENDING">
|
||||
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
|
||||
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a
|
||||
><i class="fa fa-hourglass-o"></i>
|
||||
{{ $t("Set_pending") }}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -83,7 +91,9 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
|
||||
statusChanged(
|
||||
payload: "accepted" | "declined" | "pending" | "tentative",
|
||||
) {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
@@ -23,14 +23,22 @@
|
||||
<label class="input-group-text" for="slotDuration"
|
||||
>Durée des créneaux</label
|
||||
>
|
||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
||||
<select
|
||||
v-model="slotDuration"
|
||||
id="slotDuration"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:05:00">5 minutes</option>
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
||||
<select
|
||||
v-model="slotMinTime"
|
||||
id="slotMinTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:00:00">0h</option>
|
||||
<option value="01:00:00">1h</option>
|
||||
<option value="02:00:00">2h</option>
|
||||
@@ -46,7 +54,11 @@
|
||||
<option value="12:00:00">12h</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
||||
<select
|
||||
v-model="slotMaxTime"
|
||||
id="slotMaxTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="12:00:00">12h</option>
|
||||
<option value="13:00:00">13h</option>
|
||||
<option value="14:00:00">14h</option>
|
||||
@@ -74,7 +86,9 @@
|
||||
v-model="showWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label for="showHideWE" class="form-check-label input-group-text"
|
||||
<label
|
||||
for="showHideWE"
|
||||
class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
@@ -84,12 +98,16 @@
|
||||
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
||||
<template v-slot:eventContent="{ event }: { event: EventApi }">
|
||||
<span :class="eventClasses">
|
||||
<b v-if="event.extendedProps.is === 'remote'">{{ event.title }}</b>
|
||||
<b v-if="event.extendedProps.is === 'remote'">{{
|
||||
event.title
|
||||
}}</b>
|
||||
<b v-else-if="event.extendedProps.is === 'range'"
|
||||
>{{ formatDate(event.startStr) }} -
|
||||
{{ event.extendedProps.locationName }}</b
|
||||
>
|
||||
<b v-else-if="event.extendedProps.is === 'local'">{{ event.title }}</b>
|
||||
<b v-else-if="event.extendedProps.is === 'local'">{{
|
||||
event.title
|
||||
}}</b>
|
||||
<b v-else>no 'is'</b>
|
||||
<a
|
||||
v-if="event.extendedProps.is === 'range'"
|
||||
@@ -108,7 +126,11 @@
|
||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
|
||||
<select
|
||||
v-model="dayOrWeek"
|
||||
id="dayOrWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||
<option value="week">
|
||||
{{ $t("from_week_to_week") }}
|
||||
@@ -117,16 +139,27 @@
|
||||
</div>
|
||||
<template v-if="dayOrWeek === 'day'">
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input class="form-control" type="date" v-model="copyFrom" />
|
||||
<input
|
||||
class="form-control"
|
||||
type="date"
|
||||
v-model="copyFrom"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input class="form-control" type="date" v-model="copyTo" />
|
||||
<input
|
||||
class="form-control"
|
||||
type="date"
|
||||
v-model="copyTo"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button class="btn btn-action float-end" @click="copyDay">
|
||||
<button
|
||||
class="btn btn-action float-end"
|
||||
@click="copyDay"
|
||||
>
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -138,7 +171,11 @@
|
||||
id="copyFromWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
|
||||
<option
|
||||
v-for="w in lastWeeks"
|
||||
:value="w.value"
|
||||
:key="w.value"
|
||||
>
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -147,14 +184,25 @@
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
|
||||
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
|
||||
<select
|
||||
v-model="copyToWeek"
|
||||
id="copyToWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="w in nextWeeks"
|
||||
:value="w.value"
|
||||
:key="w.value"
|
||||
>
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button class="btn btn-action float-end" @click="copyWeek">
|
||||
<button
|
||||
class="btn btn-action float-end"
|
||||
@click="copyWeek"
|
||||
>
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
|
@@ -41,7 +41,9 @@ const futureStore = function (): Promise<Store<State>> {
|
||||
});
|
||||
|
||||
store.commit("me/setWhoAmi", user, { root: true });
|
||||
store.dispatch("locations/getLocations", null, { root: true }).then((_) => {
|
||||
store
|
||||
.dispatch("locations/getLocations", null, { root: true })
|
||||
.then((_) => {
|
||||
return store.dispatch("locations/getCurrentLocation", null, {
|
||||
root: true,
|
||||
});
|
||||
|
@@ -29,7 +29,10 @@ export default {
|
||||
(state: CalendarLocalsState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.localsLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +54,10 @@ export default {
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
|
||||
addLoaded(
|
||||
state: CalendarLocalsState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.localsLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
@@ -79,7 +85,11 @@ export default {
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||
return fetchCalendarLocalForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
)
|
||||
.then((remotes: CalendarLight[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
|
@@ -40,7 +40,10 @@ export default {
|
||||
(state: CalendarRangesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.rangesLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -107,7 +110,9 @@ export default {
|
||||
state: CalendarRangesState,
|
||||
externalEvents: (EventInput & { id: string })[],
|
||||
) {
|
||||
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
|
||||
const toAdd = externalEvents.filter(
|
||||
(r) => !state.rangesIndex.has(r.id),
|
||||
);
|
||||
|
||||
toAdd.forEach((r) => {
|
||||
state.rangesIndex.add(r.id);
|
||||
@@ -115,7 +120,10 @@ export default {
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
|
||||
addLoaded(
|
||||
state: CalendarRangesState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.rangesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
@@ -134,12 +142,17 @@ export default {
|
||||
},
|
||||
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
||||
const found = state.ranges.find(
|
||||
(r) => r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||
(r) =>
|
||||
r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||
);
|
||||
|
||||
if (found !== undefined) {
|
||||
state.ranges = state.ranges.filter(
|
||||
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
|
||||
(r) =>
|
||||
!(
|
||||
r.calendarRangeId === calendarRangeId &&
|
||||
r.is === "range"
|
||||
),
|
||||
);
|
||||
|
||||
if (typeof found.id === "string") {
|
||||
@@ -198,7 +211,11 @@ export default {
|
||||
},
|
||||
createRange(
|
||||
ctx: Context,
|
||||
{ start, end, location }: { start: Date; end: Date; location: Location },
|
||||
{
|
||||
start,
|
||||
end,
|
||||
location,
|
||||
}: { start: Date; end: Date; location: Location },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||
|
||||
@@ -223,7 +240,11 @@ export default {
|
||||
},
|
||||
} as CalendarRangeCreate;
|
||||
|
||||
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body)
|
||||
return makeFetch<CalendarRangeCreate, CalendarRange>(
|
||||
"POST",
|
||||
url,
|
||||
body,
|
||||
)
|
||||
.then((newRange) => {
|
||||
ctx.commit("addRange", newRange);
|
||||
|
||||
@@ -260,7 +281,11 @@ export default {
|
||||
},
|
||||
} as CalendarRangeEdit;
|
||||
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||
"PATCH",
|
||||
url,
|
||||
body,
|
||||
)
|
||||
.then((range) => {
|
||||
ctx.commit("updateRange", range);
|
||||
return Promise.resolve(null);
|
||||
@@ -285,7 +310,11 @@ export default {
|
||||
},
|
||||
} as CalendarRangeEdit;
|
||||
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
||||
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||
"PATCH",
|
||||
url,
|
||||
body,
|
||||
)
|
||||
.then((range) => {
|
||||
ctx.commit("updateRange", range);
|
||||
return Promise.resolve(null);
|
||||
@@ -305,14 +334,20 @@ export default {
|
||||
|
||||
for (const r of rangesToCopy) {
|
||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||
start.setFullYear(
|
||||
to.getFullYear(),
|
||||
to.getMonth(),
|
||||
to.getDate(),
|
||||
);
|
||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||
const location = ctx.rootGetters["locations/getLocationById"](
|
||||
r.locationId,
|
||||
);
|
||||
|
||||
promises.push(ctx.dispatch("createRange", { start, end, location }));
|
||||
promises.push(
|
||||
ctx.dispatch("createRange", { start, end, location }),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||
@@ -334,7 +369,9 @@ export default {
|
||||
r.locationId,
|
||||
);
|
||||
|
||||
promises.push(ctx.dispatch("createRange", { start, end, location }));
|
||||
promises.push(
|
||||
ctx.dispatch("createRange", { start, end, location }),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||
|
@@ -29,7 +29,10 @@ export default {
|
||||
(state: CalendarRemotesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.remotesLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -82,7 +85,11 @@ export default {
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||
return fetchCalendarRemoteForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
)
|
||||
.then((remotes: CalendarRemote[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
|
@@ -112,8 +112,11 @@ export default {
|
||||
|
||||
results.forEach((i) => {
|
||||
if (!users.some((j) => i.user.id === j.id)) {
|
||||
let ratio = Math.floor(users.length / COLORS.length);
|
||||
let colorIndex = users.length - ratio * COLORS.length;
|
||||
let ratio = Math.floor(
|
||||
users.length / COLORS.length,
|
||||
);
|
||||
let colorIndex =
|
||||
users.length - ratio * COLORS.length;
|
||||
users.push({
|
||||
id: i.user.id,
|
||||
username: i.user.username,
|
||||
@@ -150,29 +153,45 @@ export default {
|
||||
(me) =>
|
||||
new Promise((resolve, reject) => {
|
||||
this.users.logged = me;
|
||||
let currentUser = users.find((u) => u.id === me.id);
|
||||
let currentUser = users.find(
|
||||
(u) => u.id === me.id,
|
||||
);
|
||||
this.value = currentUser;
|
||||
|
||||
fetchCalendar(currentUser.id).then(
|
||||
(calendar) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let results = calendar.results;
|
||||
let events = results.map((i) => ({
|
||||
start: i.startDate.datetime,
|
||||
end: i.endDate.datetime,
|
||||
}));
|
||||
let calendarEventsCurrentUser = {
|
||||
new Promise(
|
||||
(resolve, reject) => {
|
||||
let results =
|
||||
calendar.results;
|
||||
let events =
|
||||
results.map(
|
||||
(i) => ({
|
||||
start: i
|
||||
.startDate
|
||||
.datetime,
|
||||
end: i
|
||||
.endDate
|
||||
.datetime,
|
||||
}),
|
||||
);
|
||||
let calendarEventsCurrentUser =
|
||||
{
|
||||
events: events,
|
||||
color: "darkblue",
|
||||
id: 1000,
|
||||
editable: false,
|
||||
};
|
||||
this.calendarEvents.user = calendarEventsCurrentUser;
|
||||
this.calendarEvents.user =
|
||||
calendarEventsCurrentUser;
|
||||
|
||||
this.selectUsers(currentUser);
|
||||
this.selectUsers(
|
||||
currentUser,
|
||||
);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
resolve();
|
||||
@@ -190,7 +209,9 @@ export default {
|
||||
return `${value.username}`;
|
||||
},
|
||||
coloriseSelectedValues() {
|
||||
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
|
||||
let tags = document.querySelectorAll(
|
||||
"div.multiselect__tags-wrap",
|
||||
)[0];
|
||||
|
||||
if (tags.hasChildNodes()) {
|
||||
let children = tags.childNodes;
|
||||
@@ -211,8 +232,8 @@ export default {
|
||||
},
|
||||
selectEvents() {
|
||||
let selectedUsersId = this.users.selected.map((a) => a.id);
|
||||
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
|
||||
selectedUsersId.includes(a.id),
|
||||
this.calendarEvents.selected = this.calendarEvents.loaded.filter(
|
||||
(a) => selectedUsersId.includes(a.id),
|
||||
);
|
||||
},
|
||||
selectUsers(value) {
|
||||
@@ -222,7 +243,9 @@ export default {
|
||||
this.updateEventsSource();
|
||||
},
|
||||
unSelectUsers(value) {
|
||||
this.users.selected = this.users.selected.filter((a) => a.id != value.id);
|
||||
this.users.selected = this.users.selected.filter(
|
||||
(a) => a.id != value.id,
|
||||
);
|
||||
this.selectEvents();
|
||||
this.updateEventsSource();
|
||||
},
|
||||
|
@@ -0,0 +1,172 @@
|
||||
{% if invitations|length > 0 %}
|
||||
<div class="flex-table list-records">
|
||||
|
||||
{% for invitation in invitations %}
|
||||
{% set calendar = invitation.getCalendar %}
|
||||
|
||||
{% if calendar is not null %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row main">
|
||||
<div class="item-col">
|
||||
<div class="wrap-header">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<p class="date-label">
|
||||
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||
- {{ calendar.endDate|format_datetime('short', 'short') }}
|
||||
{% else %}
|
||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||
- {{ calendar.endDate|format_datetime('none', 'short') }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<div class="duration short-message">
|
||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||
{{ calendar.duration|date('%H:%I') }}
|
||||
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
||||
<!-- no sms will be send -->
|
||||
{% else %}
|
||||
{% if calendar.smsStatus == 'sms_sent' %}
|
||||
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
|
||||
<i class="fa fa-check "></i>
|
||||
<i class="fa fa-envelope "></i>
|
||||
</span>
|
||||
{% else %}
|
||||
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
|
||||
<i class="fa fa-envelope "></i>
|
||||
<i class="fa fa-hourglass-end "></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
{% if calendar.mainUser is not empty %}
|
||||
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if calendar.comment.comment is not empty
|
||||
or calendar.users|length > 0
|
||||
or calendar.thirdParties|length > 0
|
||||
or calendar.users|length > 0 %}
|
||||
<div class="item-row details separator">
|
||||
<div class="item-col">
|
||||
{% include '@ChillActivity/Activity/concernedGroups.html.twig' with {
|
||||
'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse',
|
||||
'render': 'wrap-list',
|
||||
'entity': calendar
|
||||
} %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.comment.comment is not empty %}
|
||||
<div class="item-row details separator">
|
||||
<div class="item-col comment">
|
||||
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.location is not empty %}
|
||||
<div class="item-row separator">
|
||||
<div>
|
||||
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
|
||||
<i class="fa fa-map-marker"></i>{% endif %}
|
||||
{% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
|
||||
{% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
|
||||
<i class="fa fa-map-marker"></i>{% endif %}
|
||||
{% if calendar.location.phonenumber1 is not empty %}<i
|
||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
|
||||
{% if calendar.location.phonenumber2 is not empty %}<i
|
||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row separator column">
|
||||
<div>
|
||||
|
||||
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if calendar.activity is not null %}
|
||||
<div class="item-row separator">
|
||||
<div class="item-col">
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
|
||||
<div class="wl-col list activity-linked">
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ calendar.activity.type.name | localize_translatable_string }}
|
||||
|
||||
{% if calendar.activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<span class="createdBy">
|
||||
{{ 'Created by'|trans }}
|
||||
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
||||
</span>
|
||||
</li>
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row separator">
|
||||
<ul class="record_actions">
|
||||
|
||||
{% if (calendar.isInvited(app.user)) %}
|
||||
{% set invite = calendar.inviteForUser(app.user) %}
|
||||
<li>
|
||||
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
|
||||
data-calendar-id="{{ calendar.id|e('html_attr') }}"></div>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_show', { 'id': calendar.id}) }}"
|
||||
class="btn btn-show "></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if invitations|length < paginator.getTotalItems %}
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
@@ -0,0 +1,27 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_calendar_invitations_list' %}
|
||||
|
||||
{% block title %}{{ 'My invitations list' |trans }}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ 'Invitation list' |trans }}</h1>
|
||||
|
||||
{% if invitations|length == 0 %}
|
||||
<p class="chill-no-data-statement">
|
||||
{{ "There is no invitation items."|trans }}
|
||||
</p>
|
||||
{% else %}
|
||||
{{ include ('@ChillCalendar/Invitations/_list_item.html.twig') }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
@@ -20,7 +20,10 @@
|
||||
</option>
|
||||
<template v-for="t in templates" :key="t.id">
|
||||
<option :value="t.id">
|
||||
{{ localizeString(t.name) || "Aucun nom défini" }}
|
||||
{{
|
||||
localizeString(t.name) ||
|
||||
"Aucun nom défini"
|
||||
}}
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
@@ -28,7 +31,9 @@
|
||||
v-if="canGenerate"
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
:href="buildUrlGenerate"
|
||||
@click.prevent="clickGenerate($event, buildUrlGenerate)"
|
||||
@click.prevent="
|
||||
clickGenerate($event, buildUrlGenerate)
|
||||
"
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
<a
|
||||
|
@@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface
|
||||
/**
|
||||
* @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true, fetch: 'EAGER')]
|
||||
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)]
|
||||
private Collection&Selectable $versions;
|
||||
|
||||
/**
|
||||
|
@@ -13,9 +13,8 @@ const startApp = (
|
||||
|
||||
const inputTitle = collectionEntry?.querySelector("input[type='text']");
|
||||
|
||||
const input_stored_object: HTMLInputElement | null = divElement.querySelector(
|
||||
"input[data-stored-object]",
|
||||
);
|
||||
const input_stored_object: HTMLInputElement | null =
|
||||
divElement.querySelector("input[data-stored-object]");
|
||||
if (null === input_stored_object) {
|
||||
throw new Error("input to stored object not found");
|
||||
}
|
||||
@@ -54,7 +53,9 @@ const startApp = (
|
||||
console.log("version added", stored_object_version);
|
||||
this.$data.existingDoc = stored_object;
|
||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
|
||||
input_stored_object.value = JSON.stringify(
|
||||
this.$data.existingDoc,
|
||||
);
|
||||
if (this.$data.inputTitle) {
|
||||
if (!this.$data.inputTitle?.value) {
|
||||
this.$data.inputTitle.value = file_name;
|
||||
|
@@ -49,7 +49,9 @@
|
||||
<li v-if="isHistoryViewable">
|
||||
<history-button
|
||||
:stored-object="props.storedObject"
|
||||
:can-edit="canEdit && props.storedObject._permissions.canEdit"
|
||||
:can-edit="
|
||||
canEdit && props.storedObject._permissions.canEdit
|
||||
"
|
||||
></history-button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -127,7 +129,9 @@ const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
||||
canDownload: true,
|
||||
canConvertPdf: true,
|
||||
returnPath:
|
||||
window.location.pathname + window.location.search + window.location.hash,
|
||||
window.location.pathname +
|
||||
window.location.search +
|
||||
window.location.hash,
|
||||
});
|
||||
|
||||
/**
|
||||
|
@@ -29,7 +29,9 @@
|
||||
</modal>
|
||||
</teleport>
|
||||
<div class="col-12 m-auto sticky-top">
|
||||
<div class="row justify-content-center border-bottom pdf-tools d-md-none">
|
||||
<div
|
||||
class="row justify-content-center border-bottom pdf-tools d-md-none"
|
||||
>
|
||||
<div class="col-5 text-center turn-page">
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
@@ -90,7 +92,10 @@
|
||||
v-if="signature.zones.length === 1 && signedState !== 'signed'"
|
||||
class="col-5 p-0 text-center turnSignature"
|
||||
>
|
||||
<button class="btn btn-light btn-sm" @click="goToSignatureZoneUnique">
|
||||
<button
|
||||
class="btn btn-light btn-sm"
|
||||
@click="goToSignatureZoneUnique"
|
||||
>
|
||||
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -145,7 +150,10 @@
|
||||
:title="trans(SIGNATURES_ADD_SIGN_ZONE)"
|
||||
>
|
||||
<template v-if="canvasEvent === 'add'">
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<div
|
||||
class="spinner-border spinner-border-sm"
|
||||
role="status"
|
||||
>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -199,7 +207,10 @@
|
||||
v-if="signature.zones.length === 1 && signedState !== 'signed'"
|
||||
class="col-4 d-xl-none text-center turnSignature p-0"
|
||||
>
|
||||
<button class="btn btn-light btn-sm" @click="goToSignatureZoneUnique">
|
||||
<button
|
||||
class="btn btn-light btn-sm"
|
||||
@click="goToSignatureZoneUnique"
|
||||
>
|
||||
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -227,7 +238,10 @@
|
||||
v-if="signature.zones.length === 1 && signedState !== 'signed'"
|
||||
class="col-4 d-none d-xl-flex p-0 text-center turnSignature"
|
||||
>
|
||||
<button class="btn btn-light btn-sm" @click="goToSignatureZoneUnique">
|
||||
<button
|
||||
class="btn btn-light btn-sm"
|
||||
@click="goToSignatureZoneUnique"
|
||||
>
|
||||
{{ trans(SIGNATURES_GO_TO_SIGNATURE_UNIQUE) }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -285,7 +299,10 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ trans(SIGNATURES_CLICK_ON_DOCUMENT) }}
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<div
|
||||
class="spinner-border spinner-border-sm"
|
||||
role="status"
|
||||
>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -545,8 +562,14 @@ const addCanvasEvents = () => {
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const canvas = document.querySelectorAll("canvas")[0] as HTMLCanvasElement;
|
||||
canvas.addEventListener("pointerup", (e) => canvasClick(e, canvas), false);
|
||||
const canvas = document.querySelectorAll(
|
||||
"canvas",
|
||||
)[0] as HTMLCanvasElement;
|
||||
canvas.addEventListener(
|
||||
"pointerup",
|
||||
(e) => canvasClick(e, canvas),
|
||||
false,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -582,7 +605,11 @@ const hitSignature = (
|
||||
scaleYToCanvas(zone.y, canvas.height, zone.PDFPage.height) <
|
||||
xy[1] &&
|
||||
xy[1] <
|
||||
scaleYToCanvas(zone.height - zone.y, canvas.height, zone.PDFPage.height) +
|
||||
scaleYToCanvas(
|
||||
zone.height - zone.y,
|
||||
canvas.height,
|
||||
zone.PDFPage.height,
|
||||
) +
|
||||
zone.PDFPage.height * zoom.value;
|
||||
|
||||
const selectZone = async (z: SignatureZone, canvas: HTMLCanvasElement) => {
|
||||
@@ -598,7 +625,8 @@ const selectZoneEvent = (e: PointerEvent, canvas: HTMLCanvasElement) =>
|
||||
signature.zones
|
||||
.filter(
|
||||
(z) =>
|
||||
(z.PDFPage.index + 1 === getCanvasId(canvas) && multiPage.value) ||
|
||||
(z.PDFPage.index + 1 === getCanvasId(canvas) &&
|
||||
multiPage.value) ||
|
||||
(z.PDFPage.index + 1 === page.value && !multiPage.value),
|
||||
)
|
||||
.map((z) => {
|
||||
|
@@ -153,10 +153,12 @@ const handleFile = async (file: File): Promise<void> => {
|
||||
</p>
|
||||
<!-- todo i18n -->
|
||||
<p v-if="has_existing_doc">
|
||||
Déposez un document ou cliquez ici pour remplacer le document existant
|
||||
Déposez un document ou cliquez ici pour remplacer le document
|
||||
existant
|
||||
</p>
|
||||
<p v-else>
|
||||
Déposez un document ou cliquez ici pour ouvrir le navigateur de fichier
|
||||
Déposez un document ou cliquez ici pour ouvrir le navigateur de
|
||||
fichier
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="waiting">
|
||||
|
@@ -35,7 +35,9 @@ async function download_and_open(event: Event): Promise<void> {
|
||||
if (null === state.content) {
|
||||
event.preventDefault();
|
||||
|
||||
const raw = await download_doc(build_convert_link(props.storedObject.uuid));
|
||||
const raw = await download_doc(
|
||||
build_convert_link(props.storedObject.uuid),
|
||||
);
|
||||
state.content = window.URL.createObjectURL(raw);
|
||||
|
||||
button.href = window.URL.createObjectURL(raw);
|
||||
|
@@ -42,7 +42,9 @@ const editionUntilFormatted = computed<string>(() => {
|
||||
<modal v-if="state.modalOpened" @close="state.modalOpened = false">
|
||||
<template v-slot:body>
|
||||
<div class="desktop-edit">
|
||||
<p class="center">Veuillez enregistrer vos modifications avant le</p>
|
||||
<p class="center">
|
||||
Veuillez enregistrer vos modifications avant le
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ editionUntilFormatted }}</strong>
|
||||
</p>
|
||||
@@ -55,21 +57,23 @@ const editionUntilFormatted = computed<string>(() => {
|
||||
|
||||
<p>
|
||||
<small
|
||||
>Le document peut être édité uniquement en utilisant Libre
|
||||
Office.</small
|
||||
>Le document peut être édité uniquement en utilisant
|
||||
Libre Office.</small
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<small
|
||||
>En cas d'échec lors de l'enregistrement, sauver le document sur
|
||||
le poste de travail avant de le déposer à nouveau ici.</small
|
||||
>En cas d'échec lors de l'enregistrement, sauver le
|
||||
document sur le poste de travail avant de le déposer
|
||||
à nouveau ici.</small
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<small
|
||||
>Vous pouvez naviguez sur d'autres pages pendant l'édition.</small
|
||||
>Vous pouvez naviguez sur d'autres pages pendant
|
||||
l'édition.</small
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -95,7 +95,10 @@ async function download_and_open(): Promise<void> {
|
||||
let raw;
|
||||
|
||||
try {
|
||||
raw = await download_and_decrypt_doc(props.storedObject, props.atVersion);
|
||||
raw = await download_and_decrypt_doc(
|
||||
props.storedObject,
|
||||
props.atVersion,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("error while downloading and decrypting document");
|
||||
console.error(e);
|
||||
|
@@ -49,7 +49,8 @@ const isRestored = computed<boolean>(
|
||||
);
|
||||
|
||||
const isDuplicated = computed<boolean>(
|
||||
() => props.version.version === 0 && null !== props.version["from-restored"],
|
||||
() =>
|
||||
props.version.version === 0 && null !== props.version["from-restored"],
|
||||
);
|
||||
|
||||
const classes = computed<{
|
||||
@@ -69,9 +70,16 @@ const classes = computed<{
|
||||
<div :class="classes">
|
||||
<div
|
||||
class="col-12 tags"
|
||||
v-if="isCurrent || isKeptBeforeConversion || isRestored || isDuplicated"
|
||||
v-if="
|
||||
isCurrent ||
|
||||
isKeptBeforeConversion ||
|
||||
isRestored ||
|
||||
isDuplicated
|
||||
"
|
||||
>
|
||||
<span class="badge bg-success" v-if="isCurrent"
|
||||
>Version actuelle</span
|
||||
>
|
||||
<span class="badge bg-success" v-if="isCurrent">Version actuelle</span>
|
||||
<span class="badge bg-info" v-if="isKeptBeforeConversion"
|
||||
>Conservée avant conversion dans un autre format</span
|
||||
>
|
||||
@@ -88,17 +96,21 @@ const classes = computed<{
|
||||
<span
|
||||
><strong> #{{ version.version + 1 }} </strong></span
|
||||
>
|
||||
<template v-if="version.createdBy !== null && version.createdAt !== null"
|
||||
<template
|
||||
v-if="version.createdBy !== null && version.createdAt !== null"
|
||||
><strong v-if="version.version == 0">créé par</strong
|
||||
><strong v-else>modifié par</strong>
|
||||
<span class="badge-user"
|
||||
><UserRenderBoxBadge :user="version.createdBy"></UserRenderBoxBadge
|
||||
><UserRenderBoxBadge
|
||||
:user="version.createdBy"
|
||||
></UserRenderBoxBadge
|
||||
></span>
|
||||
<strong>à</strong>
|
||||
{{
|
||||
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
||||
}}</template
|
||||
><template v-if="version.createdBy === null && version.createdAt !== null"
|
||||
><template
|
||||
v-if="version.createdBy === null && version.createdAt !== null"
|
||||
><strong v-if="version.version == 0">Créé le</strong
|
||||
><strong v-else>modifié le</strong>
|
||||
{{
|
||||
|
@@ -2,7 +2,9 @@
|
||||
<a
|
||||
:class="Object.assign(props.classes, { btn: true })"
|
||||
@click="beforeLeave($event)"
|
||||
:href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)"
|
||||
:href="
|
||||
build_wopi_editor_link(props.storedObject.uuid, props.returnPath)
|
||||
"
|
||||
>
|
||||
<i class="fa fa-paragraph"></i>
|
||||
Editer en ligne
|
||||
|
@@ -145,7 +145,9 @@ async function download_info_link(
|
||||
function build_wopi_editor_link(uuid: string, returnPath?: string) {
|
||||
if (returnPath === undefined) {
|
||||
returnPath =
|
||||
window.location.pathname + window.location.search + window.location.hash;
|
||||
window.location.pathname +
|
||||
window.location.search +
|
||||
window.location.hash;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -184,7 +186,10 @@ async function download_and_decrypt_doc(
|
||||
) {
|
||||
downloadInfo = storedObject._links.downloadLink;
|
||||
} else {
|
||||
downloadInfo = await download_info_link(storedObject, atVersionToDownload);
|
||||
downloadInfo = await download_info_link(
|
||||
storedObject,
|
||||
atVersionToDownload,
|
||||
);
|
||||
}
|
||||
|
||||
const rawResponse = await window.fetch(downloadInfo.url);
|
||||
@@ -239,7 +244,10 @@ async function download_doc_as_pdf(storedObject: StoredObject): Promise<Blob> {
|
||||
}
|
||||
|
||||
if (storedObject.currentVersion?.type === "application/pdf") {
|
||||
return download_and_decrypt_doc(storedObject, storedObject.currentVersion);
|
||||
return download_and_decrypt_doc(
|
||||
storedObject,
|
||||
storedObject.currentVersion,
|
||||
);
|
||||
}
|
||||
|
||||
const convertLink = build_convert_link(storedObject.uuid);
|
||||
|
@@ -43,17 +43,11 @@ class StoredObjectVersionNormalizer implements NormalizerInterface, NormalizerAw
|
||||
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]),
|
||||
];
|
||||
|
||||
$normalizationGroups = $context[AbstractNormalizer::GROUPS] ?? [];
|
||||
|
||||
if (is_string($normalizationGroups)) {
|
||||
$normalizationGroups = [$normalizationGroups];
|
||||
}
|
||||
|
||||
if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $normalizationGroups, true)) {
|
||||
if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
|
||||
$data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context);
|
||||
}
|
||||
|
||||
if (in_array(self::WITH_RESTORED_CONTEXT, $normalizationGroups, true)) {
|
||||
if (in_array(self::WITH_RESTORED_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
|
||||
$data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]);
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,7 @@ use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||
|
||||
/**
|
||||
* Immersion.
|
||||
@@ -85,14 +86,14 @@ class Immersion implements \Stringable
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
#[ORM\Column(name: 'tuteurPhoneNumber', type: 'phone_number', nullable: true)]
|
||||
#[\Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber]
|
||||
#[PhonenumberConstraint(type: 'any')]
|
||||
private ?PhoneNumber $tuteurPhoneNumber = null;
|
||||
|
||||
#[ORM\Column(name: 'structureAccName', type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
|
||||
private ?string $structureAccName = null;
|
||||
|
||||
#[ORM\Column(name: 'structureAccPhonenumber', type: 'phone_number', nullable: true)]
|
||||
#[\Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber]
|
||||
#[PhonenumberConstraint(type: 'any')]
|
||||
private ?PhoneNumber $structureAccPhonenumber = null;
|
||||
|
||||
#[ORM\Column(name: 'structureAccEmail', type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
|
||||
|
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Command;
|
||||
|
||||
use Chill\MainBundle\Security\RoleDumper;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand(name: 'chill:main:dump-list-permissions', description: 'Print a markdown reference of permissions (roles) grouped by title with dependencies).')]
|
||||
final class DumpListPermissionsCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly RoleDumper $roleDumper)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$markdown = $this->roleDumper->dumpAsMarkdown();
|
||||
$output->writeln($markdown);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
@@ -14,9 +14,9 @@ namespace Chill\MainBundle\Entity;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as MisdPhoneNumberConstraint;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
|
||||
@@ -67,12 +67,12 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
#[Serializer\Groups(['read', 'write', 'docgen:read'])]
|
||||
#[ORM\Column(type: 'phone_number', nullable: true)]
|
||||
#[MisdPhoneNumberConstraint(type: [MisdPhoneNumberConstraint::ANY])]
|
||||
#[PhonenumberConstraint(type: 'any')]
|
||||
private ?PhoneNumber $phonenumber1 = null;
|
||||
|
||||
#[Serializer\Groups(['read', 'write', 'docgen:read'])]
|
||||
#[ORM\Column(type: 'phone_number', nullable: true)]
|
||||
#[MisdPhoneNumberConstraint(type: [MisdPhoneNumberConstraint::ANY])]
|
||||
#[PhonenumberConstraint(type: 'any')]
|
||||
private ?PhoneNumber $phonenumber2 = null;
|
||||
|
||||
#[Serializer\Groups(['read'])]
|
||||
|
@@ -23,6 +23,7 @@ use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||
|
||||
/**
|
||||
* User.
|
||||
@@ -115,7 +116,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
* The user's mobile phone number.
|
||||
*/
|
||||
#[ORM\Column(type: 'phone_number', nullable: true)]
|
||||
#[\Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber]
|
||||
#[PhonenumberConstraint]
|
||||
private ?PhoneNumber $phonenumber = null;
|
||||
|
||||
/**
|
||||
|
@@ -20,7 +20,7 @@ use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||
use Chill\MainBundle\Repository\RegroupmentRepositoryInterface;
|
||||
|
||||
/**
|
||||
* @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter?: string, formatter: array{form: array<string, mixed>, version: int}}
|
||||
* @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter: string, formatter: array{form: array<string, mixed>, version: int}}
|
||||
*/
|
||||
class ExportConfigNormalizer
|
||||
{
|
||||
|
@@ -27,8 +27,6 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
||||
|
||||
/**
|
||||
* Create a CSV List for the export.
|
||||
*/
|
||||
|
@@ -31,8 +31,6 @@ interface PhoneNumberHelperInterface
|
||||
|
||||
/**
|
||||
* Return true if the validation is configured and available.
|
||||
*
|
||||
* @deprecated this is an internal behaviour of the helper and should not be taken into account outside of the implementation
|
||||
*/
|
||||
public function isPhonenumberValidationConfigured(): bool;
|
||||
|
||||
|
@@ -76,24 +76,6 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
||||
->formatOutOfCountryCallingNumber($phoneNumber, $this->config['default_carrier_code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NumberParseException
|
||||
*/
|
||||
public function parse(string $phoneNumber): PhoneNumber
|
||||
{
|
||||
$sanitizedPhoneNumber = $phoneNumber;
|
||||
|
||||
if (str_starts_with($sanitizedPhoneNumber, '00')) {
|
||||
$sanitizedPhoneNumber = '+'.substr($sanitizedPhoneNumber, 2, null);
|
||||
}
|
||||
|
||||
if (!str_starts_with($sanitizedPhoneNumber, '+') && !str_starts_with($sanitizedPhoneNumber, '0')) {
|
||||
$sanitizedPhoneNumber = '+'.$sanitizedPhoneNumber;
|
||||
}
|
||||
|
||||
return $this->phoneNumberUtil->parse($sanitizedPhoneNumber, $this->config['default_carrier_code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type (mobile, landline, ...) for phone number.
|
||||
*/
|
||||
@@ -122,7 +104,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
||||
*/
|
||||
public function isValidPhonenumberAny($phonenumber): bool
|
||||
{
|
||||
if (false === $this->isConfigured) {
|
||||
if (false === $this->isPhonenumberValidationConfigured()) {
|
||||
return true;
|
||||
}
|
||||
$validation = $this->performTwilioLookup($phonenumber);
|
||||
@@ -142,7 +124,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
||||
*/
|
||||
public function isValidPhonenumberLandOrVoip($phonenumber): bool
|
||||
{
|
||||
if (false === $this->isConfigured) {
|
||||
if (false === $this->isPhonenumberValidationConfigured()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -163,7 +145,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
||||
*/
|
||||
public function isValidPhonenumberMobile($phonenumber): bool
|
||||
{
|
||||
if (false === $this->isConfigured) {
|
||||
if (false === $this->isPhonenumberValidationConfigured()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -178,7 +160,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
||||
|
||||
private function performTwilioLookup($phonenumber)
|
||||
{
|
||||
if (false === $this->isConfigured) {
|
||||
if (false === $this->isPhonenumberValidationConfigured()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,9 @@ export const ISOToDatetime = (str: string | null): Date | null => {
|
||||
[time, timezone] = times.split(times.charAt(8)),
|
||||
[hours, minutes, seconds] = time.split(":").map((s) => parseInt(s));
|
||||
if ("0000" === timezone) {
|
||||
return new Date(Date.UTC(year, month - 1, date, hours, minutes, seconds));
|
||||
return new Date(
|
||||
Date.UTC(year, month - 1, date, hours, minutes, seconds),
|
||||
);
|
||||
}
|
||||
|
||||
return new Date(year, month - 1, date, hours, minutes, seconds);
|
||||
@@ -152,24 +154,11 @@ export const intervalISOToDays = (str: string | null): number | null => {
|
||||
vstring = "";
|
||||
break;
|
||||
default:
|
||||
throw Error("this character should not appears: " + str.charAt(i));
|
||||
throw Error(
|
||||
"this character should not appears: " + str.charAt(i),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return days;
|
||||
};
|
||||
|
||||
export function getTimezoneOffsetString(date: Date, timeZone: string): string {
|
||||
const utcDate = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
|
||||
const tzDate = new Date(date.toLocaleString("en-US", { timeZone }));
|
||||
const offsetMinutes = (utcDate.getTime() - tzDate.getTime()) / (60 * 1000);
|
||||
|
||||
// Inverser le signe pour avoir la convention ±HH:MM
|
||||
const sign = offsetMinutes <= 0 ? "+" : "-";
|
||||
const absMinutes = Math.abs(offsetMinutes);
|
||||
const hours = String(Math.floor(absMinutes / 60)).padStart(2, "0");
|
||||
const minutes = String(absMinutes % 60).padStart(2, "0");
|
||||
|
||||
return `${sign}${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
|
@@ -1,26 +1,15 @@
|
||||
import {
|
||||
DynamicKeys,
|
||||
Scope,
|
||||
ValidationExceptionInterface,
|
||||
ValidationProblemFromMap,
|
||||
ViolationFromMap
|
||||
} from "../../types";
|
||||
import { Scope } from "../../types";
|
||||
|
||||
export type body = Record<string, boolean | string | number | null>;
|
||||
export type fetchOption = Record<string, boolean | string | number | null>;
|
||||
export type Primitive = string | number | boolean | null;
|
||||
|
||||
export type Params = Record<string, number | string>;
|
||||
|
||||
export interface Pagination {
|
||||
first: number;
|
||||
items_per_page: number;
|
||||
more: boolean;
|
||||
next: string | null;
|
||||
previous: string | null;
|
||||
}
|
||||
|
||||
export interface PaginationResponse<T> {
|
||||
pagination: Pagination;
|
||||
pagination: {
|
||||
more: boolean;
|
||||
items_per_page: number;
|
||||
};
|
||||
results: T[];
|
||||
count: number;
|
||||
}
|
||||
@@ -31,115 +20,20 @@ export interface TransportExceptionInterface {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ValidationException<
|
||||
M extends Record<string, Record<string, string|number>> = Record<
|
||||
string,
|
||||
Record<string, string|number>
|
||||
>,
|
||||
>
|
||||
extends Error
|
||||
implements ValidationExceptionInterface<M>
|
||||
{
|
||||
public readonly name = "ValidationException" as const;
|
||||
public readonly problems: ValidationProblemFromMap<M>;
|
||||
public readonly violations: string[];
|
||||
public readonly violationsList: ViolationFromMap<M>[];
|
||||
public readonly titles: string[];
|
||||
public readonly propertyPaths: DynamicKeys<M> & string[];
|
||||
public readonly byProperty: Record<Extract<keyof M, string>, string[]>;
|
||||
|
||||
constructor(problem: ValidationProblemFromMap<M>) {
|
||||
const message = [problem.title, problem.detail].filter(Boolean).join(" — ");
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
|
||||
this.problems = problem;
|
||||
|
||||
this.violationsList = problem.violations;
|
||||
this.violations = problem.violations.map(
|
||||
(v) => `${v.title}: ${v.propertyPath}`,
|
||||
);
|
||||
|
||||
this.titles = problem.violations.map((v) => v.title);
|
||||
|
||||
this.propertyPaths = problem.violations.map(
|
||||
(v) => v.propertyPath,
|
||||
) as DynamicKeys<M> & string[];
|
||||
|
||||
this.byProperty = problem.violations.reduce(
|
||||
(acc, v) => {
|
||||
const key = v.propertyPath.replace('/\[\d+\]$/', "") as Extract<keyof M, string>;
|
||||
(acc[key] ||= []).push(v.title);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<Extract<keyof M, string>, string[]>,
|
||||
);
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, ValidationException);
|
||||
}
|
||||
}
|
||||
|
||||
violationsByNormalizedProperty(property: Extract<keyof M, string>): ViolationFromMap<M>[] {
|
||||
return this.violationsList.filter((v) => v.propertyPath.replace(/\[\d+\]$/, "") === property);
|
||||
}
|
||||
|
||||
violationsByNormalizedPropertyAndParams<
|
||||
P extends Extract<keyof M, string>,
|
||||
K extends Extract<keyof M[P], string>
|
||||
>(
|
||||
property: P,
|
||||
param: K,
|
||||
param_value: M[P][K]
|
||||
): ViolationFromMap<M>[]
|
||||
{
|
||||
const list = this.violationsByNormalizedProperty(property);
|
||||
|
||||
return list.filter(
|
||||
(v): boolean =>
|
||||
!!v.parameters &&
|
||||
// `with_parameter in v.parameters` check indexing
|
||||
param in v.parameters &&
|
||||
// the cast is safe, because we have overloading that bind the types
|
||||
(v.parameters as M[P])[param] === param_value
|
||||
);
|
||||
}
|
||||
export interface ValidationExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "ValidationException";
|
||||
error: object;
|
||||
violations: string[];
|
||||
titles: string[];
|
||||
propertyPaths: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the exception is a ValidationExceptionInterface
|
||||
* @param x
|
||||
*/
|
||||
export function isValidationException<M extends Record<string, Record<string, string|number>>>(
|
||||
x: unknown,
|
||||
): x is ValidationExceptionInterface<M> {
|
||||
return (
|
||||
x instanceof ValidationException ||
|
||||
(typeof x === "object" &&
|
||||
x !== null &&
|
||||
(x as any).name === "ValidationException")
|
||||
);
|
||||
}
|
||||
|
||||
export function isValidationProblem(x: unknown): x is {
|
||||
type: string;
|
||||
export interface ValidationErrorResponse extends TransportExceptionInterface {
|
||||
violations: {
|
||||
title: string;
|
||||
violations: { propertyPath: string; title: string }[];
|
||||
} {
|
||||
if (!x || typeof x !== "object") return false;
|
||||
const o = x as any;
|
||||
return (
|
||||
typeof o.type === "string" &&
|
||||
typeof o.title === "string" &&
|
||||
Array.isArray(o.violations) &&
|
||||
o.violations.every(
|
||||
(v: any) =>
|
||||
v &&
|
||||
typeof v === "object" &&
|
||||
typeof v.propertyPath === "string" &&
|
||||
typeof v.title === "string",
|
||||
)
|
||||
);
|
||||
propertyPath: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AccessExceptionInterface extends TransportExceptionInterface {
|
||||
@@ -166,151 +60,12 @@ export interface ConflictHttpExceptionInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic api method that can be adapted to any fetch request.
|
||||
* Generic api method that can be adapted to any fetch request
|
||||
*
|
||||
* What this does
|
||||
* - Performs a single HTTP request using fetch and returns the parsed JSON as Output.
|
||||
* - Interprets common API errors and throws typed exceptions you can catch in your UI.
|
||||
* - When the server returns a Symfony validation problem (HTTP 422), the error is
|
||||
* rethrown as a typed ValidationException that is aware of your Violation Map (see below).
|
||||
*
|
||||
* Important: For GET endpoints that return lists, prefer using fetchResults, which
|
||||
* handles pagination and aggregation for you.
|
||||
*
|
||||
* Violation Map (M): make your 422 errors strongly typed
|
||||
* ------------------------------------------------------
|
||||
* Symfony’s validation problem+json payload looks like this (simplified):
|
||||
*
|
||||
* {
|
||||
* "type": "https://symfony.com/errors/validation",
|
||||
* "title": "Validation Failed",
|
||||
* "violations": [
|
||||
* {
|
||||
* "propertyPath": "mobilenumber",
|
||||
* "title": "This value is not a valid phone number.",
|
||||
* "parameters": {
|
||||
* "{{ value }}": "+33 1 02 03 04 05",
|
||||
* "{{ types }}": "mobile number"
|
||||
* },
|
||||
* "type": "urn:uuid:..."
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* The makeFetch generic type parameter M lets you describe, field by field, which
|
||||
* parameters may appear for each propertyPath. Doing so gives you full type-safety when
|
||||
* consuming ValidationException in your UI code.
|
||||
*
|
||||
* How to build M (Violation Map)
|
||||
* - M is a map where each key is a server-side propertyPath (string), and the value is a
|
||||
* record describing the allowed keys in the parameters object for that property.
|
||||
* - Keys in parameters are the exact strings you receive from Symfony, including the
|
||||
* curly-braced placeholders such as "{{ value }}", "{{ types }}", etc.
|
||||
*
|
||||
* Example from Person creation (WritePersonViolationMap)
|
||||
* -----------------------------------------------------
|
||||
* In ChillPersonBundle/Resources/public/vuejs/_api/OnTheFly.ts you’ll find:
|
||||
*
|
||||
* export type WritePersonViolationMap = {
|
||||
* gender: {
|
||||
* "{{ value }}": string | null;
|
||||
* };
|
||||
* mobilenumber: {
|
||||
* "{{ types }}": string; // ex: "mobile number"
|
||||
* "{{ value }}": string; // ex: "+33 1 02 03 04 05"
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* This means:
|
||||
* - If the server reports a violation for propertyPath "gender", the parameters object
|
||||
* is expected to contain a key "{{ value }}" with a string or null value.
|
||||
* - If the server reports a violation for propertyPath "mobilenumber", the parameters
|
||||
* may include "{{ value }}" and "{{ types }}" as strings.
|
||||
*
|
||||
* How makeFetch uses M
|
||||
* - When the response has status 422 and the payload matches a Symfony validation
|
||||
* problem, makeFetch casts it to ValidationProblemFromMap<M> and throws a
|
||||
* ValidationException<M>.
|
||||
* - The ValidationException exposes helpful, pre-computed fields:
|
||||
* - exception.problem: the full typed payload
|
||||
* - exception.violations: ["Title: propertyPath", ...]
|
||||
* - exception.titles: ["Title 1", "Title 2", ...]
|
||||
* - exception.propertyPaths: ["gender", "mobilenumber", ...] (typed from M)
|
||||
* - exception.byProperty: { gender: [titles...], mobilenumber: [titles...] }
|
||||
*
|
||||
* Typical usage patterns
|
||||
* ----------------------
|
||||
* 1) GET without Validation Map (no 422 expected):
|
||||
*
|
||||
* const centers = await makeFetch<null, { showCenters: boolean; centers: Center[] }>(
|
||||
* "GET",
|
||||
* "/api/1.0/person/creation/authorized-centers",
|
||||
* null
|
||||
* );
|
||||
*
|
||||
* 2) POST with body and Violation Map:
|
||||
*
|
||||
* type WritePersonViolationMap = {
|
||||
* gender: { "{{ value }}": string | null };
|
||||
* mobilenumber: { "{{ types }}": string; "{{ value }}": string };
|
||||
* };
|
||||
*
|
||||
* try {
|
||||
* const created = await makeFetch<PersonWrite, Person, WritePersonViolationMap>(
|
||||
* "POST",
|
||||
* "/api/1.0/person/person.json",
|
||||
* personPayload
|
||||
* );
|
||||
* // Success path
|
||||
* } catch (e) {
|
||||
* if (isValidationException(e)) {
|
||||
* // Fully typed:
|
||||
* e.propertyPaths.includes("mobilenumber");
|
||||
* const firstTitleForMobile = e.byProperty.mobilenumber?.[0];
|
||||
* // You can also inspect parameter values:
|
||||
* const v = e.problem.violations.find(v => v.propertyPath === "mobilenumber");
|
||||
* const rawValue = v?.parameters?.["{{ value }}"]; // typed as string
|
||||
* } else {
|
||||
* // Other error handling (AccessException, ConflictHttpException, etc.)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Tips to design your Violation Map
|
||||
* - Use exact propertyPath strings as exposed by the API (they usually match your
|
||||
* DTO field names or entity property paths used by the validator).
|
||||
* - Inside each property, list only the placeholders that you actually read in the UI
|
||||
* (you can always add more later). This keeps your types strict but pragmatic.
|
||||
* - If a field may not include parameters at all, you can set it to an empty object {}.
|
||||
* - If you don’t care about parameter typing, you can omit M entirely and rely on the
|
||||
* default loose typing (Record<string, Primitive>), but you’ll lose safety.
|
||||
*
|
||||
* Error taxonomy thrown by makeFetch
|
||||
* - ValidationException<M> when status = 422 and payload is a validation problem.
|
||||
* - AccessException when status = 403.
|
||||
* - ConflictHttpException when status = 409.
|
||||
* - A generic error object for other non-ok statuses.
|
||||
*
|
||||
* @typeParam Input - Shape of the request body you send (if any)
|
||||
* @typeParam Output - Shape of the successful JSON response you expect
|
||||
* @typeParam M - Violation Map describing the per-field parameters you expect
|
||||
* in Symfony validation violations. See examples above.
|
||||
*
|
||||
* @param method The HTTP method to use (POST, GET, PUT, PATCH, DELETE)
|
||||
* @param url The absolute or relative URL to call
|
||||
* @param body The request payload. If null/undefined, no body is sent
|
||||
* @param options Extra fetch options/headers merged into the request
|
||||
*
|
||||
* @returns The parsed JSON response typed as Output. For 204 No Content, resolves
|
||||
* with undefined (void).
|
||||
* This method is suitable make a single fetch. When performing a GET to fetch a list of elements, always consider pagination
|
||||
* and use of the @link{fetchResults} method.
|
||||
*/
|
||||
export const makeFetch = async <
|
||||
Input,
|
||||
Output,
|
||||
M extends Record<string, Record<string, string|number>> = Record<
|
||||
string,
|
||||
Record<string, string|number>
|
||||
>,
|
||||
>(
|
||||
export const makeFetch = <Input, Output>(
|
||||
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",
|
||||
url: string,
|
||||
body?: body | Input | null,
|
||||
@@ -330,8 +85,7 @@ export const makeFetch = async <
|
||||
if (typeof options !== "undefined") {
|
||||
opts = Object.assign(opts, options);
|
||||
}
|
||||
|
||||
return fetch(url, opts).then(async (response) => {
|
||||
return fetch(url, opts).then((response) => {
|
||||
if (response.status === 204) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -341,20 +95,9 @@ export const makeFetch = async <
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
// Unprocessable Entity -> payload de validation Symfony
|
||||
const json = await response.json().catch(() => undefined);
|
||||
|
||||
if (isValidationProblem(json)) {
|
||||
// On ré-interprète le payload selon M (ParamMap) pour typer les violations
|
||||
const problem = json as unknown as ValidationProblemFromMap<M>;
|
||||
throw new ValidationException<M>(problem);
|
||||
}
|
||||
|
||||
const err = new Error(
|
||||
"Validation failed but payload is not a ValidationProblem",
|
||||
);
|
||||
(err as any).raw = json;
|
||||
throw err;
|
||||
return response.json().then((response) => {
|
||||
throw ValidationException(response);
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
@@ -419,6 +162,12 @@ function _fetchAction<T>(
|
||||
throw NotFoundException(response);
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
return response.json().then((response) => {
|
||||
throw ValidationException(response);
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw AccessException(response);
|
||||
}
|
||||
@@ -477,6 +226,24 @@ export const fetchScopes = (): Promise<Scope[]> => {
|
||||
return fetchResults("/api/1.0/main/scope.json");
|
||||
};
|
||||
|
||||
/**
|
||||
* Error objects to be thrown
|
||||
*/
|
||||
const ValidationException = (
|
||||
response: ValidationErrorResponse,
|
||||
): ValidationExceptionInterface => {
|
||||
const error = {} as ValidationExceptionInterface;
|
||||
error.name = "ValidationException";
|
||||
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;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const AccessException = (response: Response): AccessExceptionInterface => {
|
||||
const error = {} as AccessExceptionInterface;
|
||||
|
@@ -8,26 +8,6 @@ import { TranslatableString } from "ChillMainAssets/types";
|
||||
* @param locale defaults to browser locale
|
||||
* @returns The localized string or null if no translation is available
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prepends the current HTML lang code to the given URL.
|
||||
* Example: If lang="fr" and url="/about", returns "/fr/about"
|
||||
*
|
||||
* @param url The URL to localize
|
||||
* @returns The localized URL
|
||||
*/
|
||||
export function localizedUrl(url: string): string {
|
||||
const lang =
|
||||
document.documentElement.lang || navigator.language.split("-")[0] || "fr";
|
||||
// Ensure url starts with a slash and does not already start with /{lang}/
|
||||
const normalizedUrl = url.startsWith("/") ? url : `/${url}`;
|
||||
const langPrefix = `/${lang}`;
|
||||
if (normalizedUrl.startsWith(langPrefix + "/")) {
|
||||
return normalizedUrl;
|
||||
}
|
||||
return `${langPrefix}${normalizedUrl}`;
|
||||
}
|
||||
|
||||
export function localizeString(
|
||||
translatableString: TranslatableString | null | undefined,
|
||||
locale?: string,
|
||||
|
@@ -126,8 +126,7 @@ function loadDynamicPicker(element) {
|
||||
-1 ===
|
||||
this.suggested.findIndex(
|
||||
(e) => e.type === entity.type && e.id === entity.id,
|
||||
) &&
|
||||
"me" !== entity
|
||||
)
|
||||
) {
|
||||
this.suggested.push(entity);
|
||||
}
|
||||
|
@@ -0,0 +1,15 @@
|
||||
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");
|
@@ -1,12 +0,0 @@
|
||||
import App from "ChillMainAssets/vuejs/HomepageWidget/App.vue";
|
||||
import { createApp } from "vue";
|
||||
import { store } from "ChillMainAssets/vuejs/HomepageWidget/store";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
homepage_config: string;
|
||||
}
|
||||
}
|
||||
|
||||
const _app = createApp(App);
|
||||
_app.use(store).mount("#homepage_widget");
|
@@ -1,56 +1,14 @@
|
||||
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
|
||||
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
|
||||
import { CreatableEntityType } from "ChillPersonAssets/types";
|
||||
|
||||
export interface DateTime {
|
||||
datetime: string;
|
||||
datetime8601: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A date representation to use when we create or update a date
|
||||
*/
|
||||
export interface DateTimeWrite {
|
||||
/**
|
||||
* Must be a string in format Y-m-d\TH:i:sO
|
||||
*/
|
||||
datetime: string;
|
||||
}
|
||||
|
||||
export interface Civility {
|
||||
type: "chill_main_civility";
|
||||
id: number;
|
||||
abbreviation: TranslatableString;
|
||||
active: boolean;
|
||||
name: TranslatableString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight reference to Civility, to use in POST or PUT requests.
|
||||
*/
|
||||
export interface SetCivility {
|
||||
type: "chill_main_civility";
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface Gender {
|
||||
type: "chill_main_gender";
|
||||
id: number;
|
||||
label: string;
|
||||
genderTranslation: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight reference to a Gender, used in POST / PUT requests.
|
||||
*/
|
||||
export interface SetGender {
|
||||
type: "chill_main_gender";
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface Household {
|
||||
type: "household";
|
||||
id: number;
|
||||
// TODO
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
@@ -65,18 +23,6 @@ export interface Center {
|
||||
id: number;
|
||||
type: "center";
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SetCenter is a lightweight reference used in POST/PUT requests to associate an existing center with a resource.
|
||||
* It links by id only and does not create or modify centers.
|
||||
* Expected shape: { type: "center", id: number }.
|
||||
* Requests will fail if the id is invalid, the center doesn't exist, or permissions are insufficient.
|
||||
*/
|
||||
export interface SetCenter {
|
||||
id: number;
|
||||
type: "center";
|
||||
}
|
||||
|
||||
export interface Scope {
|
||||
@@ -269,142 +215,3 @@ export interface ExportGeneration {
|
||||
export interface PrivateCommentEmbeddable {
|
||||
comments: Record<number, string>;
|
||||
}
|
||||
|
||||
// API Exception types
|
||||
export interface TransportExceptionInterface {
|
||||
name: string;
|
||||
}
|
||||
|
||||
type IndexedKey<Base extends string> = `${Base}[${number}]`;
|
||||
type BaseKeys<M> = Extract<keyof M, string>;
|
||||
|
||||
export type DynamicKeys<M extends Record<string, Record<string, unknown>>> =
|
||||
| BaseKeys<M>
|
||||
| { [K in BaseKeys<M> as IndexedKey<K>]: K }[IndexedKey<BaseKeys<M>>];
|
||||
|
||||
type NormalizeKey<K extends string> = K extends `${infer B}[${number}]` ? B : K;
|
||||
|
||||
export type ViolationFromMap<M extends Record<string, Record<string, unknown>>> = {
|
||||
[K in DynamicKeys<M> & string]: { // <- note le "& string" ici
|
||||
propertyPath: K;
|
||||
title: string;
|
||||
parameters?: M[NormalizeKey<K>];
|
||||
type?: string;
|
||||
}
|
||||
}[DynamicKeys<M> & string];
|
||||
|
||||
export type ValidationProblemFromMap<
|
||||
M extends Record<string, Record<string, string|number>>,
|
||||
> = {
|
||||
type: string;
|
||||
title: string;
|
||||
detail?: string;
|
||||
violations: ViolationFromMap<M>[];
|
||||
} & Record<string, unknown>;
|
||||
|
||||
export interface ValidationExceptionInterface<
|
||||
M extends Record<string, Record<string, string|number>> = Record<
|
||||
string,
|
||||
Record<string, string|number>
|
||||
>,
|
||||
> extends Error {
|
||||
name: "ValidationException";
|
||||
/** Full server payload copy */
|
||||
problems: ValidationProblemFromMap<M>;
|
||||
/** A list of all violations, with property key */
|
||||
violationsList: ViolationFromMap<M>[];
|
||||
/** Compact list "Title: path" */
|
||||
violations: string[];
|
||||
/** Only titles */
|
||||
titles: string[];
|
||||
/** Only property paths */
|
||||
propertyPaths: DynamicKeys<M> & string[];
|
||||
/** Indexing by property (useful for display by field) */
|
||||
byProperty: Record<Extract<keyof M, string>, string[]>;
|
||||
|
||||
violationsByNormalizedProperty(property: Extract<keyof M, string>): ViolationFromMap<M>[];
|
||||
|
||||
violationsByNormalizedPropertyAndParams<
|
||||
P extends Extract<keyof M, string>,
|
||||
K extends Extract<keyof M[P], string>
|
||||
>(
|
||||
property: P,
|
||||
param: K,
|
||||
param_value: M[P][K]
|
||||
): ViolationFromMap<M>[];
|
||||
}
|
||||
|
||||
export interface AccessExceptionInterface extends TransportExceptionInterface {
|
||||
name: "AccessException";
|
||||
violations: string[];
|
||||
}
|
||||
|
||||
export interface NotFoundExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "NotFoundException";
|
||||
}
|
||||
|
||||
export interface ServerExceptionInterface extends TransportExceptionInterface {
|
||||
name: "ServerException";
|
||||
message: string;
|
||||
code: number;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface ConflictHttpExceptionInterface
|
||||
extends TransportExceptionInterface {
|
||||
name: "ConflictHttpException";
|
||||
violations: string[];
|
||||
}
|
||||
|
||||
export type ApiException =
|
||||
| ValidationExceptionInterface
|
||||
| AccessExceptionInterface
|
||||
| NotFoundExceptionInterface
|
||||
| ServerExceptionInterface
|
||||
| ConflictHttpExceptionInterface;
|
||||
|
||||
export interface Modal {
|
||||
showModal: boolean;
|
||||
modalDialogClass: string;
|
||||
}
|
||||
|
||||
export interface Selected {
|
||||
result: UserGroupOrUser;
|
||||
}
|
||||
|
||||
export interface addNewEntities {
|
||||
selected: Selected[];
|
||||
modal: Modal;
|
||||
}
|
||||
|
||||
export enum HomepageTabs {
|
||||
MyCustoms,
|
||||
MyTickets,
|
||||
MyNotifications,
|
||||
MyAccompanyingCourses,
|
||||
MyEvaluations,
|
||||
MyTasks,
|
||||
MyWorkflows,
|
||||
}
|
||||
|
||||
export interface HomepageConfig {
|
||||
defaultTab: HomepageTabs;
|
||||
displayTabs: HomepageTabs[];
|
||||
}
|
||||
|
||||
export interface TabDefinition {
|
||||
key: HomepageTabs;
|
||||
label: string;
|
||||
icon: string | null;
|
||||
counter: () => number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for the CreateModal and Create component
|
||||
*/
|
||||
export interface CreateComponentConfig {
|
||||
action?: string;
|
||||
allowedTypes: CreatableEntityType[];
|
||||
query?: string;
|
||||
}
|
||||
|
@@ -24,7 +24,9 @@
|
||||
{{ trans(getTextTitle) }}
|
||||
<span v-if="flag.loading" class="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
|
||||
<span class="sr-only">{{ trans(ADDRESS_LOADING) }}</span>
|
||||
<span class="sr-only">{{
|
||||
trans(ADDRESS_LOADING)
|
||||
}}</span>
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
@@ -88,7 +90,9 @@
|
||||
{{ trans(getTextTitle) }}
|
||||
<span v-if="flag.loading" class="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
|
||||
<span class="sr-only">{{ trans(ADDRESS_LOADING) }}</span>
|
||||
<span class="sr-only">{{
|
||||
trans(ADDRESS_LOADING)
|
||||
}}</span>
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
@@ -144,7 +148,10 @@
|
||||
</template>
|
||||
<template #action>
|
||||
<li v-if="!this.context.edit && this.useDatePane">
|
||||
<button class="btn btn-update change-icon" @click="closeEditPane">
|
||||
<button
|
||||
class="btn btn-update change-icon"
|
||||
@click="closeEditPane"
|
||||
>
|
||||
{{ trans(NEXT) }}
|
||||
<i class="fa fa-fw fa-arrow-right" />
|
||||
</button>
|
||||
@@ -171,7 +178,9 @@
|
||||
{{ trans(getTextTitle) }}
|
||||
<span v-if="flag.loading" class="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
|
||||
<span class="sr-only">{{ trans(ADDRESS_LOADING) }}</span>
|
||||
<span class="sr-only">{{
|
||||
trans(ADDRESS_LOADING)
|
||||
}}</span>
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
@@ -385,7 +394,8 @@ export default {
|
||||
getTextTitle() {
|
||||
if (
|
||||
typeof this.options.title !== "undefined" &&
|
||||
(this.options.title.edit !== null || this.options.title.create !== null)
|
||||
(this.options.title.edit !== null ||
|
||||
this.options.title.create !== null)
|
||||
) {
|
||||
console.log("this.options.title", this.options.title);
|
||||
|
||||
@@ -505,7 +515,10 @@ export default {
|
||||
if (!this.context.edit) {
|
||||
this.context.edit = true;
|
||||
this.context.addressId = params.addressId;
|
||||
console.log("context is now edit, with address", params.addressId);
|
||||
console.log(
|
||||
"context is now edit, with address",
|
||||
params.addressId,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -632,7 +645,9 @@ export default {
|
||||
? this.entity.address.confidential
|
||||
: false;
|
||||
this.entity.selected.isNoAddress =
|
||||
this.context.edit && this.entity.address.text === "" ? true : false;
|
||||
this.context.edit && this.entity.address.text === ""
|
||||
? true
|
||||
: false;
|
||||
|
||||
this.entity.selected.country = this.context.edit
|
||||
? this.entity.address.country
|
||||
@@ -727,7 +742,8 @@ export default {
|
||||
// add the address reference, if any
|
||||
if (this.entity.selected.address.addressReference !== undefined) {
|
||||
newAddress = Object.assign(newAddress, {
|
||||
addressReference: this.entity.selected.address.addressReference,
|
||||
addressReference:
|
||||
this.entity.selected.address.addressReference,
|
||||
});
|
||||
} else {
|
||||
newAddress = Object.assign(newAddress, {
|
||||
@@ -747,7 +763,10 @@ export default {
|
||||
});
|
||||
}
|
||||
if (this.validTo && null !== this.entity.selected.valid.to) {
|
||||
console.log("add validTo in fetch body", this.entity.selected.valid.to);
|
||||
console.log(
|
||||
"add validTo in fetch body",
|
||||
this.entity.selected.valid.to,
|
||||
);
|
||||
newAddress = Object.assign(newAddress, {
|
||||
validTo: {
|
||||
datetime: `${this.entity.selected.valid.to.toISOString().split("T")[0]}T00:00:00+0100`,
|
||||
@@ -759,7 +778,10 @@ export default {
|
||||
newPostcode = Object.assign(newPostcode, {
|
||||
country: { id: this.entity.selected.country.id },
|
||||
}); //TODO why not assign postcodeBody here = Object.assign(postcodeBody, {'origin': 3}); ?
|
||||
console.log("writeNew postcode is true! newPostcode: ", newPostcode);
|
||||
console.log(
|
||||
"writeNew postcode is true! newPostcode: ",
|
||||
newPostcode,
|
||||
);
|
||||
newAddress = Object.assign(newAddress, {
|
||||
newPostcode: newPostcode,
|
||||
});
|
||||
|
@@ -72,7 +72,10 @@ export default {
|
||||
this.entity.addressMap.zoom,
|
||||
);
|
||||
} else {
|
||||
this.map.setView(lonLatForLeaflet(this.addressPoint.coordinates), 15);
|
||||
this.map.setView(
|
||||
lonLatForLeaflet(this.addressPoint.coordinates),
|
||||
15,
|
||||
);
|
||||
}
|
||||
|
||||
this.map.scrollWheelZoom.disable();
|
||||
@@ -102,7 +105,9 @@ export default {
|
||||
},
|
||||
update() {
|
||||
if (this.marker && this.entity.addressMap.center) {
|
||||
this.marker.setLatLng(lonLatForLeaflet(this.entity.addressMap.center));
|
||||
this.marker.setLatLng(
|
||||
lonLatForLeaflet(this.entity.addressMap.center),
|
||||
);
|
||||
this.map.panTo(lonLatForLeaflet(this.entity.addressMap.center));
|
||||
}
|
||||
},
|
||||
|
@@ -55,7 +55,9 @@
|
||||
:placeholder="trans(ADDRESS_BUILDING_NAME)"
|
||||
v-model="buildingName"
|
||||
/>
|
||||
<label for="buildingName">{{ trans(ADDRESS_BUILDING_NAME) }}</label>
|
||||
<label for="buildingName">{{
|
||||
trans(ADDRESS_BUILDING_NAME)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating my-1">
|
||||
<input
|
||||
@@ -77,7 +79,9 @@
|
||||
:placeholder="trans(ADDRESS_DISTRIBUTION)"
|
||||
v-model="distribution"
|
||||
/>
|
||||
<label for="distribution">{{ trans(ADDRESS_DISTRIBUTION) }}</label>
|
||||
<label for="distribution">{{
|
||||
trans(ADDRESS_DISTRIBUTION)
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -57,7 +57,9 @@
|
||||
:placeholder="trans(ADDRESS_STREET_NUMBER)"
|
||||
v-model="streetNumber"
|
||||
/>
|
||||
<label for="streetNumber">{{ trans(ADDRESS_STREET_NUMBER) }}</label>
|
||||
<label for="streetNumber">{{
|
||||
trans(ADDRESS_STREET_NUMBER)
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,7 +100,9 @@ export default {
|
||||
props: ["entity", "context", "updateMapCenter", "flag", "checkErrors"],
|
||||
data() {
|
||||
return {
|
||||
value: this.context.edit ? this.entity.address.addressReference : null,
|
||||
value: this.context.edit
|
||||
? this.entity.address.addressReference
|
||||
: null,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
@@ -171,7 +175,8 @@ export default {
|
||||
.then(
|
||||
(addresses) =>
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.addresses = addresses.results;
|
||||
this.entity.loaded.addresses =
|
||||
addresses.results;
|
||||
this.isLoading = false;
|
||||
resolve();
|
||||
}),
|
||||
@@ -188,7 +193,8 @@ export default {
|
||||
.then(
|
||||
(addresses) =>
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.addresses = addresses.results;
|
||||
this.entity.loaded.addresses =
|
||||
addresses.results;
|
||||
this.isLoading = false;
|
||||
resolve();
|
||||
}),
|
||||
|
@@ -158,7 +158,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
transName(value) {
|
||||
return value.code && value.name ? `${value.name} (${value.code})` : "";
|
||||
return value.code && value.name
|
||||
? `${value.name} (${value.code})`
|
||||
: "";
|
||||
},
|
||||
selectCity(value) {
|
||||
console.log(value);
|
||||
@@ -166,7 +168,8 @@ export default {
|
||||
this.entity.selected.postcode.name = value.name;
|
||||
this.entity.selected.postcode.code = value.code;
|
||||
if (value.center) {
|
||||
this.entity.selected.postcode.coordinates = value.center.coordinates;
|
||||
this.entity.selected.postcode.coordinates =
|
||||
value.center.coordinates;
|
||||
}
|
||||
this.entity.selected.writeNew.postcode = false;
|
||||
this.$emit("getReferenceAddresses", value);
|
||||
@@ -187,7 +190,8 @@ export default {
|
||||
.then(
|
||||
(cities) =>
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.cities = cities.results.filter(
|
||||
this.entity.loaded.cities =
|
||||
cities.results.filter(
|
||||
(c) => c.origin !== 3,
|
||||
); // filter out user-defined cities
|
||||
this.isLoading = false;
|
||||
@@ -206,7 +210,8 @@ export default {
|
||||
.then(
|
||||
(cities) =>
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.cities = cities.results.filter(
|
||||
this.entity.loaded.cities =
|
||||
cities.results.filter(
|
||||
(c) => c.origin !== 3,
|
||||
); // filter out user-defined cities
|
||||
this.isLoading = false;
|
||||
|
@@ -60,8 +60,12 @@ export default {
|
||||
sortedCountries() {
|
||||
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"),
|
||||
);
|
||||
sortedCountries.push(
|
||||
...countries.filter((c) => c.countryCode === "BE"),
|
||||
);
|
||||
sortedCountries.push(
|
||||
...countries
|
||||
.filter((c) => c.countryCode !== "FR")
|
||||
|
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div v-if="insideModal === false" class="loading">
|
||||
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
|
||||
<i
|
||||
v-if="flag.loading"
|
||||
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
|
||||
/>
|
||||
<span class="sr-only">{{ $t("loading") }}</span>
|
||||
</div>
|
||||
|
||||
@@ -139,7 +142,8 @@ export default {
|
||||
address["street"] = this.entity.selected.address.street
|
||||
? this.entity.selected.address.street
|
||||
: null;
|
||||
address["streetNumber"] = this.entity.selected.address.streetNumber
|
||||
address["streetNumber"] = this.entity.selected.address
|
||||
.streetNumber
|
||||
? this.entity.selected.address.streetNumber
|
||||
: null;
|
||||
address["floor"] = this.entity.selected.address.floor
|
||||
@@ -154,10 +158,12 @@ export default {
|
||||
address["flat"] = this.entity.selected.address.flat
|
||||
? this.entity.selected.address.flat
|
||||
: null;
|
||||
address["buildingName"] = this.entity.selected.address.buildingName
|
||||
address["buildingName"] = this.entity.selected.address
|
||||
.buildingName
|
||||
? this.entity.selected.address.buildingName
|
||||
: null;
|
||||
address["distribution"] = this.entity.selected.address.distribution
|
||||
address["distribution"] = this.entity.selected.address
|
||||
.distribution
|
||||
? this.entity.selected.address.distribution
|
||||
: null;
|
||||
address["extra"] = this.entity.selected.address.extra
|
||||
|
@@ -2,7 +2,10 @@
|
||||
<div class="address-form">
|
||||
<!-- Not display in modal -->
|
||||
<div v-if="insideModal === false" class="loading">
|
||||
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
|
||||
<i
|
||||
v-if="flag.loading"
|
||||
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
|
||||
/>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
|
||||
|
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div v-if="!onlyButton" class="mt-4 flex-grow-1">
|
||||
<div class="loading">
|
||||
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
|
||||
<i
|
||||
v-if="flag.loading"
|
||||
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
|
||||
/>
|
||||
<span class="sr-only">{{ trans(ADDRESS_LOADING) }}</span>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +14,9 @@
|
||||
|
||||
<div v-if="flag.success" class="alert alert-success">
|
||||
{{ trans(getSuccessText) }}
|
||||
<span v-if="forceRedirect">{{ trans(ADDRESS_WAIT_REDIRECTION) }}</span>
|
||||
<span v-if="forceRedirect">{{
|
||||
trans(ADDRESS_WAIT_REDIRECTION)
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -42,7 +47,9 @@
|
||||
name="button"
|
||||
:title="trans(getTextButton)"
|
||||
>
|
||||
<span v-if="displayTextButton">{{ trans(getTextButton) }}</span>
|
||||
<span v-if="displayTextButton">{{
|
||||
trans(getTextButton)
|
||||
}}</span>
|
||||
</button>
|
||||
</template>
|
||||
</action-buttons>
|
||||
@@ -55,7 +62,9 @@
|
||||
:use-date-pane="useDatePane"
|
||||
/>
|
||||
|
||||
<div v-if="this.context.target.name === 'household' || this.context.edit">
|
||||
<div
|
||||
v-if="this.context.target.name === 'household' || this.context.edit"
|
||||
>
|
||||
<action-buttons :options="this.options" :defaultz="this.defaultz">
|
||||
<template #action>
|
||||
<button
|
||||
@@ -66,7 +75,9 @@
|
||||
name="button"
|
||||
:title="trans(getTextButton)"
|
||||
>
|
||||
<span v-if="displayTextButton">{{ trans(getTextButton) }}</span>
|
||||
<span v-if="displayTextButton">{{
|
||||
trans(getTextButton)
|
||||
}}</span>
|
||||
</button>
|
||||
</template>
|
||||
</action-buttons>
|
||||
@@ -88,7 +99,9 @@
|
||||
name="button"
|
||||
:title="trans(getTextButton)"
|
||||
>
|
||||
<span v-if="displayTextButton">{{ trans(getTextButton) }}</span>
|
||||
<span v-if="displayTextButton">{{
|
||||
trans(getTextButton)
|
||||
}}</span>
|
||||
</button>
|
||||
</template>
|
||||
</action-buttons>
|
||||
|
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div v-if="insideModal === false" class="loading">
|
||||
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw" />
|
||||
<i
|
||||
v-if="flag.loading"
|
||||
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
|
||||
/>
|
||||
<span class="sr-only">{{ $t("loading") }}</span>
|
||||
</div>
|
||||
|
||||
|
@@ -86,7 +86,10 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div id="waiting-screen">
|
||||
<div v-if="isPending && isFetching" class="alert alert-danger text-center">
|
||||
<div
|
||||
v-if="isPending && isFetching"
|
||||
class="alert alert-danger text-center"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
|
||||
|
@@ -1,156 +1,155 @@
|
||||
<template>
|
||||
<h2>{{ trans(MAIN_TITLE) }}</h2>
|
||||
<h2>{{ $t("main_title") }}</h2>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li v-for="tab in displayedTabs" :key="tab.key" class="nav-item">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="selectTab(tab.key)"
|
||||
:class="{ active: activeTab === 'MyCustoms' }"
|
||||
@click="selectTab('MyCustoms')"
|
||||
>
|
||||
<i v-if="tab.icon" :class="tab.icon" />
|
||||
<span v-else>{{ tab.label }}</span>
|
||||
<tab-counter v-if="tab.counter" :count="tab.counter()" />
|
||||
<i class="fa fa-dashboard" />
|
||||
</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" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: activeTab === 'MyAccompanyingCourses' }"
|
||||
@click="selectTab('MyAccompanyingCourses')"
|
||||
>
|
||||
{{ $t("my_accompanying_courses.tab") }}
|
||||
</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" />
|
||||
</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 + state.tasks.alert.count"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: activeTab === 'MyWorkflows' }"
|
||||
@click="selectTab('MyWorkflows')"
|
||||
>
|
||||
{{ $t("my_workflows.tab") }}
|
||||
<tab-counter
|
||||
:count="state.workflows.count + state.workflowsCc.count"
|
||||
/>
|
||||
</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="trans(LOADING)"
|
||||
:title="$t('loading')"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="my-4">
|
||||
<MyCustoms v-if="activeTab === HomepageTabs.MyCustoms" />
|
||||
<MyTickets v-else-if="activeTab === HomepageTabs.MyTickets" />
|
||||
<MyNotifications v-else-if="activeTab === HomepageTabs.MyNotifications" />
|
||||
<MyAccompanyingCourses
|
||||
v-else-if="activeTab === HomepageTabs.MyAccompanyingCourses"
|
||||
<my-customs v-if="activeTab === 'MyCustoms'" />
|
||||
<my-works v-else-if="activeTab === 'MyWorks'" />
|
||||
<my-evaluations v-else-if="activeTab === 'MyEvaluations'" />
|
||||
<my-tasks v-else-if="activeTab === 'MyTasks'" />
|
||||
<my-accompanying-courses
|
||||
v-else-if="activeTab === 'MyAccompanyingCourses'"
|
||||
/>
|
||||
<MyEvaluations v-else-if="activeTab === HomepageTabs.MyEvaluations" />
|
||||
<MyTasks v-else-if="activeTab === HomepageTabs.MyTasks" />
|
||||
<MyWorkflows v-else-if="activeTab === HomepageTabs.MyWorkflows" />
|
||||
<my-notifications v-else-if="activeTab === 'MyNotifications'" />
|
||||
<my-workflows v-else-if="activeTab === 'MyWorkflows'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
// Components
|
||||
import MyCustoms from "./MyCustoms.vue";
|
||||
import MyEvaluations from "./MyEvaluations.vue";
|
||||
import MyTasks from "./MyTasks.vue";
|
||||
import MyAccompanyingCourses from "./MyAccompanyingCourses.vue";
|
||||
import MyNotifications from "./MyNotifications.vue";
|
||||
<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 MyWorkflows from "./MyWorkflows.vue";
|
||||
import MyTickets from "./MyTickets.vue";
|
||||
import TabCounter from "./TabCounter.vue";
|
||||
import TabCounter from "./TabCounter";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
// Translations
|
||||
import {
|
||||
MAIN_TITLE,
|
||||
MY_TICKETS_TAB,
|
||||
MY_EVALUATIONS_TAB,
|
||||
MY_TASKS_TAB,
|
||||
MY_ACCOMPANYING_COURSES_TAB,
|
||||
MY_NOTIFICATIONS_TAB,
|
||||
MY_WORKFLOWS_TAB,
|
||||
LOADING,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
// Types
|
||||
import {
|
||||
HomepageConfig,
|
||||
HomepageTabs,
|
||||
TabDefinition,
|
||||
} from "ChillMainAssets/types";
|
||||
|
||||
const store = useStore();
|
||||
|
||||
defineExpose({ HomepageTabs });
|
||||
|
||||
const homepageConfig = ref<HomepageConfig>(JSON.parse(window.homepage_config));
|
||||
const state = computed(() => store.state.homepage);
|
||||
const ticketListState = computed(() => store.state.ticketList);
|
||||
|
||||
const tabDefinitions: TabDefinition[] = [
|
||||
{
|
||||
key: HomepageTabs.MyCustoms,
|
||||
label: "",
|
||||
icon: "fa fa-dashboard",
|
||||
counter: () => 0,
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
MyCustoms,
|
||||
MyWorks,
|
||||
MyEvaluations,
|
||||
MyTasks,
|
||||
MyWorkflows,
|
||||
MyAccompanyingCourses,
|
||||
MyNotifications,
|
||||
TabCounter,
|
||||
},
|
||||
{
|
||||
key: HomepageTabs.MyTickets,
|
||||
label: trans(MY_TICKETS_TAB),
|
||||
icon: null,
|
||||
counter: () => ticketListState.value?.count,
|
||||
data() {
|
||||
return {
|
||||
activeTab: "MyCustoms",
|
||||
};
|
||||
},
|
||||
{
|
||||
key: HomepageTabs.MyNotifications,
|
||||
label: trans(MY_NOTIFICATIONS_TAB),
|
||||
icon: null,
|
||||
counter: () => state.value?.notifications?.count,
|
||||
computed: {
|
||||
...mapState(["loading"]),
|
||||
// just to see all in devtool :
|
||||
...mapState({
|
||||
state: (state) => state,
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: HomepageTabs.MyAccompanyingCourses,
|
||||
label: trans(MY_ACCOMPANYING_COURSES_TAB),
|
||||
icon: null,
|
||||
counter: () => state.value?.accompanyingCourses?.count,
|
||||
},
|
||||
{
|
||||
key: HomepageTabs.MyEvaluations,
|
||||
label: trans(MY_EVALUATIONS_TAB),
|
||||
icon: null,
|
||||
counter: () => state.value?.evaluations?.count,
|
||||
},
|
||||
{
|
||||
key: HomepageTabs.MyTasks,
|
||||
label: trans(MY_TASKS_TAB),
|
||||
icon: null,
|
||||
counter: () =>
|
||||
state.value?.tasks?.warning?.count + state.value?.tasks?.alert?.count,
|
||||
},
|
||||
{
|
||||
key: HomepageTabs.MyWorkflows,
|
||||
label: trans(MY_WORKFLOWS_TAB),
|
||||
icon: null,
|
||||
counter: () =>
|
||||
state.value?.workflows?.count + state.value?.workflowsCc?.count,
|
||||
},
|
||||
];
|
||||
|
||||
const displayedTabs = computed(() => {
|
||||
// Always show MyCustoms first if present
|
||||
const tabs = [] as TabDefinition[];
|
||||
for (const tabEnum of homepageConfig.value.displayTabs) {
|
||||
const def = tabDefinitions.find(
|
||||
(t) => t.key === Number(HomepageTabs[tabEnum]),
|
||||
);
|
||||
if (def) tabs.push(def);
|
||||
methods: {
|
||||
selectTab(tab) {
|
||||
if (tab !== "MyCustoms") {
|
||||
this.$store.dispatch("getByTab", { tab: tab });
|
||||
}
|
||||
return tabs.filter(Boolean);
|
||||
});
|
||||
|
||||
const activeTab = ref(Number(HomepageTabs[homepageConfig.value.defaultTab]));
|
||||
|
||||
const loading = computed(() => store.state.loading);
|
||||
|
||||
function selectTab(tab: HomepageTabs) {
|
||||
if (tab !== HomepageTabs.MyCustoms) {
|
||||
store.dispatch("getByTab", { tab: tab });
|
||||
this.activeTab = tab;
|
||||
console.log(this.activeTab);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
for (const m of [
|
||||
"MyNotifications",
|
||||
"MyAccompanyingCourses",
|
||||
// 'MyWorks',
|
||||
"MyEvaluations",
|
||||
"MyTasks",
|
||||
"MyWorkflows",
|
||||
]) {
|
||||
this.$store.dispatch("getByTab", { tab: m, param: "countOnly=1" });
|
||||
}
|
||||
activeTab.value = tab;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
for (const tab of displayedTabs.value) {
|
||||
if (tab.key !== HomepageTabs.MyCustoms) {
|
||||
store.dispatch("getByTab", { tab: tab.key, param: "countOnly=1" });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@@ -25,9 +25,11 @@
|
||||
</template>
|
||||
<template #body>
|
||||
<p class="news-date">
|
||||
<time class="createdBy" datetime="{{item.startDate.datetime}}">{{
|
||||
$d(newsItemStartDate(), "text")
|
||||
}}</time>
|
||||
<time
|
||||
class="createdBy"
|
||||
datetime="{{item.startDate.datetime}}"
|
||||
>{{ $d(newsItemStartDate(), "text") }}</time
|
||||
>
|
||||
</p>
|
||||
<div v-html="convertMarkdownToHtml(item.content)"></div>
|
||||
</template>
|
||||
@@ -91,7 +93,10 @@ const truncateContent = (content: string): string => {
|
||||
|
||||
// Truncate if amount of lines are too many
|
||||
if (lines.length > props.maxLines && content.length < props.maxLength) {
|
||||
const truncatedContent = lines.slice(0, props.maxLines).join("\n").trim();
|
||||
const truncatedContent = lines
|
||||
.slice(0, props.maxLines)
|
||||
.join("\n")
|
||||
.trim();
|
||||
return truncatedContent + "...";
|
||||
}
|
||||
|
||||
@@ -120,7 +125,8 @@ const truncateContent = (content: string): string => {
|
||||
if (linkStartIndex !== -1) {
|
||||
const linkEndIndex = content.indexOf(")", linkStartIndex);
|
||||
const url = content.slice(linkStartIndex + 1, linkEndIndex);
|
||||
truncatedContent = truncatedContent.slice(0, linkStartIndex) + `(${url})`;
|
||||
truncatedContent =
|
||||
truncatedContent.slice(0, linkStartIndex) + `(${url})`;
|
||||
}
|
||||
|
||||
truncatedContent += "...";
|
||||
|
@@ -1,40 +1,47 @@
|
||||
<template>
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_ACCOMPANYING_COURSES_DESCRIPTION) }}
|
||||
{{ $t("my_accompanying_courses.description") }}
|
||||
</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">
|
||||
{{ trans(NO_DATA) }}
|
||||
</span>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{
|
||||
$t("no_data")
|
||||
}}</span>
|
||||
<tab-table v-else>
|
||||
<template #thead>
|
||||
<th scope="col">
|
||||
{{ trans(OPENING_DATE) }}
|
||||
{{ $t("opening_date") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(SOCIAL_ISSUES) }}
|
||||
{{ $t("social_issues") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(CONCERNED_PERSONS) }}
|
||||
{{ $t("concerned_persons") }}
|
||||
</th>
|
||||
<th scope="col" />
|
||||
<th scope="col" />
|
||||
</template>
|
||||
<template #tbody>
|
||||
<tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`">
|
||||
<td>{{ $d(new Date(c.openingDate.datetime), "short") }}</td>
|
||||
<tr
|
||||
v-for="(c, i) in accompanyingCourses.results"
|
||||
:key="`course-${i}`"
|
||||
>
|
||||
<td>{{ $d(c.openingDate.datetime, "short") }}</td>
|
||||
<td>
|
||||
<span
|
||||
v-for="(issue, index) in c.socialIssues"
|
||||
v-for="(i, index) in c.socialIssues"
|
||||
:key="index"
|
||||
class="chill-entity entity-social-issue"
|
||||
>
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ localizeString(issue.title) }}
|
||||
{{ localizeString(i.title) }}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="p in c.participations" class="me-1" :key="p.person.id">
|
||||
<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"
|
||||
@@ -45,16 +52,20 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="c.emergency" class="badge rounded-pill bg-danger me-1">{{
|
||||
trans(EMERGENCY)
|
||||
}}</span>
|
||||
<span v-if="c.confidential" class="badge rounded-pill bg-danger">{{
|
||||
trans(CONFIDENTIAL)
|
||||
}}</span>
|
||||
<span
|
||||
v-if="c.emergency"
|
||||
class="badge rounded-pill bg-danger me-1"
|
||||
>{{ $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)">
|
||||
{{ trans(SHOW_ENTITY, { entity: trans(THE_COURSE) }) }}
|
||||
{{ $t("show_entity", { entity: $t("the_course") }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -62,45 +73,36 @@
|
||||
</tab-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ComputedRef } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import TabTable from "./TabTable.vue";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
import { AccompanyingCourse } from "ChillPersonAssets/types";
|
||||
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import {
|
||||
MY_ACCOMPANYING_COURSES_DESCRIPTION,
|
||||
OPENING_DATE,
|
||||
SOCIAL_ISSUES,
|
||||
CONCERNED_PERSONS,
|
||||
SHOW_ENTITY,
|
||||
THE_COURSE,
|
||||
NO_DATA,
|
||||
EMERGENCY,
|
||||
CONFIDENTIAL,
|
||||
trans,
|
||||
} from "translator";
|
||||
const store = useStore();
|
||||
|
||||
const accompanyingCourses: ComputedRef<PaginationResponse<AccompanyingCourse>> =
|
||||
computed(() => store.state.homepage.accompanyingCourses);
|
||||
const isAccompanyingCoursesLoaded = computed(
|
||||
() => store.getters.isAccompanyingCoursesLoaded,
|
||||
);
|
||||
|
||||
const noResults = computed(() => {
|
||||
if (!isAccompanyingCoursesLoaded.value) {
|
||||
export default {
|
||||
name: "MyAccompanyingCourses",
|
||||
components: {
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["accompanyingCourses"]),
|
||||
...mapGetters(["isAccompanyingCoursesLoaded"]),
|
||||
noResults() {
|
||||
if (!this.isAccompanyingCoursesLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return accompanyingCourses.value.count === 0;
|
||||
return this.accompanyingCourses.count === 0;
|
||||
}
|
||||
});
|
||||
|
||||
function getUrl(c: { id: number }): string {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
localizeString,
|
||||
getUrl(c) {
|
||||
return `/fr/parcours/${c.id}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@@ -1,72 +1,95 @@
|
||||
<template>
|
||||
<span v-if="noResults" class="chill-no-data-statement">
|
||||
{{ trans(NO_DASHBOARD) }}
|
||||
</span>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{
|
||||
$t("no_dashboard")
|
||||
}}</span>
|
||||
<div v-else id="dashboards" class="container g-3">
|
||||
<div class="row">
|
||||
<div class="mbloc col-xs-12 col-sm-4">
|
||||
<div class="custom1">
|
||||
<ul class="list-unstyled">
|
||||
<li v-if="(counter.value?.notifications || 0) > 0">
|
||||
<span :class="counterClass">
|
||||
{{
|
||||
trans(COUNTER_UNREAD_NOTIFICATIONS, {
|
||||
n: counter.value?.notifications || 0,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<li v-if="counter.notifications > 0">
|
||||
<i18n-t
|
||||
keypath="counter.unread_notifications"
|
||||
tag="span"
|
||||
:class="counterClass"
|
||||
:plural="counter.notifications"
|
||||
>
|
||||
<template #n>
|
||||
<span>{{ counter.notifications }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="(counter.value?.accompanyingCourses || 0) > 0">
|
||||
<span :class="counterClass">
|
||||
{{
|
||||
trans(COUNTER_ASSIGNATED_COURSES, {
|
||||
n: counter.value?.accompanyingCourses || 0,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<li v-if="counter.accompanyingCourses > 0">
|
||||
<i18n-t
|
||||
keypath="counter.assignated_courses"
|
||||
tag="span"
|
||||
:class="counterClass"
|
||||
:plural="counter.accompanyingCourses"
|
||||
>
|
||||
<template #n>
|
||||
<span>{{
|
||||
counter.accompanyingCourses
|
||||
}}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="(counter.value?.works || 0) > 0">
|
||||
<span :class="counterClass">
|
||||
{{
|
||||
trans(COUNTER_ASSIGNATED_ACTIONS, {
|
||||
n: counter.value?.works || 0,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<li v-if="counter.works > 0">
|
||||
<i18n-t
|
||||
keypath="counter.assignated_actions"
|
||||
tag="span"
|
||||
:class="counterClass"
|
||||
:plural="counter.works"
|
||||
>
|
||||
<template #n>
|
||||
<span>{{ counter.works }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="(counter.value?.evaluations || 0) > 0">
|
||||
<span :class="counterClass">
|
||||
{{
|
||||
trans(COUNTER_ASSIGNATED_EVALUATIONS, {
|
||||
n: counter.value?.evaluations || 0,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<li v-if="counter.evaluations > 0">
|
||||
<i18n-t
|
||||
keypath="counter.assignated_evaluations"
|
||||
tag="span"
|
||||
:class="counterClass"
|
||||
:plural="counter.evaluations"
|
||||
>
|
||||
<template #n>
|
||||
<span>{{ counter.evaluations }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="(counter.value?.tasksAlert || 0) > 0">
|
||||
<span :class="counterClass">
|
||||
{{
|
||||
trans(COUNTER_ALERT_TASKS, {
|
||||
n: counter.value?.tasksAlert || 0,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<li v-if="counter.tasksAlert > 0">
|
||||
<i18n-t
|
||||
keypath="counter.alert_tasks"
|
||||
tag="span"
|
||||
:class="counterClass"
|
||||
:plural="counter.tasksAlert"
|
||||
>
|
||||
<template #n>
|
||||
<span>{{ counter.tasksAlert }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="(counter.value?.tasksWarning || 0) > 0">
|
||||
<span :class="counterClass">
|
||||
{{
|
||||
trans(COUNTER_WARNING_TASKS, {
|
||||
n: counter.value?.tasksWarning || 0,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<li v-if="counter.tasksWarning > 0">
|
||||
<i18n-t
|
||||
keypath="counter.warning_tasks"
|
||||
tag="span"
|
||||
:class="counterClass"
|
||||
:plural="counter.tasksWarning"
|
||||
>
|
||||
<template #n>
|
||||
<span>{{ counter.tasksWarning }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="hasDashboardItems">
|
||||
<template v-for="(dashboardItem, index) in dashboardItems" :key="index">
|
||||
<template v-if="this.hasDashboardItems">
|
||||
<template
|
||||
v-for="(dashboardItem, index) in this.dashboardItems"
|
||||
:key="index"
|
||||
>
|
||||
<div
|
||||
class="mbloc col-xs-12 col-sm-8 news"
|
||||
v-if="dashboardItem.type === 'news'"
|
||||
@@ -79,53 +102,44 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import News from "./DashboardWidgets/News.vue";
|
||||
import {
|
||||
NO_DASHBOARD,
|
||||
COUNTER_UNREAD_NOTIFICATIONS,
|
||||
COUNTER_ASSIGNATED_COURSES,
|
||||
COUNTER_ASSIGNATED_ACTIONS,
|
||||
COUNTER_ASSIGNATED_EVALUATIONS,
|
||||
COUNTER_ALERT_TASKS,
|
||||
COUNTER_WARNING_TASKS,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
interface MyCustom {
|
||||
id: number;
|
||||
type: string;
|
||||
metadata: Record<string, unknown>;
|
||||
userId: number;
|
||||
position: string;
|
||||
}
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const counter = computed(() => store.getters.counter);
|
||||
|
||||
const counterClass = { counter: true };
|
||||
|
||||
const dashboardItems = ref<MyCustom[]>([]);
|
||||
|
||||
const noResults = computed(() => false);
|
||||
|
||||
const hasDashboardItems = computed(() => dashboardItems.value.length > 0);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response: MyCustom[] = await makeFetch(
|
||||
"GET",
|
||||
"/api/1.0/main/dashboard-config-item.json",
|
||||
);
|
||||
dashboardItems.value = response;
|
||||
} catch (error) {
|
||||
export default {
|
||||
name: "MyCustoms",
|
||||
components: {
|
||||
News,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
counterClass: {
|
||||
counter: true, //hack to pass class 'counter' in i18n-t
|
||||
},
|
||||
dashboardItems: [],
|
||||
masonry: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["counter"]),
|
||||
noResults() {
|
||||
return false;
|
||||
},
|
||||
hasDashboardItems() {
|
||||
return this.dashboardItems.length > 0;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
makeFetch("GET", "/api/1.0/main/dashboard-config-item.json")
|
||||
.then((response) => {
|
||||
this.dashboardItems = response;
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -1,50 +1,50 @@
|
||||
<template>
|
||||
<div class="accompanying-course-work">
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_EVALUATIONS_DESCRIPTION) }}
|
||||
{{ $t("my_evaluations.description") }}
|
||||
</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">
|
||||
{{ trans(NO_DATA) }}
|
||||
</span>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{
|
||||
$t("no_data")
|
||||
}}</span>
|
||||
<tab-table v-else>
|
||||
<template #thead>
|
||||
<th scope="col">
|
||||
{{ trans(MAX_DATE) }}
|
||||
{{ $t("max_date") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(EVALUATION) }}
|
||||
{{ $t("evaluation") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(SOCIAL_ACTION) }}
|
||||
{{ $t("SocialAction") }}
|
||||
</th>
|
||||
<th scope="col" />
|
||||
</template>
|
||||
<template #tbody>
|
||||
<tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`">
|
||||
<tr
|
||||
v-for="(e, i) in evaluations.results"
|
||||
:key="`evaluation-${i}`"
|
||||
>
|
||||
<td>{{ $d(e.maxDate.datetime, "short") }}</td>
|
||||
<td>
|
||||
{{
|
||||
e.maxDate?.datetime
|
||||
? $d(new Date(e.maxDate.datetime), "short")
|
||||
: ""
|
||||
}}
|
||||
</td>
|
||||
<td>
|
||||
{{ localizeString(e.evaluation?.title ?? null) }}
|
||||
{{ localizeString(e.evaluation.title) }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="chill-entity entity-social-issue">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ e.accompanyingPeriodWork?.socialAction?.issue?.text ?? "" }}
|
||||
{{
|
||||
e.accompanyingPeriodWork.socialAction.issue
|
||||
.text
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
<h4 class="badge-title">
|
||||
<span class="title_label" />
|
||||
<span class="title_action">
|
||||
{{ e.accompanyingPeriodWork?.socialAction?.text ?? "" }}
|
||||
{{ e.accompanyingPeriodWork.socialAction.text }}
|
||||
</span>
|
||||
</h4>
|
||||
<span
|
||||
v-for="person in e.accompanyingPeriodWork?.persons ?? []"
|
||||
v-for="person in e.accompanyingPeriodWork.persons"
|
||||
class="me-1"
|
||||
:key="person.id"
|
||||
>
|
||||
@@ -58,25 +58,30 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical" role="group" aria-label="Actions">
|
||||
<div
|
||||
class="btn-group-vertical"
|
||||
role="group"
|
||||
aria-label="Actions"
|
||||
>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(e)">
|
||||
{{
|
||||
trans(SHOW_ENTITY, {
|
||||
entity: trans(THE_EVALUATION),
|
||||
$t("show_entity", {
|
||||
entity: $t("the_evaluation"),
|
||||
})
|
||||
}}
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-sm btn-show"
|
||||
:href="getUrl(e.accompanyingPeriodWork.accompanyingPeriod!)"
|
||||
v-if="
|
||||
e.accompanyingPeriodWork &&
|
||||
e.accompanyingPeriodWork.accompanyingPeriod
|
||||
:href="
|
||||
getUrl(
|
||||
e.accompanyingPeriodWork
|
||||
.accompanyingPeriod,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{
|
||||
trans(SHOW_ENTITY, {
|
||||
entity: trans(THE_COURSE),
|
||||
$t("show_entity", {
|
||||
entity: $t("the_course"),
|
||||
})
|
||||
}}
|
||||
</a>
|
||||
@@ -88,60 +93,36 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import TabTable from "./TabTable.vue";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
|
||||
import { localizeString } from "../../lib/localizationHelper/localizationHelper";
|
||||
|
||||
const store = useStore();
|
||||
|
||||
import type { ComputedRef } from "vue";
|
||||
import {
|
||||
AccompanyingPeriod,
|
||||
AccompanyingPeriodWorkEvaluation,
|
||||
AccompanyingPeriodWork,
|
||||
} from "ChillPersonAssets/types";
|
||||
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import {
|
||||
MY_EVALUATIONS_DESCRIPTION,
|
||||
MAX_DATE,
|
||||
EVALUATION,
|
||||
SHOW_ENTITY,
|
||||
THE_COURSE,
|
||||
THE_EVALUATION,
|
||||
SOCIAL_ACTION,
|
||||
NO_DATA,
|
||||
trans,
|
||||
} from "translator";
|
||||
const evaluations: ComputedRef<
|
||||
PaginationResponse<AccompanyingPeriodWorkEvaluation>
|
||||
> = computed(() => store.state.homepage.evaluations);
|
||||
const isEvaluationsLoaded = computed(() => store.getters.isEvaluationsLoaded);
|
||||
|
||||
const noResults = computed(() => {
|
||||
if (!isEvaluationsLoaded.value) {
|
||||
export default {
|
||||
name: "MyEvaluations",
|
||||
components: {
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["evaluations"]),
|
||||
...mapGetters(["isEvaluationsLoaded"]),
|
||||
noResults() {
|
||||
if (!this.isEvaluationsLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return evaluations.value.count === 0;
|
||||
return this.evaluations.count === 0;
|
||||
}
|
||||
});
|
||||
|
||||
function getUrl(
|
||||
e:
|
||||
| AccompanyingPeriodWorkEvaluation
|
||||
| AccompanyingPeriod
|
||||
| AccompanyingPeriodWork,
|
||||
): string {
|
||||
if (!e) {
|
||||
throw "entity is undefined";
|
||||
}
|
||||
|
||||
if ("type" in e && typeof e.type === "string") {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
localizeString,
|
||||
getUrl(e) {
|
||||
switch (e.type) {
|
||||
case "accompanying_period_work_evaluation":
|
||||
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork?.id}/edit#evaluations`;
|
||||
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":
|
||||
@@ -149,9 +130,9 @@ function getUrl(
|
||||
default:
|
||||
throw "entity type unknown";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_NOTIFICATIONS_DESCRIPTION) }}
|
||||
{{ $t("my_notifications.description") }}
|
||||
</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">
|
||||
{{ trans(NO_DATA) }}
|
||||
</span>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{
|
||||
$t("no_data")
|
||||
}}</span>
|
||||
<tab-table v-else>
|
||||
<template #thead>
|
||||
<th scope="col">
|
||||
{{ trans(DATE) }}
|
||||
{{ $t("Date") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(SUBJECT) }}
|
||||
{{ $t("Subject") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(FROM) }}
|
||||
{{ $t("From") }}
|
||||
</th>
|
||||
<th scope="col" />
|
||||
</template>
|
||||
<template #tbody>
|
||||
<tr v-for="(n, i) in notifications.results" :key="`notify-${i}`">
|
||||
<td>{{ $d(new Date(n.date.datetime), "long") }}</td>
|
||||
<td>{{ $d(n.date.datetime, "long") }}</td>
|
||||
<td>
|
||||
<span class="unread">
|
||||
<i class="fa fa-envelope-o" />
|
||||
@@ -31,11 +31,11 @@
|
||||
{{ n.sender.text }}
|
||||
</td>
|
||||
<td v-else>
|
||||
{{ trans(AUTOMATIC_NOTIFICATION) }}
|
||||
{{ $t("automatic_notification") }}
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getEntityUrl(n)">
|
||||
{{ trans(SHOW_ENTITY, { entity: getEntityName(n) }) }}
|
||||
{{ $t("show_entity", { entity: getEntityName(n) }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -43,67 +43,48 @@
|
||||
</tab-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ComputedRef } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import TabTable from "./TabTable.vue";
|
||||
import { Notification } from "ChillPersonAssets/types";
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import { appMessages } from "ChillMainAssets/vuejs/HomepageWidget/js/i18n";
|
||||
|
||||
import {
|
||||
MY_NOTIFICATIONS_DESCRIPTION,
|
||||
DATE,
|
||||
FROM,
|
||||
SUBJECT,
|
||||
SHOW_ENTITY,
|
||||
THE_ACTIVITY,
|
||||
THE_COURSE,
|
||||
THE_ACTION,
|
||||
THE_EVALUATION_DOCUMENT,
|
||||
THE_WORKFLOW,
|
||||
NO_DATA,
|
||||
AUTOMATIC_NOTIFICATION,
|
||||
trans,
|
||||
} from "translator";
|
||||
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
|
||||
const store = useStore();
|
||||
|
||||
const notifications: ComputedRef<PaginationResponse<Notification>> = computed(
|
||||
() => store.state.homepage.notifications,
|
||||
);
|
||||
const isNotificationsLoaded = computed(
|
||||
() => store.getters.isNotificationsLoaded,
|
||||
);
|
||||
|
||||
const noResults = computed(() => {
|
||||
if (!isNotificationsLoaded.value) {
|
||||
export default {
|
||||
name: "MyNotifications",
|
||||
components: {
|
||||
TabTable,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["notifications"]),
|
||||
...mapGetters(["isNotificationsLoaded"]),
|
||||
noResults() {
|
||||
if (!this.isNotificationsLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return notifications.value.count === 0;
|
||||
return this.notifications.count === 0;
|
||||
}
|
||||
});
|
||||
|
||||
function getNotificationUrl(n: Notification): string {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getNotificationUrl(n) {
|
||||
return `/fr/notification/${n.id}/show`;
|
||||
}
|
||||
|
||||
function getEntityName(n: Notification): string {
|
||||
},
|
||||
getEntityName(n) {
|
||||
switch (n.relatedEntityClass) {
|
||||
case "Chill\\ActivityBundle\\Entity\\Activity":
|
||||
return trans(THE_ACTIVITY);
|
||||
return appMessages.fr.the_activity;
|
||||
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod":
|
||||
return trans(THE_COURSE);
|
||||
return appMessages.fr.the_course;
|
||||
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork":
|
||||
return trans(THE_ACTION);
|
||||
return appMessages.fr.the_action;
|
||||
case "Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument":
|
||||
return trans(THE_EVALUATION_DOCUMENT);
|
||||
return appMessages.fr.the_evaluation_document;
|
||||
case "Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow":
|
||||
return trans(THE_WORKFLOW);
|
||||
return appMessages.fr.the_workflow;
|
||||
default:
|
||||
throw "notification type unknown";
|
||||
}
|
||||
}
|
||||
|
||||
function getEntityUrl(n: Notification): string {
|
||||
},
|
||||
getEntityUrl(n) {
|
||||
switch (n.relatedEntityClass) {
|
||||
case "Chill\\ActivityBundle\\Entity\\Activity":
|
||||
return `/fr/activity/${n.relatedEntityId}/show`;
|
||||
@@ -118,7 +99,9 @@ function getEntityUrl(n: Notification): string {
|
||||
default:
|
||||
throw "notification type unknown";
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_TASKS_DESCRIPTION_WARNING) }}
|
||||
{{ $t("my_tasks.description_warning") }}
|
||||
</div>
|
||||
<span v-if="noResultsAlert" class="chill-no-data-statement">
|
||||
{{ trans(NO_DATA) }}
|
||||
</span>
|
||||
<span v-if="noResultsAlert" class="chill-no-data-statement">{{
|
||||
$t("no_data")
|
||||
}}</span>
|
||||
<tab-table v-else>
|
||||
<template #thead>
|
||||
<th scope="col">
|
||||
{{ trans(WARNING_DATE) }}
|
||||
{{ $t("warning_date") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(MAX_DATE) }}
|
||||
{{ $t("max_date") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(TASK) }}
|
||||
{{ $t("task") }}
|
||||
</th>
|
||||
<th scope="col" />
|
||||
</template>
|
||||
<template #tbody>
|
||||
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
|
||||
<td v-if="t.warningDate !== null">
|
||||
{{ $d(new Date(t.warningDate.datetime), "short") }}
|
||||
<td v-if="null !== t.warningDate">
|
||||
{{ $d(t.warningDate.datetime, "short") }}
|
||||
</td>
|
||||
<td v-else />
|
||||
<td>
|
||||
<span class="outdated">{{
|
||||
$d(new Date(t.endDate.datetime), "short")
|
||||
$d(t.endDate.datetime, "short")
|
||||
}}</span>
|
||||
</td>
|
||||
<td>{{ t.title }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(t)">
|
||||
{{ trans(SHOW_ENTITY, { entity: trans(THE_TASK) }) }}
|
||||
{{ $t("show_entity", { entity: $t("the_task") }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -40,36 +40,39 @@
|
||||
</tab-table>
|
||||
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_TASKS_DESCRIPTION_ALERT) }}
|
||||
{{ $t("my_tasks.description_alert") }}
|
||||
</div>
|
||||
<span v-if="noResultsWarning" class="chill-no-data-statement">
|
||||
{{ trans(NO_DATA) }}
|
||||
</span>
|
||||
<span v-if="noResultsWarning" class="chill-no-data-statement">{{
|
||||
$t("no_data")
|
||||
}}</span>
|
||||
<tab-table v-else>
|
||||
<template #thead>
|
||||
<th scope="col">
|
||||
{{ trans(WARNING_DATE) }}
|
||||
{{ $t("warning_date") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(MAX_DATE) }}
|
||||
{{ $t("max_date") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(TASK) }}
|
||||
{{ $t("task") }}
|
||||
</th>
|
||||
<th scope="col" />
|
||||
</template>
|
||||
<template #tbody>
|
||||
<tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`">
|
||||
<tr
|
||||
v-for="(t, i) in tasks.warning.results"
|
||||
:key="`task-warning-${i}`"
|
||||
>
|
||||
<td>
|
||||
<span class="outdated">{{
|
||||
$d(new Date(t.warningDate.datetime), "short")
|
||||
$d(t.warningDate.datetime, "short")
|
||||
}}</span>
|
||||
</td>
|
||||
<td>{{ $d(new Date(t.endDate.datetime), "short") }}</td>
|
||||
<td>{{ $d(t.endDate.datetime, "short") }}</td>
|
||||
<td>{{ t.title }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(t)">
|
||||
{{ trans(SHOW_ENTITY, { entity: trans(THE_TASK) }) }}
|
||||
{{ $t("show_entity", { entity: $t("the_task") }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -77,51 +80,39 @@
|
||||
</tab-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ComputedRef } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import TabTable from "./TabTable.vue";
|
||||
import {
|
||||
MY_TASKS_DESCRIPTION_ALERT,
|
||||
MY_TASKS_DESCRIPTION_WARNING,
|
||||
MAX_DATE,
|
||||
WARNING_DATE,
|
||||
TASK,
|
||||
SHOW_ENTITY,
|
||||
THE_TASK,
|
||||
NO_DATA,
|
||||
trans,
|
||||
} from "translator";
|
||||
import { TasksState } from "./store/modules/homepage";
|
||||
import { Alert, Warning } from "ChillPersonAssets/types";
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const tasks: ComputedRef<TasksState> = computed(
|
||||
() => store.state.homepage.tasks,
|
||||
);
|
||||
const isTasksWarningLoaded = computed(() => store.getters.isTasksWarningLoaded);
|
||||
const isTasksAlertLoaded = computed(() => store.getters.isTasksAlertLoaded);
|
||||
|
||||
const noResultsAlert = computed(() => {
|
||||
if (!isTasksAlertLoaded.value) {
|
||||
export default {
|
||||
name: "MyTasks",
|
||||
components: {
|
||||
TabTable,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["tasks"]),
|
||||
...mapGetters(["isTasksWarningLoaded", "isTasksAlertLoaded"]),
|
||||
noResultsAlert() {
|
||||
if (!this.isTasksAlertLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return tasks.value.alert.count === 0;
|
||||
return this.tasks.alert.count === 0;
|
||||
}
|
||||
});
|
||||
|
||||
const noResultsWarning = computed(() => {
|
||||
if (!isTasksWarningLoaded.value) {
|
||||
},
|
||||
noResultsWarning() {
|
||||
if (!this.isTasksWarningLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return tasks.value.warning.count === 0;
|
||||
return this.tasks.warning.count === 0;
|
||||
}
|
||||
});
|
||||
|
||||
function getUrl(t: Warning | Alert): string {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getUrl(t) {
|
||||
return `/fr/task/single-task/${t.id}/show`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<div class="col-12">
|
||||
<!-- Loading state -->
|
||||
<div
|
||||
v-if="isTicketLoading"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
style="height: 200px"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div class="spinner-border mb-3" role="status">
|
||||
<span class="visually-hidden">{{
|
||||
trans(CHILL_TICKET_LIST_LOADING_TICKET)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
{{ trans(CHILL_TICKET_LIST_LOADING_TICKET) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ticket list -->
|
||||
<ticket-list-component
|
||||
v-else
|
||||
:tickets="ticketList"
|
||||
title=""
|
||||
:hasMoreTickets="pagination.next !== null"
|
||||
@fetchNextPage="fetchNextPage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
// Components
|
||||
import TicketListComponent from "../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketList/components/TicketListComponent.vue";
|
||||
|
||||
// Types
|
||||
import { Pagination } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { TicketSimple } from "src/Bundle/ChillTicketBundle/src/Resources/public/types";
|
||||
|
||||
// Translation
|
||||
import { CHILL_TICKET_LIST_LOADING_TICKET, trans } from "translator";
|
||||
|
||||
const store = useStore();
|
||||
const pagination = computed(() => store.getters.getPagination as Pagination);
|
||||
|
||||
const ticketList = computed(
|
||||
() => store.getters.getTicketList as TicketSimple[],
|
||||
);
|
||||
const isTicketLoading = computed(() => store.getters.isTicketLoading);
|
||||
|
||||
const fetchNextPage = async () => {
|
||||
if (pagination.value.next) {
|
||||
await store.dispatch("fetchTicketListByUrl", pagination.value.next);
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,27 +1,26 @@
|
||||
<template>
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_WORKFLOWS_DESCRIPTION) }}
|
||||
{{ $t("my_workflows.description") }}
|
||||
</div>
|
||||
<my-workflows-table :workflows="workflows" />
|
||||
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_WORKFLOWS_DESCRIPTION_CC) }}
|
||||
{{ $t("my_workflows.description_cc") }}
|
||||
</div>
|
||||
<my-workflows-table :workflows="workflowsCc" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import MyWorkflowsTable from "./MyWorkflowsTable.vue";
|
||||
import {
|
||||
MY_WORKFLOWS_DESCRIPTION,
|
||||
MY_WORKFLOWS_DESCRIPTION_CC,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const workflows = computed(() => store.state.homepage.workflows);
|
||||
const workflowsCc = computed(() => store.state.homepage.workflowsCc);
|
||||
export default {
|
||||
name: "MyWorkflows",
|
||||
components: {
|
||||
MyWorkflowsTable,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["workflows", "workflowsCc"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<span v-if="hasNoResults" class="chill-no-data-statement">
|
||||
{{ trans(NO_DATA) }}
|
||||
</span>
|
||||
<span v-if="hasNoResults(workflows)" class="chill-no-data-statement">{{
|
||||
$t("no_data")
|
||||
}}</span>
|
||||
<tab-table v-else>
|
||||
<template #thead>
|
||||
<th scope="col">
|
||||
{{ trans(OBJECT_WORKFLOW) }}
|
||||
{{ $t("Object_workflow") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(STEP) }}
|
||||
{{ $t("Step") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(CONCERNED_USERS) }}
|
||||
{{ $t("concerned_users") }}
|
||||
</th>
|
||||
<th scope="col" />
|
||||
</template>
|
||||
@@ -23,13 +23,15 @@
|
||||
<td>
|
||||
<div class="workflow">
|
||||
<div class="breadcrumb">
|
||||
<i class="fa fa-circle me-1 text-chill-yellow mx-2" />
|
||||
<i
|
||||
class="fa fa-circle me-1 text-chill-yellow mx-2"
|
||||
/>
|
||||
<span class="mx-2">{{ getStep(w) }}</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="w.isOnHoldAtCurrentStep"
|
||||
class="badge bg-success rounded-pill"
|
||||
>{{ trans(ON_HOLD) }}</span
|
||||
>{{ $t("on_hold") }}</span
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
@@ -46,7 +48,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(w)">
|
||||
{{ trans(SHOW_ENTITY, { entity: trans(THE_WORKFLOW) }) }}
|
||||
{{ $t("show_entity", { entity: $t("the_workflow") }) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -54,48 +56,38 @@
|
||||
</tab-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import TabTable from "./TabTable.vue";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
|
||||
import { Workflow } from "ChillPersonAssets/types";
|
||||
import {
|
||||
CONCERNED_USERS,
|
||||
OBJECT_WORKFLOW,
|
||||
ON_HOLD,
|
||||
SHOW_ENTITY,
|
||||
THE_WORKFLOW,
|
||||
NO_DATA,
|
||||
STEP,
|
||||
trans,
|
||||
} from "translator";
|
||||
import { PaginationResponse } from "ChillMainAssets/lib/api/apiMethods";
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
|
||||
|
||||
const props = defineProps<{
|
||||
workflows: PaginationResponse<Workflow>;
|
||||
}>();
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const isWorkflowsLoaded = computed(() => store.getters.isWorkflowsLoaded);
|
||||
|
||||
const hasNoResults = computed(() => {
|
||||
if (!isWorkflowsLoaded.value) {
|
||||
export default {
|
||||
name: "MyWorkflows",
|
||||
components: {
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
props: ["workflows"],
|
||||
computed: {
|
||||
...mapGetters(["isWorkflowsLoaded"]),
|
||||
},
|
||||
methods: {
|
||||
hasNoResults(workflows) {
|
||||
if (!this.isWorkflowsLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return props.workflows.count === 0;
|
||||
return workflows.count === 0;
|
||||
}
|
||||
});
|
||||
|
||||
function getUrl(w: Workflow): string {
|
||||
},
|
||||
getUrl(w) {
|
||||
return `/fr/main/workflow/${w.id}/show`;
|
||||
}
|
||||
|
||||
function getStep(w: Workflow): string {
|
||||
},
|
||||
getStep(w) {
|
||||
const lastStep = w.steps.length - 1;
|
||||
return w.steps[lastStep].currentStep.text;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@@ -1,26 +1,27 @@
|
||||
// CURRENTLY NOT IN USE
|
||||
<template>
|
||||
<div class="accompanying-course-work">
|
||||
<div class="alert alert-light">
|
||||
{{ trans(MY_WORKS_DESCRIPTION) }}
|
||||
{{ $t("my_works.description") }}
|
||||
</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">
|
||||
{{ trans(NO_DATA) }}
|
||||
</span>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{
|
||||
$t("no_data")
|
||||
}}</span>
|
||||
<tab-table v-else>
|
||||
<template #thead>
|
||||
<th scope="col">
|
||||
{{ trans(START_DATE) }}
|
||||
{{ $t("StartDate") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(SOCIAL_ACTION) }}
|
||||
{{ $t("SocialAction") }}
|
||||
</th>
|
||||
<th scope="col">
|
||||
{{ trans(CONCERNED_PERSONS) }}
|
||||
{{ $t("concerned_persons") }}
|
||||
</th>
|
||||
<th scope="col" />
|
||||
</template>
|
||||
<template #tbody>
|
||||
<tr v-for="(w, i) in works.value.results" :key="`works-${i}`">
|
||||
<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">
|
||||
@@ -36,7 +37,11 @@
|
||||
</h4>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="person in w.persons" class="me-1" :key="person.id">
|
||||
<span
|
||||
v-for="person in w.persons"
|
||||
class="me-1"
|
||||
:key="person.id"
|
||||
>
|
||||
<on-the-fly
|
||||
:type="person.type"
|
||||
:id="person.id"
|
||||
@@ -47,11 +52,15 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical" role="group" aria-label="Actions">
|
||||
<div
|
||||
class="btn-group-vertical"
|
||||
role="group"
|
||||
aria-label="Actions"
|
||||
>
|
||||
<a class="btn btn-sm btn-update" :href="getUrl(w)">
|
||||
{{
|
||||
trans(SHOW_ENTITY, {
|
||||
entity: trans(THE_ACTION),
|
||||
$t("show_entity", {
|
||||
entity: $t("the_action"),
|
||||
})
|
||||
}}
|
||||
</a>
|
||||
@@ -60,8 +69,8 @@
|
||||
:href="getUrl(w.accompanyingPeriod)"
|
||||
>
|
||||
{{
|
||||
trans(SHOW_ENTITY, {
|
||||
entity: trans(THE_COURSE),
|
||||
$t("show_entity", {
|
||||
entity: $t("the_course"),
|
||||
})
|
||||
}}
|
||||
</a>
|
||||
@@ -73,38 +82,30 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import TabTable from "./TabTable.vue";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
|
||||
import {
|
||||
MY_WORKS_DESCRIPTION,
|
||||
CONCERNED_PERSONS,
|
||||
SHOW_ENTITY,
|
||||
THE_COURSE,
|
||||
THE_ACTION,
|
||||
SOCIAL_ACTION,
|
||||
START_DATE,
|
||||
NO_DATA,
|
||||
trans,
|
||||
} from "translator";
|
||||
import { Workflow } from "ChillPersonAssets/types";
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly";
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const works = computed(() => store.state.homepage.works);
|
||||
const isWorksLoaded = computed(() => store.getters.isWorksLoaded);
|
||||
|
||||
const noResults = computed(() => {
|
||||
if (!isWorksLoaded.value) {
|
||||
export default {
|
||||
name: "MyWorks",
|
||||
components: {
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["works"]),
|
||||
...mapGetters(["isWorksLoaded"]),
|
||||
noResults() {
|
||||
if (!this.isWorksLoaded) {
|
||||
return false;
|
||||
} else {
|
||||
return works.value.count === 0;
|
||||
return this.works.count === 0;
|
||||
}
|
||||
});
|
||||
|
||||
function getUrl(e: Workflow): string {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getUrl(e) {
|
||||
switch (e.type) {
|
||||
case "accompanying_period_work":
|
||||
return `/fr/person/accompanying-period/work/${e.id}/edit`;
|
||||
@@ -113,7 +114,9 @@ function getUrl(e: Workflow): string {
|
||||
default:
|
||||
throw "entity type unknown";
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<span v-if="isCounterAvailable" class="badge rounded-pill bg-danger mx-2">
|
||||
<span v-if="isCounterAvailable" class="badge rounded-pill bg-danger">
|
||||
{{ count }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
count: number;
|
||||
}>();
|
||||
|
||||
const isCounterAvailable = computed(
|
||||
() => typeof props.count !== "undefined" && props.count > 0,
|
||||
);
|
||||
<script>
|
||||
export default {
|
||||
name: "TabCounter",
|
||||
props: ["count"],
|
||||
computed: {
|
||||
isCounterAvailable() {
|
||||
return typeof this.count !== "undefined" && this.count > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@@ -11,8 +11,11 @@
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// Pas de props à définir, composant slot simple
|
||||
<script>
|
||||
export default {
|
||||
name: "TabTable",
|
||||
props: [],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
@@ -0,0 +1,89 @@
|
||||
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 nouveaux parcours",
|
||||
description:
|
||||
"Liste des parcours d'accompagnement que l'on vient de m'attribuer depuis moins de 15 jours.",
|
||||
},
|
||||
my_notifications: {
|
||||
tab: "Mes nouvelles notifications",
|
||||
description: "Liste des notifications reçues et non lues.",
|
||||
},
|
||||
my_workflows: {
|
||||
tab: "Mes workflows",
|
||||
description: "Liste des workflows en attente d'une action.",
|
||||
description_cc: "Liste des workflows dont je suis en copie.",
|
||||
},
|
||||
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é à",
|
||||
Step: "Étape",
|
||||
concerned_users: "Usagers concernés",
|
||||
Object_workflow: "Objet du workflow",
|
||||
on_hold: "En attente",
|
||||
show_entity: "Voir {entity}",
|
||||
the_activity: "l'échange",
|
||||
the_course: "le parcours",
|
||||
the_action: "l'action",
|
||||
the_evaluation: "l'évaluation",
|
||||
the_evaluation_document: "le document",
|
||||
the_task: "la tâche",
|
||||
the_workflow: "le workflow",
|
||||
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",
|
||||
},
|
||||
emergency: "Urgent",
|
||||
confidential: "Confidentiel",
|
||||
automatic_notification: "Notification automatique",
|
||||
widget: {
|
||||
news: {
|
||||
title: "Actualités",
|
||||
readMore: "Lire la suite",
|
||||
date: "Date",
|
||||
none: "Aucune actualité",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(appMessages.fr);
|
||||
|
||||
export { appMessages };
|
@@ -1,103 +1,90 @@
|
||||
import "es6-promise/auto";
|
||||
import { Module } from "vuex";
|
||||
import {
|
||||
makeFetch,
|
||||
PaginationResponse,
|
||||
} from "ChillMainAssets/lib/api/apiMethods";
|
||||
import {
|
||||
AccompanyingCourse,
|
||||
Alert,
|
||||
Evaluation,
|
||||
Warning,
|
||||
Workflow,
|
||||
WorflowCc,
|
||||
} from "ChillPersonAssets/types";
|
||||
import { RootState } from "..";
|
||||
import { HomepageTabs } from "ChillMainAssets/types";
|
||||
import { createStore } from "vuex";
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
|
||||
export interface TasksState {
|
||||
warning: PaginationResponse<Warning>;
|
||||
alert: PaginationResponse<Alert>;
|
||||
}
|
||||
const debug = process.env.NODE_ENV !== "production";
|
||||
|
||||
export interface State {
|
||||
evaluations: PaginationResponse<Evaluation>;
|
||||
tasks: TasksState;
|
||||
accompanyingCourses: PaginationResponse<AccompanyingCourse>;
|
||||
notifications: PaginationResponse<Notification>;
|
||||
workflows: PaginationResponse<Workflow>;
|
||||
workflowsCc: PaginationResponse<WorflowCc>;
|
||||
errorMsg: unknown[];
|
||||
loading: boolean;
|
||||
ticketsLoading: boolean;
|
||||
}
|
||||
const isEmpty = (obj) => {
|
||||
return (
|
||||
obj &&
|
||||
Object.keys(obj).length <= 1 &&
|
||||
Object.getPrototypeOf(obj) === Object.prototype
|
||||
);
|
||||
};
|
||||
|
||||
export const moduleHomepage: Module<State, RootState> = {
|
||||
const store = createStore({
|
||||
strict: debug,
|
||||
state: {
|
||||
evaluations: {
|
||||
count: 0,
|
||||
} as PaginationResponse<Evaluation>,
|
||||
// works: {},
|
||||
evaluations: {},
|
||||
tasks: {
|
||||
warning: {
|
||||
count: 0,
|
||||
} as PaginationResponse<Warning>,
|
||||
alert: {
|
||||
count: 0,
|
||||
} as PaginationResponse<Alert>,
|
||||
warning: {},
|
||||
alert: {},
|
||||
},
|
||||
accompanyingCourses: {
|
||||
count: 0,
|
||||
} as PaginationResponse<AccompanyingCourse>,
|
||||
notifications: {
|
||||
count: 0,
|
||||
} as PaginationResponse<Notification>,
|
||||
workflows: {
|
||||
count: 0,
|
||||
} as PaginationResponse<Workflow>,
|
||||
workflowsCc: {
|
||||
count: 0,
|
||||
} as PaginationResponse<WorflowCc>,
|
||||
ticketsLoading: false,
|
||||
accompanyingCourses: {},
|
||||
notifications: {},
|
||||
workflows: {},
|
||||
workflowsCc: {},
|
||||
errorMsg: [],
|
||||
loading: false,
|
||||
},
|
||||
getters: {
|
||||
isTicketLoading: (state) => {
|
||||
return state.ticketsLoading;
|
||||
},
|
||||
// isWorksLoaded(state) {
|
||||
// return !isEmpty(state.works);
|
||||
// },
|
||||
isEvaluationsLoaded(state) {
|
||||
return Array.isArray(state.evaluations.results);
|
||||
return !isEmpty(state.evaluations);
|
||||
},
|
||||
isTasksWarningLoaded(state) {
|
||||
return Array.isArray(state.tasks.warning.results);
|
||||
return !isEmpty(state.tasks.warning);
|
||||
},
|
||||
isTasksAlertLoaded(state) {
|
||||
return Array.isArray(state.tasks.alert.results);
|
||||
return !isEmpty(state.tasks.alert);
|
||||
},
|
||||
isAccompanyingCoursesLoaded(state) {
|
||||
return Array.isArray(state.accompanyingCourses.results);
|
||||
return !isEmpty(state.accompanyingCourses);
|
||||
},
|
||||
isNotificationsLoaded(state) {
|
||||
return Array.isArray(state.notifications.results);
|
||||
return !isEmpty(state.notifications);
|
||||
},
|
||||
isWorkflowsLoaded(state) {
|
||||
return Array.isArray(state.workflows.results);
|
||||
return !isEmpty(state.workflows);
|
||||
},
|
||||
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,
|
||||
workflows: state.workflows.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;
|
||||
},
|
||||
addWorkflows(state, workflows) {
|
||||
@@ -109,30 +96,30 @@ export const moduleHomepage: Module<State, RootState> = {
|
||||
setLoading(state, bool) {
|
||||
state.loading = bool;
|
||||
},
|
||||
setTicketsLoading(state, bool) {
|
||||
state.ticketsLoading = bool;
|
||||
},
|
||||
catchError(state, error) {
|
||||
state.errorMsg.push(error);
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async getByTab({ commit, getters, dispatch }, { tab, param }) {
|
||||
getByTab({ commit, getters }, { tab, param }) {
|
||||
switch (tab) {
|
||||
case HomepageTabs.MyTickets:
|
||||
if (!getters.isTicketsLoaded) {
|
||||
commit("setTicketsLoading", true);
|
||||
// Utilise l'action du module ticket_list
|
||||
await dispatch(
|
||||
"fetchTicketList",
|
||||
{ byAddresseeToMe: true, byCurrentState: "open" },
|
||||
{ root: true },
|
||||
);
|
||||
|
||||
commit("setTicketsLoading", false);
|
||||
}
|
||||
break;
|
||||
case HomepageTabs.MyEvaluations:
|
||||
// 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}`;
|
||||
@@ -147,7 +134,7 @@ export const moduleHomepage: Module<State, RootState> = {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case HomepageTabs.MyTasks:
|
||||
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}`,
|
||||
@@ -172,7 +159,7 @@ export const moduleHomepage: Module<State, RootState> = {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case HomepageTabs.MyAccompanyingCourses:
|
||||
case "MyAccompanyingCourses":
|
||||
if (!getters.isAccompanyingCoursesLoaded) {
|
||||
commit("setLoading", true);
|
||||
const url = `/api/1.0/person/accompanying-course/list/by-recent-attributions${"?" + param}`;
|
||||
@@ -187,12 +174,13 @@ export const moduleHomepage: Module<State, RootState> = {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case HomepageTabs.MyNotifications:
|
||||
case "MyNotifications":
|
||||
if (!getters.isNotificationsLoaded) {
|
||||
commit("setLoading", true);
|
||||
const url = `/api/1.0/main/notification/my/unread${"?" + param}`;
|
||||
makeFetch("GET", url)
|
||||
.then((response) => {
|
||||
console.log("notifications", response);
|
||||
commit("addNotifications", response);
|
||||
commit("setLoading", false);
|
||||
})
|
||||
@@ -202,7 +190,7 @@ export const moduleHomepage: Module<State, RootState> = {
|
||||
});
|
||||
}
|
||||
break;
|
||||
case HomepageTabs.MyWorkflows:
|
||||
case "MyWorkflows":
|
||||
if (!getters.isWorflowsLoaded) {
|
||||
commit("setLoading", true);
|
||||
makeFetch("GET", "/api/1.0/main/workflow/my")
|
||||
@@ -229,4 +217,6 @@ export const moduleHomepage: Module<State, RootState> = {
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export { store };
|
@@ -1,55 +0,0 @@
|
||||
import { createStore } from "vuex";
|
||||
import { State as HomepageStates, moduleHomepage } from "./modules/homepage";
|
||||
|
||||
import {
|
||||
State as TicketListStates,
|
||||
moduleTicketList,
|
||||
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket_list";
|
||||
import {
|
||||
State as TicketStates,
|
||||
moduleTicket,
|
||||
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket";
|
||||
import {
|
||||
State as CommentStates,
|
||||
moduleComment,
|
||||
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/comment";
|
||||
import {
|
||||
moduleMotive,
|
||||
State as MotiveStates,
|
||||
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/motive";
|
||||
import {
|
||||
State as AddresseeStates,
|
||||
moduleAddressee,
|
||||
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/addressee";
|
||||
import {
|
||||
modulePersons,
|
||||
State as PersonsState,
|
||||
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/persons";
|
||||
|
||||
import {
|
||||
moduleUser,
|
||||
State as UserState,
|
||||
} from "../../../../../../ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/user";
|
||||
|
||||
export interface RootState {
|
||||
homepage: HomepageStates;
|
||||
motive: MotiveStates;
|
||||
ticket: TicketStates;
|
||||
comment: CommentStates;
|
||||
addressee: AddresseeStates;
|
||||
persons: PersonsState;
|
||||
ticketList: TicketListStates;
|
||||
user: UserState;
|
||||
}
|
||||
export const store = createStore<RootState>({
|
||||
modules: {
|
||||
homepage: moduleHomepage,
|
||||
motive: moduleMotive,
|
||||
ticket: moduleTicket,
|
||||
comment: moduleComment,
|
||||
addressee: moduleAddressee,
|
||||
person: modulePersons,
|
||||
ticketList: moduleTicketList,
|
||||
user: moduleUser,
|
||||
},
|
||||
});
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<ul class="nav nav-tabs">
|
||||
<li v-if="containsPerson" class="nav-item">
|
||||
<li v-if="allowedTypes.includes('person')" class="nav-item">
|
||||
<a class="nav-link" :class="{ active: isActive('person') }">
|
||||
<label for="person">
|
||||
<input
|
||||
@@ -10,11 +10,11 @@
|
||||
v-model="radioType"
|
||||
value="person"
|
||||
/>
|
||||
{{ trans(ONTHEFLY_CREATE_PERSON) }}
|
||||
{{ $t("onthefly.create.person") }}
|
||||
</label>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="containsThirdParty" class="nav-item">
|
||||
<li v-if="allowedTypes.includes('thirdparty')" class="nav-item">
|
||||
<a class="nav-link" :class="{ active: isActive('thirdparty') }">
|
||||
<label for="thirdparty">
|
||||
<input
|
||||
@@ -24,19 +24,18 @@
|
||||
v-model="radioType"
|
||||
value="thirdparty"
|
||||
/>
|
||||
{{ trans(ONTHEFLY_CREATE_THIRDPARTY) }}
|
||||
{{ $t("onthefly.create.thirdparty") }}
|
||||
</label>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="my-4">
|
||||
<PersonEdit
|
||||
<on-the-fly-person
|
||||
v-if="type === 'person'"
|
||||
action="create"
|
||||
:action="action"
|
||||
:query="query"
|
||||
ref="castPerson"
|
||||
@onPersonCreated="(payload) => emit('onPersonCreated', payload)"
|
||||
/>
|
||||
|
||||
<on-the-fly-thirdparty
|
||||
@@ -47,71 +46,64 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
<script>
|
||||
import OnTheFlyPerson from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue";
|
||||
import OnTheFlyThirdparty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue";
|
||||
import {
|
||||
ONTHEFLY_CREATE_PERSON,
|
||||
ONTHEFLY_CREATE_THIRDPARTY,
|
||||
trans,
|
||||
} from "translator";
|
||||
import { CreatableEntityType, Person } from "ChillPersonAssets/types";
|
||||
import { CreateComponentConfig } from "ChillMainAssets/types";
|
||||
import PersonEdit from "ChillPersonAssets/vuejs/_components/OnTheFly/PersonEdit.vue";
|
||||
|
||||
const props = withDefaults(defineProps<CreateComponentConfig>(), {
|
||||
allowedTypes: ["person"],
|
||||
action: "create",
|
||||
query: "",
|
||||
});
|
||||
|
||||
const emit =
|
||||
defineEmits<(e: "onPersonCreated", payload: { person: Person }) => void>();
|
||||
|
||||
const type = ref<CreatableEntityType | null>(null);
|
||||
|
||||
const radioType = computed<CreatableEntityType | null>({
|
||||
get: () => type.value,
|
||||
set: (val: CreatableEntityType | null) => {
|
||||
type.value = val;
|
||||
console.log("## type:", val, ", action:", props.action);
|
||||
export default {
|
||||
name: "OnTheFlyCreate",
|
||||
props: ["action", "allowedTypes", "query"],
|
||||
components: {
|
||||
OnTheFlyPerson,
|
||||
OnTheFlyThirdparty,
|
||||
},
|
||||
});
|
||||
|
||||
type PersonEditComponent = InstanceType<typeof PersonEdit>;
|
||||
|
||||
type AnyComponentInstance =
|
||||
| InstanceType<typeof OnTheFlyPerson>
|
||||
| InstanceType<typeof OnTheFlyThirdparty>
|
||||
| null;
|
||||
|
||||
const castPerson = ref<PersonEditComponent>(null);
|
||||
const castThirdparty = ref<AnyComponentInstance>(null);
|
||||
|
||||
onMounted(() => {
|
||||
type.value =
|
||||
props.allowedTypes.length === 1 && props.allowedTypes.includes("thirdparty")
|
||||
data() {
|
||||
return {
|
||||
type: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
radioType: {
|
||||
set(type) {
|
||||
this.type = type;
|
||||
console.log("## type:", type, ", action:", this.action);
|
||||
},
|
||||
get() {
|
||||
return this.type;
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.type =
|
||||
this.allowedTypes.length === 1 &&
|
||||
this.allowedTypes.includes("thirdparty")
|
||||
? "thirdparty"
|
||||
: "person";
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
isActive(tab) {
|
||||
return this.type === tab ? true : false;
|
||||
},
|
||||
castDataByType() {
|
||||
switch (this.radioType) {
|
||||
case "person":
|
||||
return this.$refs.castPerson.$data.person;
|
||||
case "thirdparty":
|
||||
let data = this.$refs.castThirdparty.$data.thirdparty;
|
||||
if (data.address !== undefined && data.address !== null) {
|
||||
data.address = { id: data.address.address_id };
|
||||
} else {
|
||||
data.address = null;
|
||||
}
|
||||
|
||||
function isActive(tab: CreatableEntityType) {
|
||||
return type.value === tab;
|
||||
}
|
||||
|
||||
const containsThirdParty = computed<boolean>(() =>
|
||||
props.allowedTypes.includes("thirdparty"),
|
||||
);
|
||||
const containsPerson = computed<boolean>(() => {
|
||||
return props.allowedTypes.includes("person");
|
||||
});
|
||||
|
||||
function save(): void {
|
||||
castPerson.value.postPerson();
|
||||
}
|
||||
|
||||
defineExpose({ save });
|
||||
return data;
|
||||
default:
|
||||
throw Error("Invalid type of entity");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
@@ -1,59 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import Create from "ChillMainAssets/vuejs/OnTheFly/components/Create.vue";
|
||||
import { CreateComponentConfig } from "ChillMainAssets/types";
|
||||
import { trans, SAVE } from "translator";
|
||||
import { useTemplateRef } from "vue";
|
||||
import { Person } from "ChillPersonAssets/types";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "onPersonCreated", payload: { person: Person }): void;
|
||||
(e: "close"): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<CreateComponentConfig>();
|
||||
const modalDialogClass = { "modal-xl": true, "modal-scrollable": true };
|
||||
|
||||
type CreateComponentType = InstanceType<typeof Create>;
|
||||
|
||||
const create = useTemplateRef<CreateComponentType>("create");
|
||||
|
||||
function save(): void {
|
||||
console.log("save from CreateModal");
|
||||
create.value?.save();
|
||||
}
|
||||
|
||||
defineExpose({ save });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<modal
|
||||
@close="() => emit('close')"
|
||||
:modal-dialog-class="modalDialogClass"
|
||||
:hide-footer="false"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">{{ modalTitle }}</h3>
|
||||
</template>
|
||||
<template #body-head>
|
||||
<div class="modal-body">
|
||||
<Create
|
||||
ref="create"
|
||||
:allowedTypes="props.allowedTypes"
|
||||
:action="props.action"
|
||||
:query="props.query"
|
||||
@onPersonCreated="(payload) => emit('onPersonCreated', payload)"
|
||||
></Create>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button class="btn btn-save" type="button" @click.prevent="save">
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@@ -9,7 +9,7 @@
|
||||
class="btn btn-sm"
|
||||
target="_blank"
|
||||
:class="classAction"
|
||||
:title="trans(titleAction)"
|
||||
:title="$t(titleAction)"
|
||||
@click="openModal"
|
||||
>
|
||||
{{ buttonText }}<span v-if="isDead"> (‡)</span>
|
||||
@@ -23,10 +23,10 @@
|
||||
>
|
||||
<template #header>
|
||||
<h3 v-if="parent" class="modal-title">
|
||||
{{ trans(titleModal, { q: parent.text }) }}
|
||||
{{ $t(titleModal, { q: parent.text }) }}
|
||||
</h3>
|
||||
<h3 v-else class="modal-title">
|
||||
{{ trans(titleModal) }}
|
||||
{{ $t(titleModal) }}
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
ref="castPerson"
|
||||
/>
|
||||
<div v-if="hasResourceComment">
|
||||
<h3>{{ trans(ONTHEFLY_RESOURCE_COMMENT_TITLE) }}</h3>
|
||||
<h3>{{ $t("onthefly.resource_comment_title") }}</h3>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ parent.comment }}
|
||||
</blockquote>
|
||||
@@ -53,7 +53,7 @@
|
||||
ref="castThirdparty"
|
||||
/>
|
||||
<div v-if="hasResourceComment">
|
||||
<h3>{{ trans(ONTHEFLY_RESOURCE_COMMENT_TITLE) }}</h3>
|
||||
<h3>{{ $t("onthefly.resource_comment_title") }}</h3>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ parent.comment }}
|
||||
</blockquote>
|
||||
@@ -82,82 +82,65 @@
|
||||
<a
|
||||
v-if="action === 'show'"
|
||||
:href="buildLocation(id, type)"
|
||||
:title="trans(titleMessage)"
|
||||
:title="$t(titleMessage)"
|
||||
class="btn btn-show"
|
||||
>{{ trans(buttonMessage) }}
|
||||
>{{ $t(buttonMessage) }}
|
||||
</a>
|
||||
<a v-else class="btn btn-save" @click="saveAction">
|
||||
{{ trans(ACTION_SAVE) }}
|
||||
{{ $t("action.save") }}
|
||||
</a>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, computed, defineEmits, defineProps } from "vue";
|
||||
|
||||
<script>
|
||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import OnTheFlyCreate from "./Create.vue";
|
||||
import OnTheFlyPerson from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue";
|
||||
import OnTheFlyThirdparty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue";
|
||||
import {
|
||||
trans,
|
||||
ACTION_SHOW,
|
||||
ACTION_EDIT,
|
||||
ACTION_CREATE,
|
||||
ACTION_ADDCONTACT,
|
||||
ONTHEFLY_CREATE_TITLE_DEFAULT,
|
||||
ONTHEFLY_CREATE_TITLE_PERSON,
|
||||
ONTHEFLY_CREATE_TITLE_THIRDPARTY,
|
||||
ONTHEFLY_SHOW_PERSON,
|
||||
ONTHEFLY_SHOW_THIRDPARTY,
|
||||
ONTHEFLY_EDIT_PERSON,
|
||||
ONTHEFLY_EDIT_THIRDPARTY,
|
||||
ONTHEFLY_ADDCONTACT_TITLE,
|
||||
ACTION_REDIRECT_PERSON,
|
||||
ACTION_REDIRECT_THIRDPARTY,
|
||||
ONTHEFLY_SHOW_FILE_PERSON,
|
||||
ONTHEFLY_SHOW_FILE_THIRDPARTY,
|
||||
ONTHEFLY_SHOW_FILE_DEFAULT,
|
||||
ONTHEFLY_RESOURCE_COMMENT_TITLE,
|
||||
ACTION_SAVE,
|
||||
} from "translator";
|
||||
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
id: [String, Number],
|
||||
action: String,
|
||||
buttonText: String,
|
||||
displayBadge: Boolean,
|
||||
isDead: Boolean,
|
||||
parent: Object,
|
||||
allowedTypes: Array,
|
||||
query: String,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["saveFormOnTheFly"]);
|
||||
|
||||
const modal = ref({
|
||||
export default {
|
||||
name: "OnTheFly",
|
||||
components: {
|
||||
Modal,
|
||||
OnTheFlyPerson,
|
||||
OnTheFlyThirdparty,
|
||||
OnTheFlyCreate,
|
||||
},
|
||||
props: [
|
||||
"type",
|
||||
"id",
|
||||
"action",
|
||||
"buttonText",
|
||||
"displayBadge",
|
||||
"isDead",
|
||||
"parent",
|
||||
"allowedTypes",
|
||||
"query",
|
||||
],
|
||||
emits: ["saveFormOnTheFly"],
|
||||
data() {
|
||||
return {
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||
});
|
||||
|
||||
const castPerson = ref();
|
||||
const castThirdparty = ref();
|
||||
const castNew = ref();
|
||||
|
||||
const hasResourceComment = computed(() => {
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasResourceComment() {
|
||||
return (
|
||||
typeof props.parent !== "undefined" &&
|
||||
props.parent !== null &&
|
||||
props.action === "show" &&
|
||||
props.parent.type === "accompanying_period_resource" &&
|
||||
props.parent.comment !== null &&
|
||||
props.parent.comment !== ""
|
||||
typeof this.parent !== "undefined" &&
|
||||
this.parent !== null &&
|
||||
this.action === "show" &&
|
||||
this.parent.type === "accompanying_period_resource" &&
|
||||
this.parent.comment !== null &&
|
||||
this.parent.comment !== ""
|
||||
);
|
||||
});
|
||||
|
||||
const classAction = computed(() => {
|
||||
switch (props.action) {
|
||||
},
|
||||
classAction() {
|
||||
switch (this.action) {
|
||||
case "show":
|
||||
return "btn-show";
|
||||
case "edit":
|
||||
@@ -169,125 +152,114 @@ const classAction = computed(() => {
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
const titleAction = computed(() => {
|
||||
switch (props.action) {
|
||||
},
|
||||
titleAction() {
|
||||
switch (this.action) {
|
||||
case "show":
|
||||
return ACTION_SHOW;
|
||||
return "action.show";
|
||||
case "edit":
|
||||
return ACTION_EDIT;
|
||||
return "action.edit";
|
||||
case "create":
|
||||
return ACTION_CREATE;
|
||||
return "action.create";
|
||||
case "addContact":
|
||||
return ACTION_ADDCONTACT;
|
||||
return "action.addContact";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
const titleCreate = computed(() => {
|
||||
if (typeof props.allowedTypes === "undefined") {
|
||||
return ONTHEFLY_CREATE_TITLE_DEFAULT;
|
||||
},
|
||||
titleCreate() {
|
||||
if (typeof this.allowedTypes === "undefined") {
|
||||
return "onthefly.create.title.default";
|
||||
}
|
||||
return props.allowedTypes.every((t) => t === "person")
|
||||
? ONTHEFLY_CREATE_TITLE_PERSON
|
||||
: props.allowedTypes.every((t) => t === "thirdparty")
|
||||
? ONTHEFLY_CREATE_TITLE_THIRDPARTY
|
||||
: ONTHEFLY_CREATE_TITLE_DEFAULT;
|
||||
});
|
||||
|
||||
const titleModal = computed(() => {
|
||||
switch (props.action) {
|
||||
return this.allowedTypes.every((t) => t === "person")
|
||||
? "onthefly.create.title.person"
|
||||
: this.allowedTypes.every((t) => t === "thirdparty")
|
||||
? "onthefly.create.title.thirdparty"
|
||||
: "onthefly.create.title.default";
|
||||
},
|
||||
titleModal() {
|
||||
switch (this.action) {
|
||||
case "show":
|
||||
if (props.type == "person") {
|
||||
return ONTHEFLY_SHOW_PERSON;
|
||||
} else if (props.type == "thirdparty") {
|
||||
return ONTHEFLY_SHOW_THIRDPARTY;
|
||||
}
|
||||
break;
|
||||
return "onthefly.show." + this.type;
|
||||
case "edit":
|
||||
if (props.type == "person") {
|
||||
return ONTHEFLY_EDIT_PERSON;
|
||||
} else if (props.type == "thirdparty") {
|
||||
return ONTHEFLY_EDIT_THIRDPARTY;
|
||||
}
|
||||
break;
|
||||
return "onthefly.edit." + this.type;
|
||||
case "create":
|
||||
return titleCreate.value;
|
||||
return this.titleCreate;
|
||||
case "addContact":
|
||||
return ONTHEFLY_ADDCONTACT_TITLE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const titleMessage = computed(() => {
|
||||
switch (props.type) {
|
||||
case "person":
|
||||
return ACTION_REDIRECT_PERSON;
|
||||
case "thirdparty":
|
||||
return ACTION_REDIRECT_THIRDPARTY;
|
||||
return "onthefly.addContact.title";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
const buttonMessage = computed(() => {
|
||||
switch (props.type) {
|
||||
},
|
||||
titleMessage() {
|
||||
switch (this.type) {
|
||||
case "person":
|
||||
return ONTHEFLY_SHOW_FILE_PERSON;
|
||||
return "action.redirect." + this.type;
|
||||
case "thirdparty":
|
||||
return ONTHEFLY_SHOW_FILE_THIRDPARTY;
|
||||
return "action.redirect." + this.type;
|
||||
default:
|
||||
return ONTHEFLY_SHOW_FILE_DEFAULT;
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
const isDisplayBadge = computed(() => {
|
||||
return props.displayBadge === true && props.buttonText !== null;
|
||||
});
|
||||
|
||||
const badgeType = computed(() => {
|
||||
return "entity-" + props.type + " badge-" + props.type;
|
||||
});
|
||||
|
||||
const getReturnPath = computed(() => {
|
||||
},
|
||||
buttonMessage() {
|
||||
switch (this.type) {
|
||||
case "person":
|
||||
return "onthefly.show.file_" + this.type;
|
||||
case "thirdparty":
|
||||
return "onthefly.show.file_" + this.type;
|
||||
}
|
||||
},
|
||||
isDisplayBadge() {
|
||||
return this.displayBadge === true && this.buttonText !== null;
|
||||
},
|
||||
badgeType() {
|
||||
return "entity-" + this.type + " badge-" + this.type;
|
||||
},
|
||||
getReturnPath() {
|
||||
return `?returnPath=${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
});
|
||||
|
||||
function closeModal() {
|
||||
modal.value.showModal = false;
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
modal.value.showModal = true;
|
||||
}
|
||||
|
||||
function changeActionTo(action) {
|
||||
console.log(action);
|
||||
// Not reactive in setup, but you can emit or use a ref if needed
|
||||
}
|
||||
|
||||
function saveAction() {
|
||||
let type = props.type,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.modal.showModal = false;
|
||||
},
|
||||
openModal() {
|
||||
// console.log('## OPEN ON THE FLY MODAL');
|
||||
// console.log('## type:', this.type, ', action:', this.action);
|
||||
this.modal.showModal = true;
|
||||
this.$nextTick(function () {
|
||||
//this.$refs.search.focus();
|
||||
});
|
||||
},
|
||||
changeActionTo(action) {
|
||||
this.$data.action = action;
|
||||
},
|
||||
saveAction() {
|
||||
// console.log('saveAction button: create/edit action with', this.type);
|
||||
let type = this.type,
|
||||
data = {};
|
||||
switch (type) {
|
||||
case "person":
|
||||
data = castPerson.value?.$data.person;
|
||||
data = this.$refs.castPerson.$data.person;
|
||||
console.log("person data are", data);
|
||||
break;
|
||||
|
||||
case "thirdparty":
|
||||
data = castThirdparty.value?.$data.thirdparty;
|
||||
data = this.$refs.castThirdparty.$data.thirdparty;
|
||||
/* never executed ? */
|
||||
break;
|
||||
|
||||
default:
|
||||
if (typeof props.type === "undefined") {
|
||||
if (props.action === "addContact") {
|
||||
if (typeof this.type === "undefined") {
|
||||
// action=create or addContact
|
||||
// console.log('will rewrite data');
|
||||
if (this.action === "addContact") {
|
||||
type = "thirdparty";
|
||||
data = castThirdparty.value?.$data.thirdparty;
|
||||
data = this.$refs.castThirdparty.$data.thirdparty;
|
||||
// console.log('data original', data);
|
||||
data.parent = {
|
||||
type: "thirdparty",
|
||||
id: props.parent.id,
|
||||
id: this.parent.id,
|
||||
};
|
||||
data.civility =
|
||||
data.civility !== null
|
||||
@@ -296,11 +268,16 @@ function saveAction() {
|
||||
id: data.civility.id,
|
||||
}
|
||||
: null;
|
||||
data.profession = data.profession !== "" ? data.profession : "";
|
||||
data.profession =
|
||||
data.profession !== "" ? data.profession : "";
|
||||
} else {
|
||||
type = castNew.value.radioType;
|
||||
data = castNew.value.castDataByType();
|
||||
if (typeof data.civility !== "undefined" && null !== data.civility) {
|
||||
type = this.$refs.castNew.radioType;
|
||||
data = this.$refs.castNew.castDataByType();
|
||||
// console.log('type', type);
|
||||
if (
|
||||
typeof data.civility !== "undefined" &&
|
||||
null !== data.civility
|
||||
) {
|
||||
data.civility =
|
||||
data.civility !== null
|
||||
? {
|
||||
@@ -313,37 +290,34 @@ function saveAction() {
|
||||
typeof data.profession !== "undefined" &&
|
||||
"" !== data.profession
|
||||
) {
|
||||
data.profession = data.profession !== "" ? data.profession : "";
|
||||
data.profession =
|
||||
data.profession !== ""
|
||||
? data.profession
|
||||
: "";
|
||||
}
|
||||
// console.log('onthefly data', data);
|
||||
}
|
||||
} else {
|
||||
throw "error with object type";
|
||||
}
|
||||
}
|
||||
emit("saveFormOnTheFly", { type: type, data: data });
|
||||
}
|
||||
|
||||
function buildLocation(id, type) {
|
||||
// pass datas to parent
|
||||
this.$emit("saveFormOnTheFly", { type: type, data: data });
|
||||
},
|
||||
buildLocation(id, type) {
|
||||
if (type === "person") {
|
||||
return encodeURI(`/fr/person/${id}/general${getReturnPath.value}`);
|
||||
// TODO i18n
|
||||
return encodeURI(
|
||||
`/fr/person/${id}/general${this.getReturnPath}`,
|
||||
);
|
||||
} else if (type === "thirdparty") {
|
||||
return encodeURI(`/fr/3party/3party/${id}/view${getReturnPath.value}`);
|
||||
return encodeURI(
|
||||
`/fr/3party/3party/${id}/view${this.getReturnPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
closeModal,
|
||||
changeActionTo,
|
||||
saveAction,
|
||||
castPerson,
|
||||
castThirdparty,
|
||||
castNew,
|
||||
hasResourceComment,
|
||||
modal,
|
||||
isDisplayBadge,
|
||||
Modal,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
@@ -1,35 +1,22 @@
|
||||
<template>
|
||||
<div class="grey-card p-2">
|
||||
<ul :class="listClasses" v-if="displayPicked">
|
||||
<li
|
||||
v-for="p in picked"
|
||||
@click="removeEntity(p)"
|
||||
:key="p === 'me' ? 'me' : p.type + p.id"
|
||||
>
|
||||
<ul :class="listClasses" v-if="picked.length && displayPicked">
|
||||
<li v-for="p in picked" @click="removeEntity(p)" :key="p.type + p.id">
|
||||
<span
|
||||
v-if="'me' === p"
|
||||
class="chill_denomination current-user updatedBy"
|
||||
>{{ trans(USER_CURRENT_USER) }}</span
|
||||
>
|
||||
<span
|
||||
v-else
|
||||
:class="getBadgeClass(p)"
|
||||
class="chill_denomination"
|
||||
:style="getBadgeStyle(p)"
|
||||
>
|
||||
{{ p.text }}
|
||||
</span>
|
||||
<span v-else class="chill_denomination">{{ p.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="record_actions mb-0">
|
||||
<ul class="record_actions">
|
||||
<li v-if="isCurrentUserPicker" class="btn btn-sm btn-misc">
|
||||
<label class="flex items-center gap-1">
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
:checked="isMePicked"
|
||||
:checked="picked.indexOf('me') >= 0 ? true : null"
|
||||
ref="itsMeCheckbox"
|
||||
:type="multiple ? 'checkbox' : 'radio'"
|
||||
@change="selectItsMe($event as InputEvent)"
|
||||
style="margin: 0"
|
||||
@change="selectItsMe"
|
||||
/>
|
||||
{{ trans(USER_CURRENT_USER) }}
|
||||
</label>
|
||||
@@ -40,289 +27,94 @@
|
||||
:key="uniqid"
|
||||
:buttonTitle="translatedListOfTypes"
|
||||
:modalTitle="translatedListOfTypes"
|
||||
:allowCreate="true"
|
||||
ref="addPersons"
|
||||
@addNewPersons="addNewEntity"
|
||||
>
|
||||
</add-persons>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="badge-suggest add-items inline text-end">
|
||||
<li
|
||||
v-for="s in suggested"
|
||||
:key="s.type + s.id"
|
||||
@click="addNewSuggested(s)"
|
||||
style="margin: 0"
|
||||
>
|
||||
<span :class="getBadgeClass(s)" :style="getBadgeStyle(s)">
|
||||
{{ s.text }}
|
||||
</span>
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li v-for="s in suggested" :key="s.id" @click="addNewSuggested(s)">
|
||||
<span>{{ s.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
defineComponent,
|
||||
withDefaults,
|
||||
} from "vue";
|
||||
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
||||
import {
|
||||
Entities,
|
||||
EntitiesOrMe,
|
||||
EntityType,
|
||||
SearchOptions,
|
||||
Suggestion,
|
||||
} from "ChillPersonAssets/types";
|
||||
import {
|
||||
PICK_ENTITY_MODAL_TITLE,
|
||||
PICK_ENTITY_USER,
|
||||
PICK_ENTITY_USER_GROUP,
|
||||
PICK_ENTITY_PERSON,
|
||||
PICK_ENTITY_THIRDPARTY,
|
||||
USER_CURRENT_USER,
|
||||
trans,
|
||||
} from "translator";
|
||||
import { addNewEntities } from "ChillMainAssets/types";
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue"; // eslint-disable-line
|
||||
import { appMessages } from "./i18n";
|
||||
import { trans, USER_CURRENT_USER } from "translator";
|
||||
|
||||
defineComponent({
|
||||
components: {
|
||||
AddPersons,
|
||||
},
|
||||
const props = defineProps({
|
||||
multiple: Boolean,
|
||||
types: Array,
|
||||
picked: Array,
|
||||
uniqid: String,
|
||||
removableIfSet: { type: Boolean, default: true },
|
||||
displayPicked: { type: Boolean, default: true },
|
||||
suggested: { type: Array, default: () => [] },
|
||||
label: String,
|
||||
isCurrentUserPicker: { type: Boolean, default: false },
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
multiple: boolean;
|
||||
types: EntityType[];
|
||||
picked: EntitiesOrMe[];
|
||||
uniqid: string;
|
||||
removableIfSet?: boolean;
|
||||
displayPicked?: boolean;
|
||||
suggested?: Entities[];
|
||||
label?: string;
|
||||
isCurrentUserPicker?: boolean;
|
||||
}>(),
|
||||
{ isCurrentUserPicker: false, displayPicked: true, removableIfSet: true },
|
||||
);
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: "addNewEntity", payload: { entity: EntitiesOrMe }): void;
|
||||
(e: "removeEntity", payload: { entity: EntitiesOrMe }): void;
|
||||
(e: "addNewEntityProcessEnded"): void;
|
||||
}>();
|
||||
const emit = defineEmits([
|
||||
"addNewEntity",
|
||||
"removeEntity",
|
||||
"addNewEntityProcessEnded",
|
||||
]);
|
||||
|
||||
const itsMeCheckbox = ref<null | HTMLInputElement>(null);
|
||||
const addPersons = ref();
|
||||
const itsMeCheckbox = ref(null);
|
||||
const addPersons = ref(null);
|
||||
|
||||
const addPersonsOptions = computed(
|
||||
() =>
|
||||
({
|
||||
const addPersonsOptions = computed(() => ({
|
||||
uniq: !props.multiple,
|
||||
type: props.types,
|
||||
priority: null,
|
||||
button: {
|
||||
size: "btn-sm",
|
||||
class: "btn-submit",
|
||||
},
|
||||
}) as SearchOptions,
|
||||
);
|
||||
|
||||
const isMePicked = computed<boolean>(() => props.picked.indexOf("me") >= 0);
|
||||
button: { size: "btn-sm", class: "btn-submit" },
|
||||
}));
|
||||
|
||||
const translatedListOfTypes = computed(() => {
|
||||
if (props.label !== undefined && props.label !== "") {
|
||||
return props.label;
|
||||
}
|
||||
|
||||
const translatedTypes = props.types.map((type: EntityType) => {
|
||||
switch (type) {
|
||||
case "user":
|
||||
return trans(PICK_ENTITY_USER, {
|
||||
count: props.multiple ? 2 : 1,
|
||||
});
|
||||
case "person":
|
||||
return trans(PICK_ENTITY_PERSON, {
|
||||
count: props.multiple ? 2 : 1,
|
||||
});
|
||||
case "thirdparty":
|
||||
return trans(PICK_ENTITY_THIRDPARTY, {
|
||||
count: props.multiple ? 2 : 1,
|
||||
});
|
||||
case "user_group":
|
||||
return trans(PICK_ENTITY_USER_GROUP, {
|
||||
count: props.multiple ? 2 : 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return `${trans(PICK_ENTITY_MODAL_TITLE, {
|
||||
count: props.multiple ? 2 : 1,
|
||||
})} ${translatedTypes.join(", ")}`;
|
||||
if (props.label) return props.label;
|
||||
let trans = props.types.map((t) =>
|
||||
props.multiple
|
||||
? appMessages.fr.pick_entity[t].toLowerCase()
|
||||
: appMessages.fr.pick_entity[t + "_one"].toLowerCase(),
|
||||
);
|
||||
return props.multiple
|
||||
? appMessages.fr.pick_entity.modal_title + trans.join(", ")
|
||||
: appMessages.fr.pick_entity.modal_title_one + trans.join(", ");
|
||||
});
|
||||
|
||||
const listClasses = computed(() => ({
|
||||
"badge-suggest": true,
|
||||
"list-suggest": true,
|
||||
"remove-items": props.removableIfSet,
|
||||
inline: true,
|
||||
}));
|
||||
|
||||
const selectItsMe = (event: InputEvent): void => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.checked) {
|
||||
addNewSuggested("me");
|
||||
} else {
|
||||
removeEntity("me");
|
||||
}
|
||||
const selectItsMe = (event) =>
|
||||
event.target.checked ? addNewSuggested("me") : removeEntity("me");
|
||||
|
||||
const addNewSuggested = (entity) => {
|
||||
emit("addNewEntity", { entity });
|
||||
};
|
||||
|
||||
function addNewSuggested(entity: EntitiesOrMe) {
|
||||
emits("addNewEntity", { entity });
|
||||
}
|
||||
|
||||
function addNewEntity({ selected }: { selected: Suggestion[] }) {
|
||||
Object.values(selected).forEach((item) => {
|
||||
emits("addNewEntity", { entity: item.result });
|
||||
});
|
||||
const addNewEntity = ({ selected, modal }) => {
|
||||
selected.forEach((item) => emit("addNewEntity", { entity: item.result }));
|
||||
addPersons.value?.resetSearch();
|
||||
modal.showModal = false;
|
||||
emit("addNewEntityProcessEnded");
|
||||
};
|
||||
|
||||
emits("addNewEntityProcessEnded");
|
||||
}
|
||||
|
||||
const removeEntity = (entity: EntitiesOrMe): void => {
|
||||
const removeEntity = (entity) => {
|
||||
if (!props.removableIfSet) return;
|
||||
if (entity === "me" && itsMeCheckbox.value) {
|
||||
itsMeCheckbox.value.checked = false;
|
||||
}
|
||||
emits("removeEntity", { entity });
|
||||
emit("removeEntity", { entity });
|
||||
};
|
||||
|
||||
function getBadgeClass(entities: Entities) {
|
||||
if (entities.type !== "user_group") {
|
||||
return entities.type;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getBadgeStyle(entities: Entities) {
|
||||
if (entities.type === "user_group") {
|
||||
return [
|
||||
`ul.badge-suggest li > span {
|
||||
color: ${entities.foregroundColor}!important;
|
||||
border-bottom-color: ${entities.backgroundColor};
|
||||
}`,
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grey-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.btn-check:checked + .btn,
|
||||
:not(.btn-check) + .btn:active,
|
||||
.btn:first-child:active,
|
||||
.btn.active,
|
||||
.btn.show {
|
||||
color: white;
|
||||
box-shadow: 0 0 0 0.2rem var(--bs-chill-green);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.as-user-group {
|
||||
display: inline-block;
|
||||
}
|
||||
ul.badge-suggest {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0px;
|
||||
min-height: 30px;
|
||||
}
|
||||
ul.badge-suggest li > span {
|
||||
white-space: normal;
|
||||
text-align: start;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
ul.badge-suggest.inline li {
|
||||
display: inline-block;
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
ul.badge-suggest.add-items li {
|
||||
position: relative;
|
||||
}
|
||||
ul.badge-suggest.add-items li span {
|
||||
cursor: pointer;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
ul.badge-suggest.add-items li span:hover {
|
||||
color: #ced4da;
|
||||
}
|
||||
ul.badge-suggest.add-items li > span:before {
|
||||
font: normal normal normal 13px ForkAwesome;
|
||||
margin-right: 1.8em;
|
||||
content: "\f067";
|
||||
color: var(--bs-success);
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 0.75rem;
|
||||
-webkit-transform: translateY(-50%);
|
||||
-moz-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
ul.badge-suggest.remove-items li {
|
||||
position: relative;
|
||||
}
|
||||
ul.badge-suggest.remove-items li span {
|
||||
cursor: pointer;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
ul.badge-suggest.remove-items li span:hover {
|
||||
color: #ced4da;
|
||||
}
|
||||
ul.badge-suggest.remove-items li > span:before {
|
||||
font: normal normal normal 13px ForkAwesome;
|
||||
margin-right: 1.8em;
|
||||
content: "\f1f8";
|
||||
color: var(--bs-danger);
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 0.75rem;
|
||||
-webkit-transform: translateY(-50%);
|
||||
-moz-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
ul.badge-suggest li > span {
|
||||
margin: 0.2rem 0.1rem;
|
||||
display: inline-block;
|
||||
padding: 0 1em 0 2.2em !important;
|
||||
background-color: #fff;
|
||||
border: 1px solid #dee2e6;
|
||||
border-bottom-width: 3px;
|
||||
border-bottom-style: solid;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
ul.badge-suggest li > span.person {
|
||||
border-bottom-color: #43b29d;
|
||||
}
|
||||
ul.badge-suggest li > span.thirdparty {
|
||||
border-bottom-color: rgb(198.9, 72, 98.1);
|
||||
}
|
||||
.current-user {
|
||||
color: var(--bs-body-color);
|
||||
background-color: var(--bs-chill-l-gray) !important;
|
||||
|
@@ -61,7 +61,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
transName(value) {
|
||||
return value.code && value.name ? `${value.name} (${value.code})` : "";
|
||||
return value.code && value.name
|
||||
? `${value.name} (${value.code})`
|
||||
: "";
|
||||
},
|
||||
selectCity(city) {
|
||||
this.$emit("selectCity", city);
|
||||
@@ -84,7 +86,9 @@ export default {
|
||||
|
||||
searchCities(query, this.country, controller)
|
||||
.then((newCities) => {
|
||||
this.cities = this.cities.filter((city) => city.id === this.picked);
|
||||
this.cities = this.cities.filter(
|
||||
(city) => city.id === this.picked,
|
||||
);
|
||||
newCities.forEach((item) => {
|
||||
this.cities.push(item);
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user