mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-11-11 22:58:26 +00:00
Compare commits
243 Commits
v4.7.0
...
ticket-app
| Author | SHA1 | Date | |
|---|---|---|---|
| 65bbdc283d | |||
|
|
f0b3e63d66 | ||
| 41a31944b3 | |||
| 51607572de | |||
| 1fc5530707 | |||
|
|
b31778c068 | ||
|
305c6deb24
|
|||
|
d691098a21
|
|||
| a22cbe0239 | |||
| 98902bdeb8 | |||
| 4765d4fe28 | |||
|
|
30bcb85549 | ||
|
6d2e78ce55
|
|||
| 61ca700bbe | |||
|
|
b43aeebc3c | ||
| 056e2dcc5f | |||
| e57d1ac696 | |||
| 0eff1d2e79 | |||
| 3928b2cc7a | |||
|
4f51ef81ad
|
|||
|
4637dc692c
|
|||
| 38935edb93 | |||
|
|
e1ef65d4ca | ||
| ec9d0be70b | |||
|
|
0ba2cbc1e8 | ||
| e87429933a | |||
|
8e2e676e3d
|
|||
| e12ad563a3 | |||
|
|
711aa8db9b | ||
| e78d44953f | |||
| 18f67801c7 | |||
|
c815e6bc69
|
|||
| 807f2711fe | |||
|
|
cd594cd580 | ||
|
fb6b26bfb5
|
|||
|
c5cedb8bd6
|
|||
|
2665e43a61
|
|||
|
25561cdf63
|
|||
| 10b73e06e1 | |||
|
|
e7c04e34a9 | ||
| 164beee7c6 | |||
|
|
4d96eb9457 | ||
| fe2eba3b29 | |||
|
|
61d1232e31 | ||
| 6594d4f6a6 | |||
| 1a66a9e864 | |||
| 1b74c119dc | |||
| 14d88810f3 | |||
| 445a2c9358 | |||
| c8baf0a8aa | |||
| faed443a96 | |||
| bbf387d96f | |||
|
|
b7c9b60744 | ||
|
2bd303bbbe
|
|||
|
c5e6122d2c
|
|||
| 088b876e20 | |||
| 3400656d7c | |||
|
568c8be7fd
|
|||
|
538ecc42ea
|
|||
|
15d26d4b06
|
|||
|
d8bd9bd7cd
|
|||
|
dcdfba5ccd
|
|||
|
0204bdd38d
|
|||
|
392fd01b56
|
|||
| 35844f3b73 | |||
| 7506b918d7 | |||
| cfba291f2c | |||
|
|
04438c09d3 | ||
| 2a54d1b909 | |||
|
|
628eeac5e0 | ||
| a2263b3fa1 | |||
| 74796d0fb0 | |||
|
c19481e40a
|
|||
|
|
6eeb717b1a | ||
|
beb7c462da
|
|||
|
|
dbf363a9e8 | ||
|
64a2f7c9ed
|
|||
| f26d9739c8 | |||
|
afa5edc1d8
|
|||
|
42d6c9e672
|
|||
|
2b22d4cb7c
|
|||
|
c8e5d0eb37
|
|||
| 2bf8ad5d6c | |||
| 11698a52e3 | |||
| 70955573e8 | |||
|
|
3df4043eb9 | ||
|
06e8264dde
|
|||
| b451d2c4a3 | |||
| 4f93150874 | |||
| 0566ab0910 | |||
|
|
f4eeee1598 | ||
| 33cf16fc13 | |||
| 0a331aab37 | |||
| d43b739654 | |||
| c72432efae | |||
|
95975fae55
|
|||
|
95a7efa138
|
|||
|
45e193ff6d
|
|||
|
dfc146ff3f
|
|||
| b41fcf66a9 | |||
|
|
a8dd1b3548 | ||
|
2b99a480ac
|
|||
|
7633e587bb
|
|||
|
fc61dfdf3a
|
|||
|
f1a5b5c49e
|
|||
|
ec685dcd47
|
|||
|
631ae3eedd
|
|||
|
440a7837ac
|
|||
|
e0abf34784
|
|||
|
377ae9a9dc
|
|||
|
034dc30e30
|
|||
|
d615111a0f
|
|||
|
ffb756c712
|
|||
|
69daccb860
|
|||
|
16435423cf
|
|||
|
697b4ab436
|
|||
|
67d804e28e
|
|||
|
cf41fa9574
|
|||
|
b8b325f7d7
|
|||
|
e97bd8c4ef
|
|||
|
e28d7df533
|
|||
|
4b20b1bc01
|
|||
|
b15733076c
|
|||
|
25be5c9ea3
|
|||
|
b035020c6f
|
|||
|
128101dc46
|
|||
|
5f2711023e
|
|||
|
bdf2ed4bbd
|
|||
|
1df542603e
|
|||
|
80bcc68ce5
|
|||
|
154fc3e2f6
|
|||
|
e45af94c78
|
|||
|
166a6fde20
|
|||
|
631f047338
|
|||
|
a777588bb8
|
|||
|
ca78d112c2
|
|||
|
bcfd317d83
|
|||
|
348740f073
|
|||
|
0d74f0980f
|
|||
|
be19dc00db
|
|||
|
643028ffd6
|
|||
|
ac4e2e5bf2
|
|||
|
498572b96e
|
|||
|
d2a61ce69b
|
|||
|
a9c0567ee1
|
|||
|
76cec5b5a8
|
|||
|
efe8a67697
|
|||
|
26dfa9b028
|
|||
|
50025044d3
|
|||
|
e6202a2e34
|
|||
|
b863bd967d
|
|||
|
e65bcf7275
|
|||
|
e00ece4200
|
|||
|
640fd71402
|
|||
|
aae50ca290
|
|||
|
1fa483598b
|
|||
|
e4b6a468f8
|
|||
|
|
66c7758023 | ||
|
|
4750d2c24e | ||
|
|
ca05e3d979 | ||
|
|
a20f9b4f86 | ||
|
|
c73c1eb8d5 | ||
|
|
8778bb0731 | ||
|
|
c7d20eebc5 | ||
|
|
b9e130c159 | ||
|
|
3e8bc94af3 | ||
|
|
0c914c9f9f | ||
|
|
580a60c939 | ||
|
|
4996ac3b7c | ||
|
|
2a23bf19cb | ||
|
|
650d2596d9 | ||
|
|
2bdd5a329e | ||
|
78d1776733
|
|||
|
66dc603c85
|
|||
|
3a8154ecce
|
|||
|
c81828e04f
|
|||
|
|
ec17dd7de2 | ||
| 76c076a5f3 | |||
|
|
f0045edd6c | ||
|
|
d00b76ffcd | ||
|
|
8991f0ef3f | ||
|
|
d6f5eae0c9 | ||
|
|
821fce3dd8 | ||
|
|
1d33ae1e39 | ||
|
|
19af0feb57 | ||
|
|
1c09e9a692 | ||
|
|
d72e748388 | ||
|
|
ab850b7b70 | ||
|
|
3f9745d8cf | ||
|
|
473765366a | ||
|
|
6500c24a7f | ||
|
|
1d00457141 | ||
|
|
eb0bf56cff | ||
|
|
7b8cd90cf1 | ||
|
|
a27d92aba0 | ||
|
|
85bdfb9e21 | ||
|
|
4cffcf4de1 | ||
|
|
b2587a688f | ||
|
|
c9f0e9843b | ||
|
|
b40ad9e445 | ||
|
|
3e10e47e29 | ||
|
|
2a1963e993 | ||
| 34c171659b | |||
|
2d8b960d9e
|
|||
| 831ae03431 | |||
|
45828174d1
|
|||
|
ed45f14a45
|
|||
|
fa67835690
|
|||
|
b434d38091
|
|||
|
|
800a952532 | ||
|
9f355032a8
|
|||
|
0bc6e62d4d
|
|||
|
46fb1c04b5
|
|||
| 3b2c3d1464 | |||
|
|
0bd6038160 | ||
|
|
baab8e94ce | ||
|
e2deb55fdb
|
|||
|
|
2cdfb50058 | ||
|
39d701feb2
|
|||
|
613ee8b186
|
|||
|
56a1a488de
|
|||
| 3f789ad0f4 | |||
|
467bea7cde
|
|||
|
670b8eb82b
|
|||
|
a9760b323f
|
|||
|
71a3a1924a
|
|||
|
ecdc1e25bf
|
|||
|
dd37427be1
|
|||
|
c8467df1b1
|
|||
|
4c89a954fa
|
|||
|
7c1f3b114d
|
|||
|
36bc4dab24
|
|||
|
4b30d92282
|
|||
|
75fbec5489
|
|||
|
912fdd6349
|
|||
|
5832542978
|
|||
|
5c3585a1ed
|
|||
|
a2f1e20ddf
|
|||
|
4d67702a76
|
|||
| 18e442db29 | |||
|
|
deb3d92189 | ||
|
a59ea7db31
|
|||
|
a738b0cac9
|
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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: ""
|
||||||
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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
|
||||||
6
.changes/unreleased/Feature-20251007-155945.yaml
Normal file
6
.changes/unreleased/Feature-20251007-155945.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Admin interface for Motive entity
|
||||||
|
time: 2025-10-07T15:59:45.597029709+02:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
||||||
|
SchemaChange: No schema change
|
||||||
6
.changes/unreleased/Feature-20251022-111552.yaml
Normal file
6
.changes/unreleased/Feature-20251022-111552.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Add an admin interface for Motive entity
|
||||||
|
time: 2025-10-22T11:15:52.13937955+02:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
||||||
|
SchemaChange: Add columns or tables
|
||||||
6
.changes/unreleased/Fixed-20251106-161605.yaml
Normal file
6
.changes/unreleased/Fixed-20251106-161605.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Fixed
|
||||||
|
body: Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
|
||||||
|
time: 2025-11-06T16:16:05.861813041+01:00
|
||||||
|
custom:
|
||||||
|
Issue: "428"
|
||||||
|
SchemaChange: No schema change
|
||||||
@@ -19,11 +19,11 @@ max_line_length = 80
|
|||||||
[COMMIT_EDITMSG]
|
[COMMIT_EDITMSG]
|
||||||
max_line_length = 0
|
max_line_length = 0
|
||||||
|
|
||||||
[*.{js, vue, ts}]
|
[*.{js,vue,ts}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
[.rst]
|
[*.rst]
|
||||||
ident_size = 3
|
indent_size = 3
|
||||||
ident_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
|||||||
@@ -234,11 +234,9 @@ This must be a decision made by a human, not by an AI. Every AI task must abort
|
|||||||
|
|
||||||
#### Running Tests
|
#### Running Tests
|
||||||
|
|
||||||
The tests are run from the project's root (not from the bundle's root).
|
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).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all tests
|
|
||||||
vendor/bin/phpunit
|
|
||||||
|
|
||||||
# Run a specific test file
|
# Run a specific test file
|
||||||
vendor/bin/phpunit path/to/TestFile.php
|
vendor/bin/phpunit path/to/TestFile.php
|
||||||
|
|||||||
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
30
.vscode/launch.json
vendored
Normal file
30
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
// 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
Normal file
23
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"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
|
- person
|
||||||
- personvendee
|
- personvendee
|
||||||
- household_edit_metadata
|
- household_edit_metadata
|
||||||
- index.js
|
- index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
## Organisation des feuilles de styles
|
## Organisation des feuilles de styles
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { trans, setLocale, setLocaleFallbacks } from "./ux-translator";
|
import {
|
||||||
|
trans,
|
||||||
|
setLocale,
|
||||||
|
getLocale,
|
||||||
|
setLocaleFallbacks,
|
||||||
|
} from "./ux-translator";
|
||||||
|
|
||||||
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
|
setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" });
|
||||||
setLocale('fr');
|
setLocale("fr");
|
||||||
|
|
||||||
export { trans };
|
export { trans, getLocale };
|
||||||
export * from '../var/translations';
|
export * from "../var/translations";
|
||||||
|
|||||||
@@ -133,6 +133,7 @@
|
|||||||
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
|
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
|
||||||
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
|
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
|
||||||
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
|
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
|
||||||
|
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
|
||||||
"Chill\\Utils\\Rector\\": "utils/rector/src"
|
"Chill\\Utils\\Rector\\": "utils/rector/src"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ return [
|
|||||||
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
|
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
|
||||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||||
Chill\WopiBundle\ChillWopiBundle::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\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||||
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
|
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
chill_doc_store:
|
chill_doc_store:
|
||||||
use_driver: openstack
|
use_driver: local_storage
|
||||||
local_storage:
|
local_storage:
|
||||||
storage_path: '%kernel.project_dir%/var/storage'
|
storage_path: '%kernel.project_dir%/var/storage'
|
||||||
openstack:
|
openstack:
|
||||||
|
|||||||
5
config/packages/chill_ticket.yaml
Normal file
5
config/packages/chill_ticket.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
chill_ticket:
|
||||||
|
ticket:
|
||||||
|
person_per_ticket: one # One of "one"; "many"
|
||||||
|
response_time_exceeded_delay: PT12H
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ doctrine_migrations:
|
|||||||
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
|
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
|
||||||
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
|
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
|
||||||
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
|
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
|
||||||
|
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
|
||||||
all_or_nothing:
|
all_or_nothing:
|
||||||
true
|
true
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ framework:
|
|||||||
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||||
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
|
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
|
||||||
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
|
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
|
||||||
|
'Chill\TicketBundle\Messenger\PostTicketUpdateMessage': async
|
||||||
# end of routes added by chill-bundles recipes
|
# end of routes added by chill-bundles recipes
|
||||||
# Route your messages to the transports
|
# Route your messages to the transports
|
||||||
# 'App\Message\YourMessage': async
|
# 'App\Message\YourMessage': async
|
||||||
|
|||||||
2
config/routes/chill_ticket.yaml
Normal file
2
config/routes/chill_ticket.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
chill_ticket_bundle:
|
||||||
|
resource: '@ChillTicketBundle/config/routes.yaml'
|
||||||
@@ -11,24 +11,94 @@
|
|||||||
Create a new bundle
|
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::
|
.. warning::
|
||||||
|
|
||||||
This part of the doc is not yet tested
|
This part of the doc is not yet tested
|
||||||
|
|
||||||
TODO
|
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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.. rubric:: Footnotes
|
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
|
||||||
|
|
||||||
.. [#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.
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"typescript-eslint": "^8.13.0",
|
"typescript-eslint": "^8.13.0",
|
||||||
"vue-loader": "^17.0.0",
|
"vue-loader": "^17.0.0",
|
||||||
|
"vue-tsc": "^3.1.3",
|
||||||
"webpack": "^5.75.0",
|
"webpack": "^5.75.0",
|
||||||
"webpack-cli": "^5.0.1"
|
"webpack-cli": "^5.0.1"
|
||||||
},
|
},
|
||||||
@@ -80,12 +81,12 @@
|
|||||||
"dev": "encore dev",
|
"dev": "encore dev",
|
||||||
"watch": "encore dev --watch",
|
"watch": "encore dev --watch",
|
||||||
"build": "encore production --progress",
|
"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> 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 src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||||
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
||||||
"specs-create-dir": "mkdir -p templates/api",
|
"specs-create-dir": "mkdir -p templates/api",
|
||||||
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||||
"version": "node --version",
|
"version": "node --version",
|
||||||
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,10 @@
|
|||||||
<!-- temporarily removed, the time to find a fix -->
|
<!-- temporarily removed, the time to find a fix -->
|
||||||
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
|
|
||||||
|
<testsuite name="TicketBundle">
|
||||||
|
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
|
||||||
|
</testsuite>
|
||||||
<!--
|
<!--
|
||||||
<testsuite name="ReportBundle">
|
<testsuite name="ReportBundle">
|
||||||
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
||||||
|
|||||||
8
resources/ticket_motives_import/README.md
Normal file
8
resources/ticket_motives_import/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
In this directory, you find an example of file for the command `chill:main:ticket_motives_import`.
|
||||||
|
|
||||||
|
This file contains a list of ticket motives to import into the system. Each entry is a dictionary with two keys: `code` and `label`. The `code` key contains the unique code for the ticket motive, and the `label` key contains the human-readable label for the ticket motive.
|
||||||
|
|
||||||
|
The `stored_objects` key contains the documents that will be associated with the tickets. They must be found in the same directory.
|
||||||
|
|
||||||
|
The command `chill:main:ticket_motives_import` uses this file to import the specified ticket motives into the system.
|
||||||
|
|
||||||
136
resources/ticket_motives_import/motives.yaml
Normal file
136
resources/ticket_motives_import/motives.yaml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
- label:
|
||||||
|
fr: Appel famille pour annonce de décès
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Date du décès
|
||||||
|
- label:
|
||||||
|
fr: lieu du décès (domicile ou hôpital)
|
||||||
|
- label:
|
||||||
|
fr: nom de l’hôpital
|
||||||
|
- label:
|
||||||
|
fr: service concerné
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 2_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 3_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 4_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 'Appel famille pour annonce absence : hospitalisation ou consultation'
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Quel hôpital
|
||||||
|
- label:
|
||||||
|
fr: quel service
|
||||||
|
- label:
|
||||||
|
fr: pour quelles raisons
|
||||||
|
- label:
|
||||||
|
fr: 'consultation : date et heure'
|
||||||
|
- label:
|
||||||
|
fr: hospitalisation complète ou HDJ
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 5_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 6_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 7_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 'Appel famille pour annonce absence : interruption de prise en charge'
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Pour quelles raisons ? Date
|
||||||
|
- label:
|
||||||
|
fr: durée
|
||||||
|
- label:
|
||||||
|
fr: accord médical ?
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 8_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 9_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 10_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 'Appel famille pour annonce absence : changement d’adresse'
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Où
|
||||||
|
- label:
|
||||||
|
fr: Pourquoi ? Pour combien de temps ? Besoin d’un relais des soins ? Nouvelle adresse ?
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 11_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 12_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 13_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: Appel famille pour altération de l’état général du patient
|
||||||
|
urgent: true
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Recherche des symptômes
|
||||||
|
- label:
|
||||||
|
fr: Attentes par rapport à la demande
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 14_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 15_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 16_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: Appel famille pour prise en charge de la douleur
|
||||||
|
urgent: true
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Localisation douleur
|
||||||
|
- label:
|
||||||
|
fr: Horaire dernier passage
|
||||||
|
- label:
|
||||||
|
fr: Traitements en cours
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 17_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 18_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 19_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: Appel famille pour information sur la date de prise en charge
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations: []
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 20_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 21_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 22_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
6
resources/translation_override/README.md
Normal file
6
resources/translation_override/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
In this directory, you find an example of file for the command `chill:main:override_translation`.
|
||||||
|
|
||||||
|
This file contains a list of translations to override in the translation catalogue. Each entry is a dictionary with two keys: `from` and `to`. The `from` key contains the original translation string, and the `to` key contains the replacement string.
|
||||||
|
|
||||||
|
The command `chill:main:override_translation` uses this file to generate a new translation catalogue with the specified overrides applied.
|
||||||
|
|
||||||
8
resources/translation_override/overrides.yaml
Normal file
8
resources/translation_override/overrides.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- {from: "de l'usager", to: "du patient"}
|
||||||
|
- {from: "l'usager", to: "le patient"}
|
||||||
|
- {from: "L'usager", to: "Le patient"}
|
||||||
|
- {from: "d'usagers", to: "de patients"}
|
||||||
|
- {from: "usagers", to: "patients"}
|
||||||
|
- {from: "Usagers", to: "Patients"}
|
||||||
|
- {from: "usager", to: "patient"}
|
||||||
|
- {from: "Usager", to: "Patient"}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<concerned-groups v-if="hasPerson" />
|
<concerned-groups v-if="hasPerson" />
|
||||||
<social-issues-acc v-if="hasSocialIssues" />
|
<social-issues-acc v-if="hasSocialIssues" />
|
||||||
<location v-if="hasLocation" />
|
<location v-if="hasLocation" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -10,12 +10,12 @@ import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
|
|||||||
import Location from "./components/Location.vue";
|
import Location from "./components/Location.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
||||||
components: {
|
components: {
|
||||||
ConcernedGroups,
|
ConcernedGroups,
|
||||||
SocialIssuesAcc,
|
SocialIssuesAcc,
|
||||||
Location,
|
Location,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,46 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||||
<persons-bloc
|
<persons-bloc
|
||||||
v-for="bloc in contextPersonsBlocs"
|
v-for="bloc in contextPersonsBlocs"
|
||||||
:key="bloc.key"
|
:key="bloc.key"
|
||||||
:bloc="bloc"
|
:bloc="bloc"
|
||||||
:bloc-width="getBlocWidth"
|
:bloc-width="getBlocWidth"
|
||||||
:set-persons-in-bloc="setPersonsInBloc"
|
:set-persons-in-bloc="setPersonsInBloc"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"
|
||||||
getContext === 'accompanyingCourse' &&
|
>
|
||||||
suggestedEntities.length > 0
|
<ul class="list-suggest add-items inline">
|
||||||
"
|
<li
|
||||||
|
v-for="(p, i) in suggestedEntities"
|
||||||
|
@click="addSuggestedEntity(p)"
|
||||||
|
:key="`suggestedEntities-${i}`"
|
||||||
>
|
>
|
||||||
<ul class="list-suggest add-items inline">
|
<person-text v-if="p.type === 'person'" :person="p" />
|
||||||
<li
|
<span v-else>{{ p.text }}</span>
|
||||||
v-for="(p, i) in suggestedEntities"
|
</li>
|
||||||
@click="addSuggestedEntity(p)"
|
</ul>
|
||||||
:key="`suggestedEntities-${i}`"
|
</div>
|
||||||
>
|
|
||||||
<person-text v-if="p.type === 'person'" :person="p" />
|
|
||||||
<span v-else>{{ p.text }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li class="add-persons">
|
<li class="add-persons">
|
||||||
<add-persons
|
<add-persons
|
||||||
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
v-bind:key="addPersons.key"
|
v-bind:key="addPersons.key"
|
||||||
v-bind:options="addPersonsOptions"
|
v-bind:options="addPersonsOptions"
|
||||||
@addNewPersons="addNewPersons"
|
@addNewPersons="addNewPersons"
|
||||||
ref="addPersons"
|
ref="addPersons"
|
||||||
>
|
>
|
||||||
</add-persons>
|
</add-persons>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -49,208 +46,208 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
|||||||
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
||||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||||
import {
|
import {
|
||||||
ACTIVITY_BLOC_PERSONS,
|
ACTIVITY_BLOC_PERSONS,
|
||||||
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||||
ACTIVITY_BLOC_THIRDPARTY,
|
ACTIVITY_BLOC_THIRDPARTY,
|
||||||
ACTIVITY_BLOC_USERS,
|
ACTIVITY_BLOC_USERS,
|
||||||
ACTIVITY_ADD_PERSONS,
|
ACTIVITY_ADD_PERSONS,
|
||||||
trans,
|
trans,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ConcernedGroups",
|
name: "ConcernedGroups",
|
||||||
components: {
|
components: {
|
||||||
AddPersons,
|
AddPersons,
|
||||||
PersonsBloc,
|
PersonsBloc,
|
||||||
PersonText,
|
PersonText,
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_ADD_PERSONS,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
personsBlocs: [
|
||||||
|
{
|
||||||
|
key: "persons",
|
||||||
|
title: trans(ACTIVITY_BLOC_PERSONS),
|
||||||
|
persons: [],
|
||||||
|
included: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "personsAssociated",
|
||||||
|
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "personsNotAssociated",
|
||||||
|
title: "activity.bloc_persons_not_associated",
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "thirdparty",
|
||||||
|
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.thirdPartiesVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "users",
|
||||||
|
title: trans(ACTIVITY_BLOC_USERS),
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.usersVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addPersons: {
|
||||||
|
key: "activity",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isComponentVisible() {
|
||||||
|
return window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0 ||
|
||||||
|
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
||||||
|
window.activity.activityType.usersVisible !== 0
|
||||||
|
: true;
|
||||||
},
|
},
|
||||||
setup() {
|
...mapState({
|
||||||
return {
|
persons: (state) => state.activity.persons,
|
||||||
trans,
|
thirdParties: (state) => state.activity.thirdParties,
|
||||||
ACTIVITY_ADD_PERSONS,
|
users: (state) => state.activity.users,
|
||||||
};
|
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
||||||
|
}),
|
||||||
|
...mapGetters(["suggestedEntities"]),
|
||||||
|
getContext() {
|
||||||
|
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
||||||
},
|
},
|
||||||
data() {
|
contextPersonsBlocs() {
|
||||||
return {
|
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
||||||
personsBlocs: [
|
|
||||||
{
|
|
||||||
key: "persons",
|
|
||||||
title: trans(ACTIVITY_BLOC_PERSONS),
|
|
||||||
persons: [],
|
|
||||||
included: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "personsAssociated",
|
|
||||||
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
|
||||||
persons: [],
|
|
||||||
included: window.activity
|
|
||||||
? window.activity.activityType.personsVisible !== 0
|
|
||||||
: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "personsNotAssociated",
|
|
||||||
title: "activity.bloc_persons_not_associated",
|
|
||||||
persons: [],
|
|
||||||
included: window.activity
|
|
||||||
? window.activity.activityType.personsVisible !== 0
|
|
||||||
: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "thirdparty",
|
|
||||||
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
|
||||||
persons: [],
|
|
||||||
included: window.activity
|
|
||||||
? window.activity.activityType.thirdPartiesVisible !== 0
|
|
||||||
: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "users",
|
|
||||||
title: trans(ACTIVITY_BLOC_USERS),
|
|
||||||
persons: [],
|
|
||||||
included: window.activity
|
|
||||||
? window.activity.activityType.usersVisible !== 0
|
|
||||||
: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
addPersons: {
|
|
||||||
key: "activity",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
addPersonsOptions() {
|
||||||
isComponentVisible() {
|
let optionsType = [];
|
||||||
return window.activity
|
if (window.activity) {
|
||||||
? window.activity.activityType.personsVisible !== 0 ||
|
if (window.activity.activityType.personsVisible !== 0) {
|
||||||
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
optionsType.push("person");
|
||||||
window.activity.activityType.usersVisible !== 0
|
}
|
||||||
: true;
|
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
||||||
},
|
optionsType.push("thirdparty");
|
||||||
...mapState({
|
}
|
||||||
persons: (state) => state.activity.persons,
|
if (window.activity.activityType.usersVisible !== 0) {
|
||||||
thirdParties: (state) => state.activity.thirdParties,
|
optionsType.push("user");
|
||||||
users: (state) => state.activity.users,
|
}
|
||||||
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
} else {
|
||||||
}),
|
optionsType = ["person", "thirdparty", "user"];
|
||||||
...mapGetters(["suggestedEntities"]),
|
}
|
||||||
getContext() {
|
return {
|
||||||
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
type: optionsType,
|
||||||
},
|
priority: null,
|
||||||
contextPersonsBlocs() {
|
uniq: false,
|
||||||
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
button: {
|
||||||
},
|
size: "btn-sm",
|
||||||
addPersonsOptions() {
|
|
||||||
let optionsType = [];
|
|
||||||
if (window.activity) {
|
|
||||||
if (window.activity.activityType.personsVisible !== 0) {
|
|
||||||
optionsType.push("person");
|
|
||||||
}
|
|
||||||
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
|
||||||
optionsType.push("thirdparty");
|
|
||||||
}
|
|
||||||
if (window.activity.activityType.usersVisible !== 0) {
|
|
||||||
optionsType.push("user");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
optionsType = ["person", "thirdparty", "user"];
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: optionsType,
|
|
||||||
priority: null,
|
|
||||||
uniq: false,
|
|
||||||
button: {
|
|
||||||
size: "btn-sm",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getBlocWidth() {
|
|
||||||
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
getBlocWidth() {
|
||||||
this.setPersonsInBloc();
|
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
||||||
},
|
},
|
||||||
methods: {
|
},
|
||||||
setPersonsInBloc() {
|
mounted() {
|
||||||
let groups;
|
this.setPersonsInBloc();
|
||||||
if (this.accompanyingCourse) {
|
},
|
||||||
groups = this.splitPersonsInGroups();
|
methods: {
|
||||||
}
|
setPersonsInBloc() {
|
||||||
this.personsBlocs.forEach((bloc) => {
|
let groups;
|
||||||
if (this.accompanyingCourse) {
|
if (this.accompanyingCourse) {
|
||||||
switch (bloc.key) {
|
groups = this.splitPersonsInGroups();
|
||||||
case "personsAssociated":
|
}
|
||||||
bloc.persons = groups.personsAssociated;
|
this.personsBlocs.forEach((bloc) => {
|
||||||
bloc.included = true;
|
if (this.accompanyingCourse) {
|
||||||
break;
|
switch (bloc.key) {
|
||||||
case "personsNotAssociated":
|
case "personsAssociated":
|
||||||
bloc.persons = groups.personsNotAssociated;
|
bloc.persons = groups.personsAssociated;
|
||||||
bloc.included = true;
|
bloc.included = true;
|
||||||
break;
|
break;
|
||||||
}
|
case "personsNotAssociated":
|
||||||
} else {
|
bloc.persons = groups.personsNotAssociated;
|
||||||
switch (bloc.key) {
|
bloc.included = true;
|
||||||
case "persons":
|
break;
|
||||||
bloc.persons = this.persons;
|
}
|
||||||
bloc.included = true;
|
} else {
|
||||||
break;
|
switch (bloc.key) {
|
||||||
}
|
case "persons":
|
||||||
}
|
bloc.persons = this.persons;
|
||||||
switch (bloc.key) {
|
bloc.included = true;
|
||||||
case "thirdparty":
|
break;
|
||||||
bloc.persons = this.thirdParties;
|
}
|
||||||
break;
|
}
|
||||||
case "users":
|
switch (bloc.key) {
|
||||||
bloc.persons = this.users;
|
case "thirdparty":
|
||||||
break;
|
bloc.persons = this.thirdParties;
|
||||||
}
|
break;
|
||||||
}, groups);
|
case "users":
|
||||||
},
|
bloc.persons = this.users;
|
||||||
splitPersonsInGroups() {
|
break;
|
||||||
let personsAssociated = [];
|
}
|
||||||
let personsNotAssociated = this.persons;
|
}, groups);
|
||||||
let participations = this.getCourseParticipations();
|
|
||||||
this.persons.forEach((person) => {
|
|
||||||
participations.forEach((participation) => {
|
|
||||||
if (person.id === participation.id) {
|
|
||||||
//console.log(person.id);
|
|
||||||
personsAssociated.push(person);
|
|
||||||
personsNotAssociated = personsNotAssociated.filter(
|
|
||||||
(p) => p !== person,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
personsAssociated: personsAssociated,
|
|
||||||
personsNotAssociated: personsNotAssociated,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getCourseParticipations() {
|
|
||||||
let participations = [];
|
|
||||||
this.accompanyingCourse.participations.forEach((participation) => {
|
|
||||||
if (!participation.endDate) {
|
|
||||||
participations.push(participation.person);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return participations;
|
|
||||||
},
|
|
||||||
addNewPersons({ selected, modal }) {
|
|
||||||
console.log("@@@ CLICK button addNewPersons", selected);
|
|
||||||
selected.forEach((item) => {
|
|
||||||
this.$store.dispatch("addPersonsInvolved", item);
|
|
||||||
}, this);
|
|
||||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
|
||||||
modal.showModal = false;
|
|
||||||
this.setPersonsInBloc();
|
|
||||||
},
|
|
||||||
addSuggestedEntity(person) {
|
|
||||||
this.$store.dispatch("addPersonsInvolved", {
|
|
||||||
result: person,
|
|
||||||
type: "person",
|
|
||||||
});
|
|
||||||
this.setPersonsInBloc();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
splitPersonsInGroups() {
|
||||||
|
let personsAssociated = [];
|
||||||
|
let personsNotAssociated = this.persons;
|
||||||
|
let participations = this.getCourseParticipations();
|
||||||
|
this.persons.forEach((person) => {
|
||||||
|
participations.forEach((participation) => {
|
||||||
|
if (person.id === participation.id) {
|
||||||
|
//console.log(person.id);
|
||||||
|
personsAssociated.push(person);
|
||||||
|
personsNotAssociated = personsNotAssociated.filter(
|
||||||
|
(p) => p !== person,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
personsAssociated: personsAssociated,
|
||||||
|
personsNotAssociated: personsNotAssociated,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getCourseParticipations() {
|
||||||
|
let participations = [];
|
||||||
|
this.accompanyingCourse.participations.forEach((participation) => {
|
||||||
|
if (!participation.endDate) {
|
||||||
|
participations.push(participation.person);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return participations;
|
||||||
|
},
|
||||||
|
addNewPersons({ selected, modal }) {
|
||||||
|
console.log("@@@ CLICK button addNewPersons", selected);
|
||||||
|
selected.forEach((item) => {
|
||||||
|
this.$store.dispatch("addPersonsInvolved", item);
|
||||||
|
}, this);
|
||||||
|
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||||
|
modal.showModal = false;
|
||||||
|
this.setPersonsInBloc();
|
||||||
|
},
|
||||||
|
addSuggestedEntity(person) {
|
||||||
|
this.$store.dispatch("addPersonsInvolved", {
|
||||||
|
result: person,
|
||||||
|
type: "person",
|
||||||
|
});
|
||||||
|
this.setPersonsInBloc();
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<li>
|
<li>
|
||||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||||
<span class="chill_denomination">
|
<span class="chill_denomination">
|
||||||
<person-text :person="person" :is-cut="true" />
|
<person-text :person="person" :is-cut="true" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PersonBadge",
|
name: "PersonBadge",
|
||||||
props: ["person"],
|
props: ["person"],
|
||||||
components: {
|
components: {
|
||||||
PersonText,
|
PersonText,
|
||||||
},
|
},
|
||||||
// computed: {
|
// computed: {
|
||||||
// textCutted() {
|
// textCutted() {
|
||||||
// let more = (this.person.text.length > 15) ?'…' : '';
|
// let more = (this.person.text.length > 15) ?'…' : '';
|
||||||
// return this.person.text.slice(0,15) + more;
|
// return this.person.text.slice(0,15) + more;
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
emits: ["remove"],
|
emits: ["remove"],
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<h4>{{ $t(bloc.title) }}</h4>
|
<h4>{{ $t(bloc.title) }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<ul class="list-suggest remove-items">
|
<ul class="list-suggest remove-items">
|
||||||
<person-badge
|
<person-badge
|
||||||
v-for="person in bloc.persons"
|
v-for="person in bloc.persons"
|
||||||
:key="person.id"
|
:key="person.id"
|
||||||
:person="person"
|
:person="person"
|
||||||
@remove="removePerson"
|
@remove="removePerson"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PersonBadge from "./PersonBadge.vue";
|
import PersonBadge from "./PersonBadge.vue";
|
||||||
export default {
|
export default {
|
||||||
name: "PersonsBloc",
|
name: "PersonsBloc",
|
||||||
components: {
|
components: {
|
||||||
PersonBadge,
|
PersonBadge,
|
||||||
},
|
},
|
||||||
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
||||||
methods: {
|
methods: {
|
||||||
removePerson(item) {
|
removePerson(item) {
|
||||||
console.log("@@ CLICK remove person: item", item);
|
console.log("@@ CLICK remove person: item", item);
|
||||||
this.$store.dispatch("removePersonInvolved", item);
|
this.$store.dispatch("removePersonInvolved", item);
|
||||||
this.setPersonsInBloc();
|
this.setPersonsInBloc();
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#location">
|
<teleport to="#location">
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<label :class="locationClassList">
|
<label :class="locationClassList">
|
||||||
{{ trans(ACTIVITY_LOCATION) }}
|
{{ trans(ACTIVITY_LOCATION) }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="selectLocation"
|
name="selectLocation"
|
||||||
id="selectLocation"
|
id="selectLocation"
|
||||||
label="name"
|
label="name"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
open-direction="top"
|
open-direction="top"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||||
:custom-label="customLabel"
|
:custom-label="customLabel"
|
||||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||||
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||||
:options="availableLocations"
|
:options="availableLocations"
|
||||||
group-values="locations"
|
group-values="locations"
|
||||||
group-label="locationGroup"
|
group-label="locationGroup"
|
||||||
v-model="location"
|
v-model="location"
|
||||||
/>
|
/>
|
||||||
<new-location v-bind:available-locations="availableLocations" />
|
<new-location v-bind:available-locations="availableLocations" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -35,60 +35,60 @@ import VueMultiselect from "vue-multiselect";
|
|||||||
import NewLocation from "./Location/NewLocation.vue";
|
import NewLocation from "./Location/NewLocation.vue";
|
||||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||||
import {
|
import {
|
||||||
trans,
|
trans,
|
||||||
ACTIVITY_LOCATION,
|
ACTIVITY_LOCATION,
|
||||||
ACTIVITY_CHOOSE_LOCATION,
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
MULTISELECT_SELECT_LABEL,
|
MULTISELECT_SELECT_LABEL,
|
||||||
MULTISELECT_DESELECT_LABEL,
|
MULTISELECT_DESELECT_LABEL,
|
||||||
MULTISELECT_SELECTED_LABEL,
|
MULTISELECT_SELECTED_LABEL,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Location",
|
name: "Location",
|
||||||
components: {
|
components: {
|
||||||
NewLocation,
|
NewLocation,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_LOCATION,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
|
MULTISELECT_SELECT_LABEL,
|
||||||
|
MULTISELECT_DESELECT_LABEL,
|
||||||
|
MULTISELECT_SELECTED_LABEL,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["activity", "availableLocations"]),
|
||||||
|
...mapGetters(["suggestedEntities"]),
|
||||||
|
location: {
|
||||||
|
get() {
|
||||||
|
return this.activity.location;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.dispatch("updateLocation", value);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
},
|
||||||
return {
|
methods: {
|
||||||
trans,
|
labelAccompanyingCourseLocation(value) {
|
||||||
ACTIVITY_LOCATION,
|
return `${value.address.text} (${localizeString(value.locationType.title)})`;
|
||||||
ACTIVITY_CHOOSE_LOCATION,
|
|
||||||
MULTISELECT_SELECT_LABEL,
|
|
||||||
MULTISELECT_DESELECT_LABEL,
|
|
||||||
MULTISELECT_SELECTED_LABEL,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
data() {
|
customLabel(value) {
|
||||||
return {
|
return value.locationType
|
||||||
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
? value.name
|
||||||
};
|
? value.name === "__AccompanyingCourseLocation__"
|
||||||
},
|
? this.labelAccompanyingCourseLocation(value)
|
||||||
computed: {
|
: `${value.name} (${localizeString(value.locationType.title)})`
|
||||||
...mapState(["activity", "availableLocations"]),
|
: localizeString(value.locationType.title)
|
||||||
...mapGetters(["suggestedEntities"]),
|
: "";
|
||||||
location: {
|
|
||||||
get() {
|
|
||||||
return this.activity.location;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$store.dispatch("updateLocation", value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
labelAccompanyingCourseLocation(value) {
|
|
||||||
return `${value.address.text} (${localizeString(value.locationType.title)})`;
|
|
||||||
},
|
|
||||||
customLabel(value) {
|
|
||||||
return value.locationType
|
|
||||||
? value.name
|
|
||||||
? value.name === "__AccompanyingCourseLocation__"
|
|
||||||
? this.labelAccompanyingCourseLocation(value)
|
|
||||||
: `${value.name} (${localizeString(value.locationType.title)})`
|
|
||||||
: localizeString(value.locationType.title)
|
|
||||||
: "";
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,123 +1,114 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-sm btn-create" @click="openModal">
|
<a class="btn btn-sm btn-create" @click="openModal">
|
||||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<modal
|
<modal
|
||||||
v-if="modal.showModal"
|
v-if="modal.showModal"
|
||||||
:modalDialogClass="modal.modalDialogClass"
|
:modalDialogClass="modal.modalDialogClass"
|
||||||
@close="modal.showModal = false"
|
@close="modal.showModal = false"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<h3 class="modal-title">
|
<h3 class="modal-title">
|
||||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<form>
|
<form>
|
||||||
<div class="alert alert-warning" v-if="errors.length">
|
<div class="alert alert-warning" v-if="errors.length">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(e, i) in errors" :key="i">
|
<li v-for="(e, i) in errors" :key="i">
|
||||||
{{ e }}
|
{{ e }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<select
|
<select
|
||||||
class="form-select form-select-lg"
|
class="form-select form-select-lg"
|
||||||
id="type"
|
id="type"
|
||||||
required
|
required
|
||||||
v-model="selectType"
|
v-model="selectType"
|
||||||
>
|
>
|
||||||
<option selected disabled value="">
|
<option selected disabled value="">
|
||||||
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
||||||
v-for="t in locationTypes"
|
{{ localizeString(t.title) }}
|
||||||
:value="t"
|
</option>
|
||||||
:key="t.id"
|
</select>
|
||||||
>
|
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
|
||||||
{{ localizeString(t.title) }}
|
</div>
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<label>{{
|
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
|
|
||||||
}}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="name"
|
id="name"
|
||||||
v-model="inputName"
|
v-model="inputName"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="name">{{
|
<label for="name">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<add-address
|
<add-address
|
||||||
:context="addAddress.context"
|
:context="addAddress.context"
|
||||||
:options="addAddress.options"
|
:options="addAddress.options"
|
||||||
:addressChangedCallback="submitNewAddress"
|
:addressChangedCallback="submitNewAddress"
|
||||||
v-if="showAddAddress"
|
v-if="showAddAddress"
|
||||||
ref="addAddress"
|
ref="addAddress"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="phonenumber1"
|
id="phonenumber1"
|
||||||
v-model="inputPhonenumber1"
|
v-model="inputPhonenumber1"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="phonenumber1">{{
|
<label for="phonenumber1">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="phonenumber2"
|
id="phonenumber2"
|
||||||
v-model="inputPhonenumber2"
|
v-model="inputPhonenumber2"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="phonenumber2">{{
|
<label for="phonenumber2">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="email"
|
id="email"
|
||||||
v-model="inputEmail"
|
v-model="inputEmail"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="email">{{
|
<label for="email">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button
|
<button class="btn btn-save" @click.prevent="saveNewLocation">
|
||||||
class="btn btn-save"
|
{{ trans(SAVE) }}
|
||||||
@click.prevent="saveNewLocation"
|
</button>
|
||||||
>
|
</template>
|
||||||
{{ trans(SAVE) }}
|
</modal>
|
||||||
</button>
|
</teleport>
|
||||||
</template>
|
</div>
|
||||||
</modal>
|
|
||||||
</teleport>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -128,237 +119,236 @@ import { getLocationTypes } from "../../api";
|
|||||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||||
import {
|
import {
|
||||||
SAVE,
|
SAVE,
|
||||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||||
ACTIVITY_CREATE_NEW_LOCATION,
|
ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
trans,
|
trans,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NewLocation",
|
name: "NewLocation",
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
AddAddress,
|
AddAddress,
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
SAVE,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
|
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||||
|
ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ["availableLocations"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
errors: [],
|
||||||
|
selected: {
|
||||||
|
type: null,
|
||||||
|
name: null,
|
||||||
|
addressId: null,
|
||||||
|
phonenumber1: null,
|
||||||
|
phonenumber2: null,
|
||||||
|
email: null,
|
||||||
|
},
|
||||||
|
locationTypes: [],
|
||||||
|
modal: {
|
||||||
|
showModal: false,
|
||||||
|
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||||
|
},
|
||||||
|
addAddress: {
|
||||||
|
options: {
|
||||||
|
button: {
|
||||||
|
text: {
|
||||||
|
create: "activity.create_address",
|
||||||
|
edit: "activity.edit_address",
|
||||||
|
},
|
||||||
|
size: "btn-sm",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
create: "activity.create_address",
|
||||||
|
edit: "activity.edit_address",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
target: {
|
||||||
|
//name, id
|
||||||
|
},
|
||||||
|
edit: false,
|
||||||
|
addressId: null,
|
||||||
|
defaults: window.addaddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["activity"]),
|
||||||
|
selectType: {
|
||||||
|
get() {
|
||||||
|
return this.selected.type;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.type = value;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
inputName: {
|
||||||
return {
|
get() {
|
||||||
trans,
|
return this.selected.name;
|
||||||
SAVE,
|
},
|
||||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
set(value) {
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
this.selected.name = value;
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
},
|
||||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
},
|
||||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
inputEmail: {
|
||||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
get() {
|
||||||
ACTIVITY_CREATE_NEW_LOCATION,
|
return this.selected.email;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.email = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputPhonenumber1: {
|
||||||
|
get() {
|
||||||
|
return this.selected.phonenumber1;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.phonenumber1 = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputPhonenumber2: {
|
||||||
|
get() {
|
||||||
|
return this.selected.phonenumber2;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.phonenumber2 = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasPhonenumber1() {
|
||||||
|
return (
|
||||||
|
this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""
|
||||||
|
);
|
||||||
|
},
|
||||||
|
showAddAddress() {
|
||||||
|
let cond = false;
|
||||||
|
if (this.selected.type) {
|
||||||
|
if (this.selected.type.addressRequired !== "never") {
|
||||||
|
cond = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cond;
|
||||||
|
},
|
||||||
|
showContactData() {
|
||||||
|
let cond = false;
|
||||||
|
if (this.selected.type) {
|
||||||
|
if (this.selected.type.contactData !== "never") {
|
||||||
|
cond = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cond;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getLocationTypesList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
localizeString,
|
||||||
|
checkForm() {
|
||||||
|
let cond = true;
|
||||||
|
this.errors = [];
|
||||||
|
if (!this.selected.type) {
|
||||||
|
this.errors.push("Type de localisation requis");
|
||||||
|
cond = false;
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
this.selected.type.addressRequired === "required" &&
|
||||||
|
!this.selected.addressId
|
||||||
|
) {
|
||||||
|
this.errors.push("Adresse requise");
|
||||||
|
cond = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.selected.type.contactData === "required" &&
|
||||||
|
!this.selected.phonenumber1
|
||||||
|
) {
|
||||||
|
this.errors.push("Numéro de téléphone requis");
|
||||||
|
cond = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.selected.type.contactData === "required" &&
|
||||||
|
!this.selected.email
|
||||||
|
) {
|
||||||
|
this.errors.push("Adresse email requise");
|
||||||
|
cond = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cond;
|
||||||
|
},
|
||||||
|
getLocationTypesList() {
|
||||||
|
getLocationTypes().then((results) => {
|
||||||
|
this.locationTypes = results.filter(
|
||||||
|
(t) => t.availableForUsers === true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openModal() {
|
||||||
|
this.modal.showModal = true;
|
||||||
|
},
|
||||||
|
saveNewLocation() {
|
||||||
|
if (this.checkForm()) {
|
||||||
|
let body = {
|
||||||
|
type: "location",
|
||||||
|
name: this.selected.name,
|
||||||
|
locationType: {
|
||||||
|
id: this.selected.type.id,
|
||||||
|
type: "location-type",
|
||||||
|
},
|
||||||
|
phonenumber1: this.selected.phonenumber1,
|
||||||
|
phonenumber2: this.selected.phonenumber2,
|
||||||
|
email: this.selected.email,
|
||||||
};
|
};
|
||||||
},
|
if (this.selected.addressId) {
|
||||||
props: ["availableLocations"],
|
body = Object.assign(body, {
|
||||||
data() {
|
address: {
|
||||||
return {
|
id: this.selected.addressId,
|
||||||
errors: [],
|
|
||||||
selected: {
|
|
||||||
type: null,
|
|
||||||
name: null,
|
|
||||||
addressId: null,
|
|
||||||
phonenumber1: null,
|
|
||||||
phonenumber2: null,
|
|
||||||
email: null,
|
|
||||||
},
|
},
|
||||||
locationTypes: [],
|
});
|
||||||
modal: {
|
}
|
||||||
showModal: false,
|
|
||||||
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
|
||||||
},
|
|
||||||
addAddress: {
|
|
||||||
options: {
|
|
||||||
button: {
|
|
||||||
text: {
|
|
||||||
create: "activity.create_address",
|
|
||||||
edit: "activity.edit_address",
|
|
||||||
},
|
|
||||||
size: "btn-sm",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
create: "activity.create_address",
|
|
||||||
edit: "activity.edit_address",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
target: {
|
|
||||||
//name, id
|
|
||||||
},
|
|
||||||
edit: false,
|
|
||||||
addressId: null,
|
|
||||||
defaults: window.addaddress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(["activity"]),
|
|
||||||
selectType: {
|
|
||||||
get() {
|
|
||||||
return this.selected.type;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.type = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputName: {
|
|
||||||
get() {
|
|
||||||
return this.selected.name;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.name = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputEmail: {
|
|
||||||
get() {
|
|
||||||
return this.selected.email;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.email = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputPhonenumber1: {
|
|
||||||
get() {
|
|
||||||
return this.selected.phonenumber1;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.phonenumber1 = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputPhonenumber2: {
|
|
||||||
get() {
|
|
||||||
return this.selected.phonenumber2;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.phonenumber2 = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hasPhonenumber1() {
|
|
||||||
return (
|
|
||||||
this.selected.phonenumber1 !== null &&
|
|
||||||
this.selected.phonenumber1 !== ""
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showAddAddress() {
|
|
||||||
let cond = false;
|
|
||||||
if (this.selected.type) {
|
|
||||||
if (this.selected.type.addressRequired !== "never") {
|
|
||||||
cond = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cond;
|
|
||||||
},
|
|
||||||
showContactData() {
|
|
||||||
let cond = false;
|
|
||||||
if (this.selected.type) {
|
|
||||||
if (this.selected.type.contactData !== "never") {
|
|
||||||
cond = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cond;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getLocationTypesList();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
localizeString,
|
|
||||||
checkForm() {
|
|
||||||
let cond = true;
|
|
||||||
this.errors = [];
|
|
||||||
if (!this.selected.type) {
|
|
||||||
this.errors.push("Type de localisation requis");
|
|
||||||
cond = false;
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
this.selected.type.addressRequired === "required" &&
|
|
||||||
!this.selected.addressId
|
|
||||||
) {
|
|
||||||
this.errors.push("Adresse requise");
|
|
||||||
cond = false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.selected.type.contactData === "required" &&
|
|
||||||
!this.selected.phonenumber1
|
|
||||||
) {
|
|
||||||
this.errors.push("Numéro de téléphone requis");
|
|
||||||
cond = false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.selected.type.contactData === "required" &&
|
|
||||||
!this.selected.email
|
|
||||||
) {
|
|
||||||
this.errors.push("Adresse email requise");
|
|
||||||
cond = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cond;
|
|
||||||
},
|
|
||||||
getLocationTypesList() {
|
|
||||||
getLocationTypes().then((results) => {
|
|
||||||
this.locationTypes = results.filter(
|
|
||||||
(t) => t.availableForUsers === true,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
openModal() {
|
|
||||||
this.modal.showModal = true;
|
|
||||||
},
|
|
||||||
saveNewLocation() {
|
|
||||||
if (this.checkForm()) {
|
|
||||||
let body = {
|
|
||||||
type: "location",
|
|
||||||
name: this.selected.name,
|
|
||||||
locationType: {
|
|
||||||
id: this.selected.type.id,
|
|
||||||
type: "location-type",
|
|
||||||
},
|
|
||||||
phonenumber1: this.selected.phonenumber1,
|
|
||||||
phonenumber2: this.selected.phonenumber2,
|
|
||||||
email: this.selected.email,
|
|
||||||
};
|
|
||||||
if (this.selected.addressId) {
|
|
||||||
body = Object.assign(body, {
|
|
||||||
address: {
|
|
||||||
id: this.selected.addressId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
makeFetch("POST", "/api/1.0/main/location.json", body)
|
makeFetch("POST", "/api/1.0/main/location.json", body)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.$store.dispatch("addAvailableLocationGroup", {
|
this.$store.dispatch("addAvailableLocationGroup", {
|
||||||
locationGroup: "Localisations nouvellement créées",
|
locationGroup: "Localisations nouvellement créées",
|
||||||
locations: [response],
|
locations: [response],
|
||||||
});
|
});
|
||||||
this.$store.dispatch("updateLocation", response);
|
this.$store.dispatch("updateLocation", response);
|
||||||
this.modal.showModal = false;
|
this.modal.showModal = false;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.name === "ValidationException") {
|
if (error.name === "ValidationException") {
|
||||||
for (let v of error.violations) {
|
for (let v of error.violations) {
|
||||||
this.errors.push(v);
|
this.errors.push(v);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.errors.push("An error occurred");
|
this.errors.push("An error occurred");
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
submitNewAddress(payload) {
|
}
|
||||||
this.selected.addressId = payload.addressId;
|
|
||||||
this.addAddress.context.addressId = payload.addressId;
|
|
||||||
this.addAddress.context.edit = true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
submitNewAddress(payload) {
|
||||||
|
this.selected.addressId = payload.addressId;
|
||||||
|
this.addAddress.context.addressId = payload.addressId;
|
||||||
|
this.addAddress.context.edit = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="inline-choice">
|
<span class="inline-choice">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
name="action"
|
name="action"
|
||||||
:id="action.id"
|
:id="action.id"
|
||||||
:value="action"
|
:value="action"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" :for="action.id">
|
<label class="form-check-label" :for="action.id">
|
||||||
<span class="badge bg-light text-dark" :title="action.text">{{
|
<span class="badge bg-light text-dark" :title="action.text">{{
|
||||||
action.text
|
action.text
|
||||||
}}</span>
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "CheckSocialAction",
|
name: "CheckSocialAction",
|
||||||
props: ["action", "selection"],
|
props: ["action", "selection"],
|
||||||
emits: ["updateSelected"],
|
emits: ["updateSelected"],
|
||||||
computed: {
|
computed: {
|
||||||
selected: {
|
selected: {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit("updateSelected", value);
|
this.$emit("updateSelected", value);
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.selection;
|
return this.selection;
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -41,13 +41,13 @@ export default {
|
|||||||
@import "ChillPersonAssets/chill/scss/mixins";
|
@import "ChillPersonAssets/chill/scss/mixins";
|
||||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
span.badge {
|
span.badge {
|
||||||
@include badge_social($social-action-color);
|
@include badge_social($social-action-color);
|
||||||
font-size: 95%;
|
font-size: 95%;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
max-width: 100%; /* Adjust as needed */
|
max-width: 100%; /* Adjust as needed */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,38 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="inline-choice">
|
<span class="inline-choice">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
name="issue"
|
name="issue"
|
||||||
:id="issue.id"
|
:id="issue.id"
|
||||||
:value="issue"
|
:value="issue"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" :for="issue.id">
|
<label class="form-check-label" :for="issue.id">
|
||||||
<span class="badge bg-chill-l-gray text-dark">{{
|
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
|
||||||
issue.text
|
</label>
|
||||||
}}</span>
|
</div>
|
||||||
</label>
|
</span>
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "CheckSocialIssue",
|
name: "CheckSocialIssue",
|
||||||
props: ["issue", "selection"],
|
props: ["issue", "selection"],
|
||||||
emits: ["updateSelected"],
|
emits: ["updateSelected"],
|
||||||
computed: {
|
computed: {
|
||||||
selected: {
|
selected: {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit("updateSelected", value);
|
this.$emit("updateSelected", value);
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.selection;
|
return this.selection;
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -41,9 +39,9 @@ export default {
|
|||||||
@import "ChillPersonAssets/chill/scss/mixins";
|
@import "ChillPersonAssets/chill/scss/mixins";
|
||||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
span.badge {
|
span.badge {
|
||||||
@include badge_social($social-issue-color);
|
@include badge_social($social-issue-color);
|
||||||
font-size: 95%;
|
font-size: 95%;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\Controller;
|
|||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\CalendarBundle\Form\CalendarType;
|
use Chill\CalendarBundle\Form\CalendarType;
|
||||||
|
use Chill\CalendarBundle\Form\CancelType;
|
||||||
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
|
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
|
||||||
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
|
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
|
||||||
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
||||||
@@ -30,6 +31,7 @@ use Chill\PersonBundle\Repository\PersonRepository;
|
|||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use http\Exception\UnexpectedValueException;
|
use http\Exception\UnexpectedValueException;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
@@ -60,6 +62,7 @@ class CalendarController extends AbstractController
|
|||||||
private readonly UserRepositoryInterface $userRepository,
|
private readonly UserRepositoryInterface $userRepository,
|
||||||
private readonly TranslatorInterface $translator,
|
private readonly TranslatorInterface $translator,
|
||||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,6 +114,55 @@ class CalendarController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route(path: '/{_locale}/calendar/calendar/{id}/cancel', name: 'chill_calendar_calendar_cancel')]
|
||||||
|
public function cancelAction(Calendar $calendar, Request $request): Response
|
||||||
|
{
|
||||||
|
// Deal with sms being sent or not
|
||||||
|
// Communicate cancellation with the remote calendar.
|
||||||
|
|
||||||
|
$this->denyAccessUnlessGranted(CalendarVoter::EDIT, $calendar);
|
||||||
|
|
||||||
|
[$person, $accompanyingPeriod] = [$calendar->getPerson(), $calendar->getAccompanyingPeriod()];
|
||||||
|
|
||||||
|
$form = $this->createForm(CancelType::class, $calendar);
|
||||||
|
$form->add('submit', SubmitType::class);
|
||||||
|
|
||||||
|
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
||||||
|
$view = '@ChillCalendar/Calendar/cancelCalendarByAccompanyingCourse.html.twig';
|
||||||
|
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]);
|
||||||
|
} elseif ($person instanceof Person) {
|
||||||
|
$view = '@ChillCalendar/Calendar/cancelCalendarByPerson.html.twig';
|
||||||
|
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]);
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException('nor person or accompanying period');
|
||||||
|
}
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
|
||||||
|
$this->logger->notice('A calendar event has been cancelled', [
|
||||||
|
'by_user' => $this->getUser()->getUsername(),
|
||||||
|
'calendar_id' => $calendar->getId(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$calendar->setStatus($calendar::STATUS_CANCELED);
|
||||||
|
$calendar->setSmsStatus($calendar::SMS_CANCEL_PENDING);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', $this->translator->trans('chill_calendar.calendar_canceled'));
|
||||||
|
|
||||||
|
return new RedirectResponse($redirectRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render($view, [
|
||||||
|
'calendar' => $calendar,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'accompanyingCourse' => $accompanyingPeriod,
|
||||||
|
'person' => $person,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit a calendar item.
|
* Edit a calendar item.
|
||||||
*/
|
*/
|
||||||
@@ -266,7 +318,7 @@ class CalendarController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->getUser() instanceof User) {
|
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';
|
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\CalendarBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Repository\InviteRepository;
|
||||||
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
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 InviteRepository $inviteRepository, private readonly PaginatorFactory $paginator, private readonly DocGeneratorTemplateRepositoryInterface $docGeneratorTemplateRepository) {}
|
||||||
|
|
||||||
|
#[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 = count($this->inviteRepository->findBy(['user' => $user]));
|
||||||
|
$paginator = $this->paginator->create($total);
|
||||||
|
|
||||||
|
$invitations = $this->inviteRepository->findBy(
|
||||||
|
['user' => $user],
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
$paginator->getItemsPerPage(),
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
$view = '@ChillCalendar/Invitations/listByUser.html.twig';
|
||||||
|
|
||||||
|
return $this->render($view, [
|
||||||
|
'invitations' => $invitations,
|
||||||
|
'paginator' => $paginator,
|
||||||
|
'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ class LoadCancelReason extends Fixture implements FixtureGroupInterface
|
|||||||
$arr = [
|
$arr = [
|
||||||
['name' => CancelReason::CANCELEDBY_USER],
|
['name' => CancelReason::CANCELEDBY_USER],
|
||||||
['name' => CancelReason::CANCELEDBY_PERSON],
|
['name' => CancelReason::CANCELEDBY_PERSON],
|
||||||
['name' => CancelReason::CANCELEDBY_DONOTCOUNT],
|
['name' => CancelReason::CANCELEDBY_OTHER],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($arr as $a) {
|
foreach ($arr as $a) {
|
||||||
|
|||||||
@@ -269,6 +269,11 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente
|
|||||||
return $this->cancelReason;
|
return $this->cancelReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isCanceled(): bool
|
||||||
|
{
|
||||||
|
return null !== $this->cancelReason;
|
||||||
|
}
|
||||||
|
|
||||||
public function getCenters(): ?iterable
|
public function getCenters(): ?iterable
|
||||||
{
|
{
|
||||||
return match ($this->getContext()) {
|
return match ($this->getContext()) {
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
#[ORM\Table(name: 'chill_calendar.cancel_reason')]
|
#[ORM\Table(name: 'chill_calendar.cancel_reason')]
|
||||||
class CancelReason
|
class CancelReason
|
||||||
{
|
{
|
||||||
final public const CANCELEDBY_DONOTCOUNT = 'CANCELEDBY_DONOTCOUNT';
|
final public const CANCELEDBY_OTHER = 'CANCELEDBY_OTHER';
|
||||||
|
|
||||||
final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON';
|
final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON';
|
||||||
|
|
||||||
final public const CANCELEDBY_USER = 'CANCELEDBY_USER';
|
final public const CANCELEDBY_USER = 'CANCELEDBY_USER';
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
|
||||||
private ?bool $active = null;
|
private bool $active = true;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
|
||||||
private ?string $canceledBy = null;
|
private ?string $canceledBy = null;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use Chill\CalendarBundle\Entity\CancelReason;
|
|||||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
@@ -28,7 +28,14 @@ class CancelReasonType extends AbstractType
|
|||||||
->add('active', CheckboxType::class, [
|
->add('active', CheckboxType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
->add('canceledBy', TextType::class);
|
->add('canceledBy', ChoiceType::class, [
|
||||||
|
'choices' => [
|
||||||
|
'chill_calendar.canceled_by.user' => CancelReason::CANCELEDBY_USER,
|
||||||
|
'chill_calendar.canceled_by.person' => CancelReason::CANCELEDBY_PERSON,
|
||||||
|
'chill_calendar.canceled_by.other' => CancelReason::CANCELEDBY_OTHER,
|
||||||
|
],
|
||||||
|
'required' => true,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
|||||||
42
src/Bundle/ChillCalendarBundle/Form/CancelType.php
Normal file
42
src/Bundle/ChillCalendarBundle/Form/CancelType.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?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\Form;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Entity\CancelReason;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class CancelType extends AbstractType
|
||||||
|
{
|
||||||
|
public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper) {}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder->add('cancelReason', EntityType::class, [
|
||||||
|
'class' => CancelReason::class,
|
||||||
|
'required' => true,
|
||||||
|
'choice_label' => fn (CancelReason $cancelReason) => $this->translatableStringHelper->localize($cancelReason->getName()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Calendar::class,
|
||||||
|
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,13 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
if ($this->security->isGranted('ROLE_USER')) {
|
if ($this->security->isGranted('ROLE_USER')) {
|
||||||
$menu->addChild('My calendar list', [
|
$menu->addChild('My calendar list', [
|
||||||
'route' => 'chill_calendar_calendar_list_my',
|
'route' => 'chill_calendar_calendar_list_my',
|
||||||
|
])
|
||||||
|
->setExtras([
|
||||||
|
'order' => 8,
|
||||||
|
'icon' => 'tasks',
|
||||||
|
]);
|
||||||
|
$menu->addChild('invite.list.title', [
|
||||||
|
'route' => 'chill_calendar_invitations_list_my',
|
||||||
])
|
])
|
||||||
->setExtras([
|
->setExtras([
|
||||||
'order' => 9,
|
'order' => 9,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace Chill\CalendarBundle\Messenger\Doctrine;
|
|||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\CalendarBundle\Messenger\Message\CalendarMessage;
|
use Chill\CalendarBundle\Messenger\Message\CalendarMessage;
|
||||||
use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage;
|
use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
use Doctrine\ORM\Event\PostPersistEventArgs;
|
use Doctrine\ORM\Event\PostPersistEventArgs;
|
||||||
use Doctrine\ORM\Event\PostRemoveEventArgs;
|
use Doctrine\ORM\Event\PostRemoveEventArgs;
|
||||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||||
@@ -31,6 +32,17 @@ class CalendarEntityListener
|
|||||||
{
|
{
|
||||||
public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) {}
|
public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) {}
|
||||||
|
|
||||||
|
private function getAuthenticatedUser(): User
|
||||||
|
{
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new \LogicException('Expected an instance of User.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void
|
public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void
|
||||||
{
|
{
|
||||||
if (!$calendar->preventEnqueueChanges) {
|
if (!$calendar->preventEnqueueChanges) {
|
||||||
@@ -38,7 +50,7 @@ class CalendarEntityListener
|
|||||||
new CalendarMessage(
|
new CalendarMessage(
|
||||||
$calendar,
|
$calendar,
|
||||||
CalendarMessage::CALENDAR_PERSIST,
|
CalendarMessage::CALENDAR_PERSIST,
|
||||||
$this->security->getUser()
|
$this->getAuthenticatedUser()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -50,7 +62,7 @@ class CalendarEntityListener
|
|||||||
$this->messageBus->dispatch(
|
$this->messageBus->dispatch(
|
||||||
new CalendarRemovedMessage(
|
new CalendarRemovedMessage(
|
||||||
$calendar,
|
$calendar,
|
||||||
$this->security->getUser()
|
$this->getAuthenticatedUser()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -58,12 +70,19 @@ class CalendarEntityListener
|
|||||||
|
|
||||||
public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void
|
public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void
|
||||||
{
|
{
|
||||||
if (!$calendar->preventEnqueueChanges) {
|
if ($calendar->getStatus() === $calendar::STATUS_CANCELED) {
|
||||||
|
$this->messageBus->dispatch(
|
||||||
|
new CalendarRemovedMessage(
|
||||||
|
$calendar,
|
||||||
|
$this->getAuthenticatedUser()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} elseif (!$calendar->preventEnqueueChanges) {
|
||||||
$this->messageBus->dispatch(
|
$this->messageBus->dispatch(
|
||||||
new CalendarMessage(
|
new CalendarMessage(
|
||||||
$calendar,
|
$calendar,
|
||||||
CalendarMessage::CALENDAR_UPDATE,
|
CalendarMessage::CALENDAR_UPDATE,
|
||||||
$this->security->getUser()
|
$this->getAuthenticatedUser()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ class CalendarRemovedMessage
|
|||||||
|
|
||||||
public function getRemoteId(): string
|
public function getRemoteId(): string
|
||||||
{
|
{
|
||||||
|
dump($this->remoteId);
|
||||||
|
|
||||||
return $this->remoteId;
|
return $this->remoteId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ class CalendarRepository implements ObjectRepository
|
|||||||
$qb->expr()->eq('c.mainUser', ':user'),
|
$qb->expr()->eq('c.mainUser', ':user'),
|
||||||
$qb->expr()->gte('c.startDate', ':startDate'),
|
$qb->expr()->gte('c.startDate', ':startDate'),
|
||||||
$qb->expr()->lte('c.endDate', ':endDate'),
|
$qb->expr()->lte('c.endDate', ':endDate'),
|
||||||
|
$qb->expr()->isNull('c.cancelReason'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
->setParameters([
|
->setParameters([
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class InviteRepository implements ObjectRepository
|
|||||||
/**
|
/**
|
||||||
* @return array|Invite[]
|
* @return array|Invite[]
|
||||||
*/
|
*/
|
||||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||||
{
|
{
|
||||||
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
Chill\CalendarBundle\Controller\:
|
Chill\CalendarBundle\Controller\:
|
||||||
autowire: true
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
resource: '../../../Controller'
|
resource: '../../../Controller'
|
||||||
tags: ['controller.service_arguments']
|
tags: ['controller.service_arguments']
|
||||||
|
|||||||
@@ -1,76 +1,74 @@
|
|||||||
import { EventInput } from "@fullcalendar/core";
|
import { EventInput } from "@fullcalendar/core";
|
||||||
import {
|
import {
|
||||||
DateTime,
|
DateTime,
|
||||||
Location,
|
Location,
|
||||||
User,
|
User,
|
||||||
UserAssociatedInterface,
|
UserAssociatedInterface,
|
||||||
} from "../../../ChillMainBundle/Resources/public/types";
|
} from "../../../ChillMainBundle/Resources/public/types";
|
||||||
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
|
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
|
||||||
|
|
||||||
export interface CalendarRange {
|
export interface CalendarRange {
|
||||||
id: number;
|
id: number;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
user: User;
|
user: User;
|
||||||
location: Location;
|
location: Location;
|
||||||
createdAt: DateTime;
|
createdAt: DateTime;
|
||||||
createdBy: User;
|
createdBy: User;
|
||||||
updatedAt: DateTime;
|
updatedAt: DateTime;
|
||||||
updatedBy: User;
|
updatedBy: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarRangeCreate {
|
export interface CalendarRangeCreate {
|
||||||
user: UserAssociatedInterface;
|
user: UserAssociatedInterface;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
location: Location;
|
location: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarRangeEdit {
|
export interface CalendarRangeEdit {
|
||||||
startDate?: DateTime;
|
startDate?: DateTime;
|
||||||
endDate?: DateTime;
|
endDate?: DateTime;
|
||||||
location?: Location;
|
location?: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Calendar {
|
export interface Calendar {
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarLight {
|
export interface CalendarLight {
|
||||||
id: number;
|
id: number;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
mainUser: User;
|
mainUser: User;
|
||||||
persons: Person[];
|
persons: Person[];
|
||||||
status: "valid" | "moved" | "canceled";
|
status: "valid" | "moved" | "canceled";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarRemote {
|
export interface CalendarRemote {
|
||||||
id: number;
|
id: number;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
title: string;
|
title: string;
|
||||||
isAllDay: boolean;
|
isAllDay: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventInputCalendarRange = EventInput & {
|
export type EventInputCalendarRange = EventInput & {
|
||||||
id: string;
|
id: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
userLabel: string;
|
userLabel: string;
|
||||||
calendarRangeId: number;
|
calendarRangeId: number;
|
||||||
locationId: number;
|
locationId: number;
|
||||||
locationName: string;
|
locationName: string;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
is: "range";
|
is: "range";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isEventInputCalendarRange(
|
export function isEventInputCalendarRange(
|
||||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||||
): toBeDetermined is EventInputCalendarRange {
|
): toBeDetermined is EventInputCalendarRange {
|
||||||
return (
|
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
|
||||||
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@@ -1,119 +1,105 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :style="style" class="calendar-active">
|
<div :style="style" class="calendar-active">
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ user.text }}
|
{{ user.text }}
|
||||||
<template v-if="invite !== null">
|
<template v-if="invite !== null">
|
||||||
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||||
<i
|
<i v-else-if="invite.status === 'declined'" class="fa fa-times" />
|
||||||
v-else-if="invite.status === 'declined'"
|
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
|
||||||
class="fa fa-times"
|
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
|
||||||
/>
|
<span v-else="">{{ invite.status }}</span>
|
||||||
<i
|
</template>
|
||||||
v-else-if="invite.status === 'pending'"
|
</span>
|
||||||
class="fa fa-question-o"
|
<span class="form-check-inline form-switch">
|
||||||
/>
|
<input
|
||||||
<i
|
class="form-check-input"
|
||||||
v-else-if="invite.status === 'tentative'"
|
type="checkbox"
|
||||||
class="fa fa-question"
|
id="flexSwitchCheckDefault"
|
||||||
/>
|
v-model="rangeShow"
|
||||||
<span v-else="">{{ invite.status }}</span>
|
/>
|
||||||
</template>
|
<label
|
||||||
</span>
|
class="form-check-label"
|
||||||
<span class="form-check-inline form-switch">
|
for="flexSwitchCheckDefault"
|
||||||
<input
|
title="Disponibilités"
|
||||||
class="form-check-input"
|
><i class="fa fa-calendar-check-o"
|
||||||
type="checkbox"
|
/></label>
|
||||||
id="flexSwitchCheckDefault"
|
</span>
|
||||||
v-model="rangeShow"
|
<span class="form-check-inline form-switch">
|
||||||
/>
|
<input
|
||||||
<label
|
class="form-check-input"
|
||||||
class="form-check-label"
|
type="checkbox"
|
||||||
for="flexSwitchCheckDefault"
|
id="flexSwitchCheckDefault"
|
||||||
title="Disponibilités"
|
v-model="remoteShow"
|
||||||
><i class="fa fa-calendar-check-o"
|
/>
|
||||||
/></label>
|
<label
|
||||||
</span>
|
class="form-check-label"
|
||||||
<span class="form-check-inline form-switch">
|
for="flexSwitchCheckDefault"
|
||||||
<input
|
title="Agenda"
|
||||||
class="form-check-input"
|
><i class="fa fa-calendar"
|
||||||
type="checkbox"
|
/></label>
|
||||||
id="flexSwitchCheckDefault"
|
</span>
|
||||||
v-model="remoteShow"
|
</div>
|
||||||
/>
|
|
||||||
<label
|
|
||||||
class="form-check-label"
|
|
||||||
for="flexSwitchCheckDefault"
|
|
||||||
title="Agenda"
|
|
||||||
><i class="fa fa-calendar"
|
|
||||||
/></label>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CalendarActive",
|
name: "CalendarActive",
|
||||||
props: {
|
props: {
|
||||||
user: {
|
user: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
invite: {
|
|
||||||
type: Object,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
invite: {
|
||||||
style() {
|
type: Object,
|
||||||
return {
|
required: false,
|
||||||
backgroundColor: this.$store.getters.getUserData(this.user)
|
default: null,
|
||||||
.mainColor,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
rangeShow: {
|
|
||||||
set(value) {
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user: this.user,
|
|
||||||
ranges: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.$store.getters.isRangeShownOnCalendarForUser(
|
|
||||||
this.user,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
remoteShow: {
|
|
||||||
set(value) {
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user: this.user,
|
|
||||||
remotes: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.$store.getters.isRemoteShownOnCalendarForUser(
|
|
||||||
this.user,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
style() {
|
||||||
|
return {
|
||||||
|
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
rangeShow: {
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.user,
|
||||||
|
ranges: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remoteShow: {
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.user,
|
||||||
|
remotes: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.calendar-active {
|
.calendar-active {
|
||||||
margin: 0 0.25rem 0.25rem 0;
|
margin: 0 0.25rem 0.25rem 0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
color: var(--bs-blue);
|
color: var(--bs-blue);
|
||||||
|
|
||||||
& > .badge-user {
|
& > .badge-user {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
|
|||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
export const fetchCalendarRangeForUser = (
|
export const fetchCalendarRangeForUser = (
|
||||||
user: User,
|
user: User,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
): Promise<CalendarRange[]> => {
|
): Promise<CalendarRange[]> => {
|
||||||
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchCalendarRemoteForUser = (
|
export const fetchCalendarRemoteForUser = (
|
||||||
user: User,
|
user: User,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
): Promise<CalendarRemote[]> => {
|
): Promise<CalendarRemote[]> => {
|
||||||
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchCalendarLocalForUser = (
|
export const fetchCalendarLocalForUser = (
|
||||||
user: User,
|
user: User,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
): Promise<CalendarLight[]> => {
|
): Promise<CalendarLight[]> => {
|
||||||
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
const COLORS = [
|
const COLORS = [
|
||||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||||
"#8dd3c7",
|
"#8dd3c7",
|
||||||
"#ffffb3",
|
"#ffffb3",
|
||||||
"#bebada",
|
"#bebada",
|
||||||
"#fb8072",
|
"#fb8072",
|
||||||
"#80b1d3",
|
"#80b1d3",
|
||||||
"#fdb462",
|
"#fdb462",
|
||||||
"#b3de69",
|
"#b3de69",
|
||||||
"#fccde5",
|
"#fccde5",
|
||||||
"#d9d9d9",
|
"#d9d9d9",
|
||||||
"#bc80bd",
|
"#bc80bd",
|
||||||
"#ccebc5",
|
"#ccebc5",
|
||||||
"#ffed6f",
|
"#ffed6f",
|
||||||
];
|
];
|
||||||
|
|
||||||
export { COLORS };
|
export { COLORS };
|
||||||
|
|||||||
@@ -1,117 +1,117 @@
|
|||||||
import { COLORS } from "../const";
|
import { COLORS } from "../const";
|
||||||
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
import {
|
import {
|
||||||
DateTime,
|
DateTime,
|
||||||
User,
|
User,
|
||||||
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
|
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
|
||||||
import type { EventInputCalendarRange } from "../../../types";
|
import type { EventInputCalendarRange } from "../../../types";
|
||||||
import { EventInput } from "@fullcalendar/core";
|
import { EventInput } from "@fullcalendar/core";
|
||||||
|
|
||||||
export interface UserData {
|
export interface UserData {
|
||||||
user: User;
|
user: User;
|
||||||
calendarRanges: CalendarRange[];
|
calendarRanges: CalendarRange[];
|
||||||
calendarRangesLoaded: {}[];
|
calendarRangesLoaded: {}[];
|
||||||
remotes: CalendarRemote[];
|
remotes: CalendarRemote[];
|
||||||
remotesLoaded: {}[];
|
remotesLoaded: {}[];
|
||||||
locals: CalendarRemote[];
|
locals: CalendarRemote[];
|
||||||
localsLoaded: {}[];
|
localsLoaded: {}[];
|
||||||
mainColor: string;
|
mainColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addIdToValue = (string: string, id: number): string => {
|
export const addIdToValue = (string: string, id: number): string => {
|
||||||
const array = string ? string.split(",") : [];
|
const array = string ? string.split(",") : [];
|
||||||
array.push(id.toString());
|
array.push(id.toString());
|
||||||
const str = array.join();
|
const str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeIdFromValue = (string: string, id: number) => {
|
export const removeIdFromValue = (string: string, id: number) => {
|
||||||
let array = string.split(",");
|
let array = string.split(",");
|
||||||
array = array.filter((el) => el !== id.toString());
|
array = array.filter((el) => el !== id.toString());
|
||||||
const str = array.join();
|
const str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assign missing keys for the ConcernedGroups component
|
* Assign missing keys for the ConcernedGroups component
|
||||||
*/
|
*/
|
||||||
export const mapEntity = (entity: EventInput): EventInput => {
|
export const mapEntity = (entity: EventInput): EventInput => {
|
||||||
const calendar = { ...entity };
|
const calendar = { ...entity };
|
||||||
Object.assign(calendar, { thirdParties: entity.professionals });
|
Object.assign(calendar, { thirdParties: entity.professionals });
|
||||||
|
|
||||||
if (entity.startDate !== null) {
|
if (entity.startDate !== null) {
|
||||||
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
||||||
}
|
}
|
||||||
if (entity.endDate !== null) {
|
if (entity.endDate !== null) {
|
||||||
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.calendarRange !== null) {
|
if (entity.calendarRange !== null) {
|
||||||
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
||||||
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return calendar;
|
return calendar;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createUserData = (user: User, colorIndex: number): UserData => {
|
export const createUserData = (user: User, colorIndex: number): UserData => {
|
||||||
const colorId = colorIndex % COLORS.length;
|
const colorId = colorIndex % COLORS.length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: user,
|
user: user,
|
||||||
calendarRanges: [],
|
calendarRanges: [],
|
||||||
calendarRangesLoaded: [],
|
calendarRangesLoaded: [],
|
||||||
remotes: [],
|
remotes: [],
|
||||||
remotesLoaded: [],
|
remotesLoaded: [],
|
||||||
locals: [],
|
locals: [],
|
||||||
localsLoaded: [],
|
localsLoaded: [],
|
||||||
mainColor: COLORS[colorId],
|
mainColor: COLORS[colorId],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
||||||
export const calendarRangeToFullCalendarEvent = (
|
export const calendarRangeToFullCalendarEvent = (
|
||||||
entity: CalendarRange,
|
entity: CalendarRange,
|
||||||
): EventInputCalendarRange => {
|
): EventInputCalendarRange => {
|
||||||
return {
|
return {
|
||||||
id: `range_${entity.id}`,
|
id: `range_${entity.id}`,
|
||||||
title: "(" + entity.user.text + ")",
|
title: "(" + entity.user.text + ")",
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: false,
|
allDay: false,
|
||||||
userId: entity.user.id,
|
userId: entity.user.id,
|
||||||
userLabel: entity.user.label,
|
userLabel: entity.user.label,
|
||||||
calendarRangeId: entity.id,
|
calendarRangeId: entity.id,
|
||||||
locationId: entity.location.id,
|
locationId: entity.location.id,
|
||||||
locationName: entity.location.name,
|
locationName: entity.location.name,
|
||||||
is: "range",
|
is: "range",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remoteToFullCalendarEvent = (
|
export const remoteToFullCalendarEvent = (
|
||||||
entity: CalendarRemote,
|
entity: CalendarRemote,
|
||||||
): EventInput & { id: string } => {
|
): EventInput & { id: string } => {
|
||||||
return {
|
return {
|
||||||
id: `range_${entity.id}`,
|
id: `range_${entity.id}`,
|
||||||
title: entity.title,
|
title: entity.title,
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: entity.isAllDay,
|
allDay: entity.isAllDay,
|
||||||
is: "remote",
|
is: "remote",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const localsToFullCalendarEvent = (
|
export const localsToFullCalendarEvent = (
|
||||||
entity: CalendarLight,
|
entity: CalendarLight,
|
||||||
): EventInput & { id: string; originId: number } => {
|
): EventInput & { id: string; originId: number } => {
|
||||||
return {
|
return {
|
||||||
id: `local_${entity.id}`,
|
id: `local_${entity.id}`,
|
||||||
title: entity.persons.map((p) => p.text).join(", "),
|
title: entity.persons.map((p) => p.text).join(", "),
|
||||||
originId: entity.id,
|
originId: entity.id,
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: false,
|
allDay: false,
|
||||||
is: "local",
|
is: "local",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,58 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button
|
<button
|
||||||
id="btnGroupDrop1"
|
id="btnGroupDrop1"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-misc dropdown-toggle"
|
class="btn btn-misc dropdown-toggle"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<template v-if="status === Statuses.PENDING">
|
||||||
|
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="status === Statuses.ACCEPTED">
|
||||||
|
<span class="fa fa-check"></span> {{ $t("Accepted") }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="status === Statuses.DECLINED">
|
||||||
|
<span class="fa fa-times"></span> {{ $t("Declined") }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
||||||
|
<span class="fa fa-question"></span> {{ $t("Tentative") }}
|
||||||
|
</template>
|
||||||
|
</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
|
||||||
>
|
>
|
||||||
<template v-if="status === Statuses.PENDING">
|
</li>
|
||||||
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
|
<li v-if="status !== Statuses.DECLINED">
|
||||||
</template>
|
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)"
|
||||||
<template v-else-if="status === Statuses.ACCEPTED">
|
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a
|
||||||
<span class="fa fa-check"></span> {{ $t("Accepted") }}
|
>
|
||||||
</template>
|
</li>
|
||||||
<template v-else-if="status === Statuses.DECLINED">
|
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
|
||||||
<span class="fa fa-times"></span> {{ $t("Declined") }}
|
<a
|
||||||
</template>
|
class="dropdown-item"
|
||||||
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
|
||||||
<span class="fa fa-question"></span> {{ $t("Tentative") }}
|
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a
|
||||||
</template>
|
>
|
||||||
</button>
|
</li>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
<li v-if="status !== Statuses.PENDING">
|
||||||
<li v-if="status !== Statuses.ACCEPTED">
|
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
|
||||||
<a
|
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a
|
||||||
class="dropdown-item"
|
>
|
||||||
@click="changeStatus(Statuses.ACCEPTED)"
|
</li>
|
||||||
><i class="fa fa-check" aria-hidden="true"></i>
|
</ul>
|
||||||
{{ $t("Accept") }}</a
|
</div>
|
||||||
>
|
|
||||||
</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
|
|
||||||
>
|
|
||||||
</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
|
|
||||||
>
|
|
||||||
</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
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -64,69 +56,67 @@ const PENDING = "pending";
|
|||||||
const TENTATIVELY_ACCEPTED = "tentative";
|
const TENTATIVELY_ACCEPTED = "tentative";
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
messages: {
|
messages: {
|
||||||
fr: {
|
fr: {
|
||||||
Give_an_answer: "Répondre",
|
Give_an_answer: "Répondre",
|
||||||
Accepted: "Accepté",
|
Accepted: "Accepté",
|
||||||
Declined: "Refusé",
|
Declined: "Refusé",
|
||||||
Tentative: "Accepté provisoirement",
|
Tentative: "Accepté provisoirement",
|
||||||
Accept: "Accepter",
|
Accept: "Accepter",
|
||||||
Decline: "Refuser",
|
Decline: "Refuser",
|
||||||
Tentatively_accept: "Accepter provisoirement",
|
Tentatively_accept: "Accepter provisoirement",
|
||||||
Set_pending: "Ne pas répondre",
|
Set_pending: "Ne pas répondre",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Answer",
|
name: "Answer",
|
||||||
i18n,
|
i18n,
|
||||||
props: {
|
props: {
|
||||||
calendarId: { type: Number, required: true },
|
calendarId: { type: Number, required: true },
|
||||||
status: {
|
status: {
|
||||||
type: String as PropType<
|
type: String as PropType<
|
||||||
"accepted" | "declined" | "pending" | "tentative"
|
"accepted" | "declined" | "pending" | "tentative"
|
||||||
>,
|
>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: {
|
},
|
||||||
statusChanged(
|
emits: {
|
||||||
payload: "accepted" | "declined" | "pending" | "tentative",
|
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
|
||||||
) {
|
return true;
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
},
|
||||||
return {
|
data() {
|
||||||
Statuses: {
|
return {
|
||||||
ACCEPTED,
|
Statuses: {
|
||||||
DECLINED,
|
ACCEPTED,
|
||||||
PENDING,
|
DECLINED,
|
||||||
TENTATIVELY_ACCEPTED,
|
PENDING,
|
||||||
},
|
TENTATIVELY_ACCEPTED,
|
||||||
};
|
},
|
||||||
},
|
};
|
||||||
methods: {
|
},
|
||||||
changeStatus: function (
|
methods: {
|
||||||
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
changeStatus: function (
|
||||||
) {
|
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
||||||
console.log("changeStatus", newStatus);
|
) {
|
||||||
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
|
console.log("changeStatus", newStatus);
|
||||||
window
|
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
|
||||||
.fetch(url, {
|
window
|
||||||
method: "POST",
|
.fetch(url, {
|
||||||
})
|
method: "POST",
|
||||||
.then((r: Response) => {
|
})
|
||||||
if (!r.ok) {
|
.then((r: Response) => {
|
||||||
console.error("could not confirm answer", newStatus);
|
if (!r.ok) {
|
||||||
return;
|
console.error("could not confirm answer", newStatus);
|
||||||
}
|
return;
|
||||||
console.log("answer sent", newStatus);
|
}
|
||||||
this.$emit("statusChanged", newStatus);
|
console.log("answer sent", newStatus);
|
||||||
});
|
this.$emit("statusChanged", newStatus);
|
||||||
},
|
});
|
||||||
},
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Teleport" to="body">
|
<component :is="Teleport" to="body">
|
||||||
<modal v-if="showModal" @close="closeModal">
|
<modal v-if="showModal" @close="closeModal">
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h3>{{ "Modifier le lieu" }}</h3>
|
<h3>{{ "Modifier le lieu" }}</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<div></div>
|
<div></div>
|
||||||
<label>Localisation</label>
|
<label>Localisation</label>
|
||||||
<vue-multiselect
|
<vue-multiselect
|
||||||
v-model="location"
|
v-model="location"
|
||||||
:options="locations"
|
:options="locations"
|
||||||
:label="'name'"
|
:label="'name'"
|
||||||
:track-by="'id'"
|
:track-by="'id'"
|
||||||
></vue-multiselect>
|
></vue-multiselect>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:footer>
|
<template v-slot:footer>
|
||||||
<button class="btn btn-save" @click="saveAndClose">
|
<button class="btn btn-save" @click="saveAndClose">
|
||||||
{{ "Enregistrer" }}
|
{{ "Enregistrer" }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</modal>
|
</modal>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
|
|||||||
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
||||||
|
|
||||||
const Teleport = teleport_ as new () => {
|
const Teleport = teleport_ as new () => {
|
||||||
$props: VNodeProps & TeleportProps;
|
$props: VNodeProps & TeleportProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = useStore(key);
|
const store = useStore(key);
|
||||||
@@ -50,37 +50,37 @@ const showModal = ref(false);
|
|||||||
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
|
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
|
||||||
|
|
||||||
const locations = computed<Location[]>(() => {
|
const locations = computed<Location[]>(() => {
|
||||||
return store.state.locations.locations;
|
return store.state.locations.locations;
|
||||||
});
|
});
|
||||||
|
|
||||||
const startEdit = function (event: EventApi): void {
|
const startEdit = function (event: EventApi): void {
|
||||||
console.log("startEditing", event);
|
console.log("startEditing", event);
|
||||||
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
||||||
location.value =
|
location.value =
|
||||||
store.getters["locations/getLocationById"](
|
store.getters["locations/getLocationById"](
|
||||||
event.extendedProps.locationId,
|
event.extendedProps.locationId,
|
||||||
) || null;
|
) || null;
|
||||||
|
|
||||||
console.log("new location value", location.value);
|
console.log("new location value", location.value);
|
||||||
console.log("calendar range id", calendarRangeId.value);
|
console.log("calendar range id", calendarRangeId.value);
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAndClose = function (e: Event): void {
|
const saveAndClose = function (e: Event): void {
|
||||||
console.log("saveEditAndClose", e);
|
console.log("saveEditAndClose", e);
|
||||||
|
|
||||||
store
|
store
|
||||||
.dispatch("calendarRanges/patchRangeLocation", {
|
.dispatch("calendarRanges/patchRangeLocation", {
|
||||||
location: location.value,
|
location: location.value,
|
||||||
calendarRangeId: calendarRangeId.value,
|
calendarRangeId: calendarRangeId.value,
|
||||||
})
|
})
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = function (_: any): void {
|
const closeModal = function (_: any): void {
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ startEdit });
|
defineExpose({ startEdit });
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
const appMessages = {
|
const appMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
created_availabilities: "Lieu des plages de disponibilités créées",
|
created_availabilities: "Lieu des plages de disponibilités créées",
|
||||||
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
||||||
show_my_calendar: "Afficher mon calendrier",
|
show_my_calendar: "Afficher mon calendrier",
|
||||||
show_weekends: "Afficher les week-ends",
|
show_weekends: "Afficher les week-ends",
|
||||||
copy_range: "Copier",
|
copy_range: "Copier",
|
||||||
copy_range_from_to: "Copier les plages",
|
copy_range_from_to: "Copier les plages",
|
||||||
from_day_to_day: "d'un jour à l'autre",
|
from_day_to_day: "d'un jour à l'autre",
|
||||||
from_week_to_week: "d'une semaine à l'autre",
|
from_week_to_week: "d'une semaine à l'autre",
|
||||||
copy_range_how_to:
|
copy_range_how_to:
|
||||||
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
|
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
|
||||||
new_range_to_save: "Nouvelles plages à enregistrer",
|
new_range_to_save: "Nouvelles plages à enregistrer",
|
||||||
update_range_to_save: "Plages à modifier",
|
update_range_to_save: "Plages à modifier",
|
||||||
delete_range_to_save: "Plages à supprimer",
|
delete_range_to_save: "Plages à supprimer",
|
||||||
by: "Par",
|
by: "Par",
|
||||||
main_user_concerned: "Utilisateur concerné",
|
main_user_concerned: "Utilisateur concerné",
|
||||||
dateFrom: "De",
|
dateFrom: "De",
|
||||||
dateTo: "à",
|
dateTo: "à",
|
||||||
day: "Jour",
|
day: "Jour",
|
||||||
week: "Semaine",
|
week: "Semaine",
|
||||||
month: "Mois",
|
month: "Mois",
|
||||||
today: "Aujourd'hui",
|
today: "Aujourd'hui",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export { appMessages };
|
export { appMessages };
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
futureStore().then((store) => {
|
futureStore().then((store) => {
|
||||||
const i18n = _createI18n(appMessages, false);
|
const i18n = _createI18n(appMessages, false);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app></app>`,
|
template: `<app></app>`,
|
||||||
})
|
})
|
||||||
.use(store, key)
|
.use(store, key)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component("app", App2)
|
.component("app", App2)
|
||||||
.mount("#myCalendar");
|
.mount("#myCalendar");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
|
|||||||
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
||||||
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
||||||
import calendarRemotes, {
|
import calendarRemotes, {
|
||||||
CalendarRemotesState,
|
CalendarRemotesState,
|
||||||
} from "./modules/calendarRemotes";
|
} from "./modules/calendarRemotes";
|
||||||
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||||
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
|
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
@@ -15,42 +15,40 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
|
|||||||
const debug = process.env.NODE_ENV !== "production";
|
const debug = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
calendarRanges: CalendarRangesState;
|
calendarRanges: CalendarRangesState;
|
||||||
calendarRemotes: CalendarRemotesState;
|
calendarRemotes: CalendarRemotesState;
|
||||||
calendarLocals: CalendarLocalsState;
|
calendarLocals: CalendarLocalsState;
|
||||||
fullCalendar: FullCalendarState;
|
fullCalendar: FullCalendarState;
|
||||||
me: MeState;
|
me: MeState;
|
||||||
locations: LocationState;
|
locations: LocationState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const key: InjectionKey<Store<State>> = Symbol();
|
export const key: InjectionKey<Store<State>> = Symbol();
|
||||||
|
|
||||||
const futureStore = function (): Promise<Store<State>> {
|
const futureStore = function (): Promise<Store<State>> {
|
||||||
return whoami().then((user: User) => {
|
return whoami().then((user: User) => {
|
||||||
const store = createStore<State>({
|
const store = createStore<State>({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
modules: {
|
modules: {
|
||||||
me,
|
me,
|
||||||
fullCalendar,
|
fullCalendar,
|
||||||
calendarRanges,
|
calendarRanges,
|
||||||
calendarRemotes,
|
calendarRemotes,
|
||||||
calendarLocals,
|
calendarLocals,
|
||||||
locations,
|
locations,
|
||||||
},
|
},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
});
|
|
||||||
|
|
||||||
store.commit("me/setWhoAmi", user, { root: true });
|
|
||||||
store
|
|
||||||
.dispatch("locations/getLocations", null, { root: true })
|
|
||||||
.then((_) => {
|
|
||||||
return store.dispatch("locations/getCurrentLocation", null, {
|
|
||||||
root: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.resolve(store);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
store.commit("me/setWhoAmi", user, { root: true });
|
||||||
|
store.dispatch("locations/getLocations", null, { root: true }).then((_) => {
|
||||||
|
return store.dispatch("locations/getCurrentLocation", null, {
|
||||||
|
root: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve(store);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default futureStore;
|
export default futureStore;
|
||||||
|
|||||||
@@ -8,109 +8,99 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
|||||||
import { COLORS } from "../../../Calendar/const";
|
import { COLORS } from "../../../Calendar/const";
|
||||||
|
|
||||||
export interface CalendarLocalsState {
|
export interface CalendarLocalsState {
|
||||||
locals: EventInput[];
|
locals: EventInput[];
|
||||||
localsLoaded: { start: number; end: number }[];
|
localsLoaded: { start: number; end: number }[];
|
||||||
localsIndex: Set<string>;
|
localsIndex: Set<string>;
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarLocalsState, State>;
|
type Context = ActionContext<CalendarLocalsState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarLocalsState => ({
|
state: (): CalendarLocalsState => ({
|
||||||
locals: [],
|
locals: [],
|
||||||
localsLoaded: [],
|
localsLoaded: [],
|
||||||
localsIndex: new Set<string>(),
|
localsIndex: new Set<string>(),
|
||||||
key: 0,
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isLocalsLoaded:
|
isLocalsLoaded:
|
||||||
(state: CalendarLocalsState) =>
|
(state: CalendarLocalsState) =>
|
||||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||||
for (const range of state.localsLoaded) {
|
for (const range of state.localsLoaded) {
|
||||||
if (
|
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||||
start.getTime() === range.start &&
|
return true;
|
||||||
end.getTime() === range.end
|
}
|
||||||
) {
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
||||||
|
console.log("addLocals", ranges);
|
||||||
|
|
||||||
|
const toAdd = ranges
|
||||||
|
.map((cr) => localsToFullCalendarEvent(cr))
|
||||||
|
.filter((r) => !state.localsIndex.has(r.id));
|
||||||
|
|
||||||
|
toAdd.forEach((r) => {
|
||||||
|
state.localsIndex.add(r.id);
|
||||||
|
state.locals.push(r);
|
||||||
|
});
|
||||||
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
mutations: {
|
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
|
||||||
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
state.localsLoaded.push({
|
||||||
console.log("addLocals", ranges);
|
start: payload.start.getTime(),
|
||||||
|
end: payload.end.getTime(),
|
||||||
const toAdd = ranges
|
});
|
||||||
.map((cr) => localsToFullCalendarEvent(cr))
|
|
||||||
.filter((r) => !state.localsIndex.has(r.id));
|
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
|
||||||
state.localsIndex.add(r.id);
|
|
||||||
state.locals.push(r);
|
|
||||||
});
|
|
||||||
state.key = state.key + toAdd.length;
|
|
||||||
},
|
|
||||||
addLoaded(
|
|
||||||
state: CalendarLocalsState,
|
|
||||||
payload: { start: Date; end: Date },
|
|
||||||
) {
|
|
||||||
state.localsLoaded.push({
|
|
||||||
start: payload.start.getTime(),
|
|
||||||
end: payload.end.getTime(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
},
|
||||||
fetchLocals(
|
actions: {
|
||||||
ctx: Context,
|
fetchLocals(
|
||||||
payload: { start: Date; end: Date },
|
ctx: Context,
|
||||||
): Promise<null> {
|
payload: { start: Date; end: Date },
|
||||||
const start = payload.start;
|
): Promise<null> {
|
||||||
const end = payload.end;
|
const start = payload.start;
|
||||||
|
const end = payload.end;
|
||||||
|
|
||||||
if (ctx.rootGetters["me/getMe"] === null) {
|
if (ctx.rootGetters["me/getMe"] === null) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.commit("addLoaded", {
|
ctx.commit("addLoaded", {
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchCalendarLocalForUser(
|
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||||
ctx.rootGetters["me/getMe"],
|
.then((remotes: CalendarLight[]) => {
|
||||||
start,
|
// to be add when reactivity problem will be solve ?
|
||||||
end,
|
//ctx.commit('addRemotes', remotes);
|
||||||
)
|
const inputs = remotes
|
||||||
.then((remotes: CalendarLight[]) => {
|
.map((cr) => localsToFullCalendarEvent(cr))
|
||||||
// to be add when reactivity problem will be solve ?
|
.map((cr) => ({
|
||||||
//ctx.commit('addRemotes', remotes);
|
...cr,
|
||||||
const inputs = remotes
|
backgroundColor: COLORS[0],
|
||||||
.map((cr) => localsToFullCalendarEvent(cr))
|
textColor: "black",
|
||||||
.map((cr) => ({
|
editable: false,
|
||||||
...cr,
|
}));
|
||||||
backgroundColor: COLORS[0],
|
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||||
textColor: "black",
|
root: true,
|
||||||
editable: false,
|
});
|
||||||
}));
|
return Promise.resolve(null);
|
||||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
})
|
||||||
root: true,
|
.catch((e: TransportExceptionInterface) => {
|
||||||
});
|
console.error(e);
|
||||||
return Promise.resolve(null);
|
|
||||||
})
|
|
||||||
.catch((e: TransportExceptionInterface) => {
|
|
||||||
console.error(e);
|
|
||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
} as Module<CalendarLocalsState, State>;
|
} as Module<CalendarLocalsState, State>;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { State } from "./../index";
|
import { State } from "./../index";
|
||||||
import { ActionContext, Module } from "vuex";
|
import { ActionContext, Module } from "vuex";
|
||||||
import {
|
import {
|
||||||
CalendarRange,
|
CalendarRange,
|
||||||
CalendarRangeCreate,
|
CalendarRangeCreate,
|
||||||
CalendarRangeEdit,
|
CalendarRangeEdit,
|
||||||
isEventInputCalendarRange,
|
isEventInputCalendarRange,
|
||||||
} from "../../../../types";
|
} from "../../../../types";
|
||||||
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
|
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
|
||||||
@@ -12,369 +12,332 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
|
|||||||
import { EventInput } from "@fullcalendar/core";
|
import { EventInput } from "@fullcalendar/core";
|
||||||
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
import {
|
import {
|
||||||
datetimeToISO,
|
datetimeToISO,
|
||||||
dateToISO,
|
dateToISO,
|
||||||
ISOToDatetime,
|
ISOToDatetime,
|
||||||
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
import type { EventInputCalendarRange } from "../../../../types";
|
import type { EventInputCalendarRange } from "../../../../types";
|
||||||
|
|
||||||
export interface CalendarRangesState {
|
export interface CalendarRangesState {
|
||||||
ranges: (EventInput | EventInputCalendarRange)[];
|
ranges: (EventInput | EventInputCalendarRange)[];
|
||||||
rangesLoaded: { start: number; end: number }[];
|
rangesLoaded: { start: number; end: number }[];
|
||||||
rangesIndex: Set<string>;
|
rangesIndex: Set<string>;
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarRangesState, State>;
|
type Context = ActionContext<CalendarRangesState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarRangesState => ({
|
state: (): CalendarRangesState => ({
|
||||||
ranges: [],
|
ranges: [],
|
||||||
rangesLoaded: [],
|
rangesLoaded: [],
|
||||||
rangesIndex: new Set<string>(),
|
rangesIndex: new Set<string>(),
|
||||||
key: 0,
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isRangeLoaded:
|
isRangeLoaded:
|
||||||
(state: CalendarRangesState) =>
|
(state: CalendarRangesState) =>
|
||||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||||
for (const range of state.rangesLoaded) {
|
for (const range of state.rangesLoaded) {
|
||||||
if (
|
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||||
start.getTime() === range.start &&
|
return true;
|
||||||
end.getTime() === range.end
|
}
|
||||||
) {
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
getRangesOnDate:
|
getRangesOnDate:
|
||||||
(state: CalendarRangesState) =>
|
(state: CalendarRangesState) =>
|
||||||
(date: Date): EventInputCalendarRange[] => {
|
(date: Date): EventInputCalendarRange[] => {
|
||||||
const founds = [];
|
const founds = [];
|
||||||
const dateStr = dateToISO(date) as string;
|
const dateStr = dateToISO(date) as string;
|
||||||
|
|
||||||
for (const range of state.ranges) {
|
for (const range of state.ranges) {
|
||||||
if (
|
if (
|
||||||
isEventInputCalendarRange(range) &&
|
isEventInputCalendarRange(range) &&
|
||||||
range.start.startsWith(dateStr)
|
range.start.startsWith(dateStr)
|
||||||
) {
|
) {
|
||||||
founds.push(range);
|
founds.push(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return founds;
|
return founds;
|
||||||
},
|
},
|
||||||
getRangesOnWeek:
|
getRangesOnWeek:
|
||||||
(state: CalendarRangesState) =>
|
(state: CalendarRangesState) =>
|
||||||
(mondayDate: Date): EventInputCalendarRange[] => {
|
(mondayDate: Date): EventInputCalendarRange[] => {
|
||||||
const founds = [];
|
const founds = [];
|
||||||
for (const d of Array.from(Array(7).keys())) {
|
for (const d of Array.from(Array(7).keys())) {
|
||||||
const dateOfWeek = new Date(mondayDate);
|
const dateOfWeek = new Date(mondayDate);
|
||||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||||
const dateStr = dateToISO(dateOfWeek) as string;
|
const dateStr = dateToISO(dateOfWeek) as string;
|
||||||
for (const range of state.ranges) {
|
for (const range of state.ranges) {
|
||||||
if (
|
if (
|
||||||
isEventInputCalendarRange(range) &&
|
isEventInputCalendarRange(range) &&
|
||||||
range.start.startsWith(dateStr)
|
range.start.startsWith(dateStr)
|
||||||
) {
|
) {
|
||||||
founds.push(range);
|
founds.push(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return founds;
|
return founds;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
||||||
|
const toAdd = ranges
|
||||||
|
.map((cr) => calendarRangeToFullCalendarEvent(cr))
|
||||||
|
.map((cr) => ({
|
||||||
|
...cr,
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderColor: "#3788d8",
|
||||||
|
textColor: "black",
|
||||||
|
}))
|
||||||
|
.filter((r) => !state.rangesIndex.has(r.id));
|
||||||
|
|
||||||
|
toAdd.forEach((r) => {
|
||||||
|
state.rangesIndex.add(r.id);
|
||||||
|
state.ranges.push(r);
|
||||||
|
});
|
||||||
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
mutations: {
|
addExternals(
|
||||||
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
state: CalendarRangesState,
|
||||||
const toAdd = ranges
|
externalEvents: (EventInput & { id: string })[],
|
||||||
.map((cr) => calendarRangeToFullCalendarEvent(cr))
|
) {
|
||||||
.map((cr) => ({
|
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
|
||||||
...cr,
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderColor: "#3788d8",
|
|
||||||
textColor: "black",
|
|
||||||
}))
|
|
||||||
.filter((r) => !state.rangesIndex.has(r.id));
|
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
toAdd.forEach((r) => {
|
||||||
state.rangesIndex.add(r.id);
|
state.rangesIndex.add(r.id);
|
||||||
state.ranges.push(r);
|
state.ranges.push(r);
|
||||||
});
|
});
|
||||||
state.key = state.key + toAdd.length;
|
state.key = state.key + toAdd.length;
|
||||||
},
|
|
||||||
addExternals(
|
|
||||||
state: CalendarRangesState,
|
|
||||||
externalEvents: (EventInput & { id: string })[],
|
|
||||||
) {
|
|
||||||
const toAdd = externalEvents.filter(
|
|
||||||
(r) => !state.rangesIndex.has(r.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
|
||||||
state.rangesIndex.add(r.id);
|
|
||||||
state.ranges.push(r);
|
|
||||||
});
|
|
||||||
state.key = state.key + toAdd.length;
|
|
||||||
},
|
|
||||||
addLoaded(
|
|
||||||
state: CalendarRangesState,
|
|
||||||
payload: { start: Date; end: Date },
|
|
||||||
) {
|
|
||||||
state.rangesLoaded.push({
|
|
||||||
start: payload.start.getTime(),
|
|
||||||
end: payload.end.getTime(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
|
||||||
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
|
||||||
state.ranges.push({
|
|
||||||
...asEvent,
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderColor: "#3788d8",
|
|
||||||
textColor: "black",
|
|
||||||
});
|
|
||||||
state.rangesIndex.add(asEvent.id);
|
|
||||||
state.key = state.key + 1;
|
|
||||||
},
|
|
||||||
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
|
||||||
const found = state.ranges.find(
|
|
||||||
(r) =>
|
|
||||||
r.calendarRangeId === calendarRangeId && r.is === "range",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (found !== undefined) {
|
|
||||||
state.ranges = state.ranges.filter(
|
|
||||||
(r) =>
|
|
||||||
!(
|
|
||||||
r.calendarRangeId === calendarRangeId &&
|
|
||||||
r.is === "range"
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (typeof found.id === "string") {
|
|
||||||
// should always be true
|
|
||||||
state.rangesIndex.delete(found.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.key = state.key + 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
|
||||||
const found = state.ranges.find(
|
|
||||||
(r) => r.calendarRangeId === range.id && r.is === "range",
|
|
||||||
);
|
|
||||||
const newEvent = calendarRangeToFullCalendarEvent(range);
|
|
||||||
|
|
||||||
if (found !== undefined) {
|
|
||||||
found.start = newEvent.start;
|
|
||||||
found.end = newEvent.end;
|
|
||||||
found.locationId = range.location.id;
|
|
||||||
found.locationName = range.location.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.key = state.key + 1;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
|
||||||
fetchRanges(
|
state.rangesLoaded.push({
|
||||||
ctx: Context,
|
start: payload.start.getTime(),
|
||||||
payload: { start: Date; end: Date },
|
end: payload.end.getTime(),
|
||||||
): Promise<null> {
|
});
|
||||||
const start = payload.start;
|
|
||||||
const end = payload.end;
|
|
||||||
|
|
||||||
if (ctx.rootGetters["me/getMe"] === null) {
|
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.getters.isRangeLoaded({ start, end })) {
|
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.commit("addLoaded", {
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
});
|
|
||||||
|
|
||||||
return fetchCalendarRangeForUser(
|
|
||||||
ctx.rootGetters["me/getMe"],
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
).then((ranges: CalendarRange[]) => {
|
|
||||||
ctx.commit("addRanges", ranges);
|
|
||||||
return Promise.resolve(null);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
createRange(
|
|
||||||
ctx: Context,
|
|
||||||
{
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
location,
|
|
||||||
}: { start: Date; end: Date; location: Location },
|
|
||||||
): Promise<null> {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
|
||||||
|
|
||||||
if (ctx.rootState.me.me === null) {
|
|
||||||
throw new Error("user is currently null");
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
user: {
|
|
||||||
id: ctx.rootState.me.me.id,
|
|
||||||
type: "user",
|
|
||||||
},
|
|
||||||
startDate: {
|
|
||||||
datetime: datetimeToISO(start),
|
|
||||||
},
|
|
||||||
endDate: {
|
|
||||||
datetime: datetimeToISO(end),
|
|
||||||
},
|
|
||||||
location: {
|
|
||||||
id: location.id,
|
|
||||||
type: "location",
|
|
||||||
},
|
|
||||||
} as CalendarRangeCreate;
|
|
||||||
|
|
||||||
return makeFetch<CalendarRangeCreate, CalendarRange>(
|
|
||||||
"POST",
|
|
||||||
url,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.then((newRange) => {
|
|
||||||
ctx.commit("addRange", newRange);
|
|
||||||
|
|
||||||
return Promise.resolve(null);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteRange(ctx: Context, calendarRangeId: number) {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
|
||||||
|
|
||||||
makeFetch<undefined, never>("DELETE", url).then(() => {
|
|
||||||
ctx.commit("removeRange", calendarRangeId);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
patchRangeTime(
|
|
||||||
ctx,
|
|
||||||
{
|
|
||||||
calendarRangeId,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
}: { calendarRangeId: number; start: Date; end: Date },
|
|
||||||
): Promise<null> {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
|
||||||
const body = {
|
|
||||||
startDate: {
|
|
||||||
datetime: datetimeToISO(start),
|
|
||||||
},
|
|
||||||
endDate: {
|
|
||||||
datetime: datetimeToISO(end),
|
|
||||||
},
|
|
||||||
} as CalendarRangeEdit;
|
|
||||||
|
|
||||||
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
|
||||||
"PATCH",
|
|
||||||
url,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.then((range) => {
|
|
||||||
ctx.commit("updateRange", range);
|
|
||||||
return Promise.resolve(null);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
return Promise.resolve(null);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
patchRangeLocation(
|
|
||||||
ctx,
|
|
||||||
{
|
|
||||||
location,
|
|
||||||
calendarRangeId,
|
|
||||||
}: { location: Location; calendarRangeId: number },
|
|
||||||
): Promise<null> {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
|
||||||
const body = {
|
|
||||||
location: {
|
|
||||||
id: location.id,
|
|
||||||
type: "location",
|
|
||||||
},
|
|
||||||
} as CalendarRangeEdit;
|
|
||||||
|
|
||||||
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
|
||||||
"PATCH",
|
|
||||||
url,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.then((range) => {
|
|
||||||
ctx.commit("updateRange", range);
|
|
||||||
return Promise.resolve(null);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
return Promise.resolve(null);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
copyFromDayToAnotherDay(
|
|
||||||
ctx,
|
|
||||||
{ from, to }: { from: Date; to: Date },
|
|
||||||
): Promise<null> {
|
|
||||||
const rangesToCopy: EventInputCalendarRange[] =
|
|
||||||
ctx.getters["getRangesOnDate"](from);
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for (const r of rangesToCopy) {
|
|
||||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
|
||||||
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 }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
|
||||||
},
|
|
||||||
copyFromWeekToAnotherWeek(
|
|
||||||
ctx: Context,
|
|
||||||
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
|
||||||
): Promise<null> {
|
|
||||||
const rangesToCopy: EventInputCalendarRange[] =
|
|
||||||
ctx.getters["getRangesOnWeek"](fromMonday);
|
|
||||||
const promises = [];
|
|
||||||
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
|
||||||
for (const r of rangesToCopy) {
|
|
||||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
|
||||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
|
||||||
start.setTime(start.getTime() + diffTime);
|
|
||||||
end.setTime(end.getTime() + diffTime);
|
|
||||||
const location = ctx.rootGetters["locations/getLocationById"](
|
|
||||||
r.locationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
ctx.dispatch("createRange", { start, end, location }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
||||||
|
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
||||||
|
state.ranges.push({
|
||||||
|
...asEvent,
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderColor: "#3788d8",
|
||||||
|
textColor: "black",
|
||||||
|
});
|
||||||
|
state.rangesIndex.add(asEvent.id);
|
||||||
|
state.key = state.key + 1;
|
||||||
|
},
|
||||||
|
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
||||||
|
const found = state.ranges.find(
|
||||||
|
(r) => r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (found !== undefined) {
|
||||||
|
state.ranges = state.ranges.filter(
|
||||||
|
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof found.id === "string") {
|
||||||
|
// should always be true
|
||||||
|
state.rangesIndex.delete(found.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.key = state.key + 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
||||||
|
const found = state.ranges.find(
|
||||||
|
(r) => r.calendarRangeId === range.id && r.is === "range",
|
||||||
|
);
|
||||||
|
const newEvent = calendarRangeToFullCalendarEvent(range);
|
||||||
|
|
||||||
|
if (found !== undefined) {
|
||||||
|
found.start = newEvent.start;
|
||||||
|
found.end = newEvent.end;
|
||||||
|
found.locationId = range.location.id;
|
||||||
|
found.locationName = range.location.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.key = state.key + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
fetchRanges(
|
||||||
|
ctx: Context,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const start = payload.start;
|
||||||
|
const end = payload.end;
|
||||||
|
|
||||||
|
if (ctx.rootGetters["me/getMe"] === null) {
|
||||||
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.getters.isRangeLoaded({ start, end })) {
|
||||||
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.commit("addLoaded", {
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetchCalendarRangeForUser(
|
||||||
|
ctx.rootGetters["me/getMe"],
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
).then((ranges: CalendarRange[]) => {
|
||||||
|
ctx.commit("addRanges", ranges);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createRange(
|
||||||
|
ctx: Context,
|
||||||
|
{ start, end, location }: { start: Date; end: Date; location: Location },
|
||||||
|
): Promise<null> {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||||
|
|
||||||
|
if (ctx.rootState.me.me === null) {
|
||||||
|
throw new Error("user is currently null");
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
user: {
|
||||||
|
id: ctx.rootState.me.me.id,
|
||||||
|
type: "user",
|
||||||
|
},
|
||||||
|
startDate: {
|
||||||
|
datetime: datetimeToISO(start),
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
datetime: datetimeToISO(end),
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
id: location.id,
|
||||||
|
type: "location",
|
||||||
|
},
|
||||||
|
} as CalendarRangeCreate;
|
||||||
|
|
||||||
|
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body)
|
||||||
|
.then((newRange) => {
|
||||||
|
ctx.commit("addRange", newRange);
|
||||||
|
|
||||||
|
return Promise.resolve(null);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteRange(ctx: Context, calendarRangeId: number) {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
|
||||||
|
makeFetch<undefined, never>("DELETE", url).then(() => {
|
||||||
|
ctx.commit("removeRange", calendarRangeId);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
patchRangeTime(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
calendarRangeId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
}: { calendarRangeId: number; start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
const body = {
|
||||||
|
startDate: {
|
||||||
|
datetime: datetimeToISO(start),
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
datetime: datetimeToISO(end),
|
||||||
|
},
|
||||||
|
} as CalendarRangeEdit;
|
||||||
|
|
||||||
|
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
||||||
|
.then((range) => {
|
||||||
|
ctx.commit("updateRange", range);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
patchRangeLocation(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
location,
|
||||||
|
calendarRangeId,
|
||||||
|
}: { location: Location; calendarRangeId: number },
|
||||||
|
): Promise<null> {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
const body = {
|
||||||
|
location: {
|
||||||
|
id: location.id,
|
||||||
|
type: "location",
|
||||||
|
},
|
||||||
|
} as CalendarRangeEdit;
|
||||||
|
|
||||||
|
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
||||||
|
.then((range) => {
|
||||||
|
ctx.commit("updateRange", range);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
copyFromDayToAnotherDay(
|
||||||
|
ctx,
|
||||||
|
{ from, to }: { from: Date; to: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
|
ctx.getters["getRangesOnDate"](from);
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const r of rangesToCopy) {
|
||||||
|
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||||
|
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 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
|
},
|
||||||
|
copyFromWeekToAnotherWeek(
|
||||||
|
ctx: Context,
|
||||||
|
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
|
ctx.getters["getRangesOnWeek"](fromMonday);
|
||||||
|
const promises = [];
|
||||||
|
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
||||||
|
for (const r of rangesToCopy) {
|
||||||
|
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||||
|
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||||
|
start.setTime(start.getTime() + diffTime);
|
||||||
|
end.setTime(end.getTime() + diffTime);
|
||||||
|
const location = ctx.rootGetters["locations/getLocationById"](
|
||||||
|
r.locationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
promises.push(ctx.dispatch("createRange", { start, end, location }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
|
},
|
||||||
|
},
|
||||||
} as Module<CalendarRangesState, State>;
|
} as Module<CalendarRangesState, State>;
|
||||||
|
|||||||
@@ -8,109 +8,102 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
|||||||
import { COLORS } from "../../../Calendar/const";
|
import { COLORS } from "../../../Calendar/const";
|
||||||
|
|
||||||
export interface CalendarRemotesState {
|
export interface CalendarRemotesState {
|
||||||
remotes: EventInput[];
|
remotes: EventInput[];
|
||||||
remotesLoaded: { start: number; end: number }[];
|
remotesLoaded: { start: number; end: number }[];
|
||||||
remotesIndex: Set<string>;
|
remotesIndex: Set<string>;
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarRemotesState, State>;
|
type Context = ActionContext<CalendarRemotesState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarRemotesState => ({
|
state: (): CalendarRemotesState => ({
|
||||||
remotes: [],
|
remotes: [],
|
||||||
remotesLoaded: [],
|
remotesLoaded: [],
|
||||||
remotesIndex: new Set<string>(),
|
remotesIndex: new Set<string>(),
|
||||||
key: 0,
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isRemotesLoaded:
|
isRemotesLoaded:
|
||||||
(state: CalendarRemotesState) =>
|
(state: CalendarRemotesState) =>
|
||||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||||
for (const range of state.remotesLoaded) {
|
for (const range of state.remotesLoaded) {
|
||||||
if (
|
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||||
start.getTime() === range.start &&
|
return true;
|
||||||
end.getTime() === range.end
|
}
|
||||||
) {
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
||||||
|
console.log("addRemotes", ranges);
|
||||||
|
|
||||||
|
const toAdd = ranges
|
||||||
|
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||||
|
.filter((r) => !state.remotesIndex.has(r.id));
|
||||||
|
|
||||||
|
toAdd.forEach((r) => {
|
||||||
|
state.remotesIndex.add(r.id);
|
||||||
|
state.remotes.push(r);
|
||||||
|
});
|
||||||
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
mutations: {
|
addLoaded(
|
||||||
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
state: CalendarRemotesState,
|
||||||
console.log("addRemotes", ranges);
|
payload: { start: Date; end: Date },
|
||||||
|
) {
|
||||||
const toAdd = ranges
|
state.remotesLoaded.push({
|
||||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
start: payload.start.getTime(),
|
||||||
.filter((r) => !state.remotesIndex.has(r.id));
|
end: payload.end.getTime(),
|
||||||
|
});
|
||||||
toAdd.forEach((r) => {
|
|
||||||
state.remotesIndex.add(r.id);
|
|
||||||
state.remotes.push(r);
|
|
||||||
});
|
|
||||||
state.key = state.key + toAdd.length;
|
|
||||||
},
|
|
||||||
addLoaded(
|
|
||||||
state: CalendarRemotesState,
|
|
||||||
payload: { start: Date; end: Date },
|
|
||||||
) {
|
|
||||||
state.remotesLoaded.push({
|
|
||||||
start: payload.start.getTime(),
|
|
||||||
end: payload.end.getTime(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
},
|
||||||
fetchRemotes(
|
actions: {
|
||||||
ctx: Context,
|
fetchRemotes(
|
||||||
payload: { start: Date; end: Date },
|
ctx: Context,
|
||||||
): Promise<null> {
|
payload: { start: Date; end: Date },
|
||||||
const start = payload.start;
|
): Promise<null> {
|
||||||
const end = payload.end;
|
const start = payload.start;
|
||||||
|
const end = payload.end;
|
||||||
|
|
||||||
if (ctx.rootGetters["me/getMe"] === null) {
|
if (ctx.rootGetters["me/getMe"] === null) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.commit("addLoaded", {
|
ctx.commit("addLoaded", {
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchCalendarRemoteForUser(
|
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||||
ctx.rootGetters["me/getMe"],
|
.then((remotes: CalendarRemote[]) => {
|
||||||
start,
|
// to be add when reactivity problem will be solve ?
|
||||||
end,
|
//ctx.commit('addRemotes', remotes);
|
||||||
)
|
const inputs = remotes
|
||||||
.then((remotes: CalendarRemote[]) => {
|
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||||
// to be add when reactivity problem will be solve ?
|
.map((cr) => ({
|
||||||
//ctx.commit('addRemotes', remotes);
|
...cr,
|
||||||
const inputs = remotes
|
backgroundColor: COLORS[0],
|
||||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
textColor: "black",
|
||||||
.map((cr) => ({
|
editable: false,
|
||||||
...cr,
|
}));
|
||||||
backgroundColor: COLORS[0],
|
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||||
textColor: "black",
|
root: true,
|
||||||
editable: false,
|
});
|
||||||
}));
|
return Promise.resolve(null);
|
||||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
})
|
||||||
root: true,
|
.catch((e: TransportExceptionInterface) => {
|
||||||
});
|
console.error(e);
|
||||||
return Promise.resolve(null);
|
|
||||||
})
|
|
||||||
.catch((e: TransportExceptionInterface) => {
|
|
||||||
console.error(e);
|
|
||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
} as Module<CalendarRemotesState, State>;
|
} as Module<CalendarRemotesState, State>;
|
||||||
|
|||||||
@@ -2,77 +2,77 @@ import { State } from "./../index";
|
|||||||
import { ActionContext } from "vuex";
|
import { ActionContext } from "vuex";
|
||||||
|
|
||||||
export interface FullCalendarState {
|
export interface FullCalendarState {
|
||||||
currentView: {
|
currentView: {
|
||||||
start: Date | null;
|
start: Date | null;
|
||||||
end: Date | null;
|
end: Date | null;
|
||||||
};
|
};
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<FullCalendarState, State>;
|
type Context = ActionContext<FullCalendarState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): FullCalendarState => ({
|
state: (): FullCalendarState => ({
|
||||||
currentView: {
|
currentView: {
|
||||||
start: null,
|
start: null,
|
||||||
end: null,
|
end: null,
|
||||||
},
|
|
||||||
key: 0,
|
|
||||||
}),
|
|
||||||
mutations: {
|
|
||||||
setCurrentDatesView: function (
|
|
||||||
state: FullCalendarState,
|
|
||||||
payload: { start: Date; end: Date },
|
|
||||||
): void {
|
|
||||||
state.currentView.start = payload.start;
|
|
||||||
state.currentView.end = payload.end;
|
|
||||||
},
|
|
||||||
increaseKey: function (state: FullCalendarState): void {
|
|
||||||
state.key = state.key + 1;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
key: 0,
|
||||||
setCurrentDatesView(
|
}),
|
||||||
ctx: Context,
|
mutations: {
|
||||||
{ start, end }: { start: Date | null; end: Date | null },
|
setCurrentDatesView: function (
|
||||||
): Promise<null> {
|
state: FullCalendarState,
|
||||||
console.log("dispatch setCurrentDatesView", { start, end });
|
payload: { start: Date; end: Date },
|
||||||
|
): void {
|
||||||
if (
|
state.currentView.start = payload.start;
|
||||||
ctx.state.currentView.start !== start ||
|
state.currentView.end = payload.end;
|
||||||
ctx.state.currentView.end !== end
|
|
||||||
) {
|
|
||||||
ctx.commit("setCurrentDatesView", { start, end });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start !== null && end !== null) {
|
|
||||||
return Promise.all([
|
|
||||||
ctx
|
|
||||||
.dispatch(
|
|
||||||
"calendarRanges/fetchRanges",
|
|
||||||
{ start, end },
|
|
||||||
{ root: true },
|
|
||||||
)
|
|
||||||
.then((_) => Promise.resolve(null)),
|
|
||||||
ctx
|
|
||||||
.dispatch(
|
|
||||||
"calendarRemotes/fetchRemotes",
|
|
||||||
{ start, end },
|
|
||||||
{ root: true },
|
|
||||||
)
|
|
||||||
.then((_) => Promise.resolve(null)),
|
|
||||||
ctx
|
|
||||||
.dispatch(
|
|
||||||
"calendarLocals/fetchLocals",
|
|
||||||
{ start, end },
|
|
||||||
{ root: true },
|
|
||||||
)
|
|
||||||
.then((_) => Promise.resolve(null)),
|
|
||||||
]).then((_) => Promise.resolve(null));
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
increaseKey: function (state: FullCalendarState): void {
|
||||||
|
state.key = state.key + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
setCurrentDatesView(
|
||||||
|
ctx: Context,
|
||||||
|
{ start, end }: { start: Date | null; end: Date | null },
|
||||||
|
): Promise<null> {
|
||||||
|
console.log("dispatch setCurrentDatesView", { start, end });
|
||||||
|
|
||||||
|
if (
|
||||||
|
ctx.state.currentView.start !== start ||
|
||||||
|
ctx.state.currentView.end !== end
|
||||||
|
) {
|
||||||
|
ctx.commit("setCurrentDatesView", { start, end });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start !== null && end !== null) {
|
||||||
|
return Promise.all([
|
||||||
|
ctx
|
||||||
|
.dispatch(
|
||||||
|
"calendarRanges/fetchRanges",
|
||||||
|
{ start, end },
|
||||||
|
{ root: true },
|
||||||
|
)
|
||||||
|
.then((_) => Promise.resolve(null)),
|
||||||
|
ctx
|
||||||
|
.dispatch(
|
||||||
|
"calendarRemotes/fetchRemotes",
|
||||||
|
{ start, end },
|
||||||
|
{ root: true },
|
||||||
|
)
|
||||||
|
.then((_) => Promise.resolve(null)),
|
||||||
|
ctx
|
||||||
|
.dispatch(
|
||||||
|
"calendarLocals/fetchLocals",
|
||||||
|
{ start, end },
|
||||||
|
{ root: true },
|
||||||
|
)
|
||||||
|
.then((_) => Promise.resolve(null)),
|
||||||
|
]).then((_) => Promise.resolve(null));
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,61 +5,61 @@ import { getLocations } from "../../../../../../../ChillMainBundle/Resources/pub
|
|||||||
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||||
|
|
||||||
export interface LocationState {
|
export interface LocationState {
|
||||||
locations: Location[];
|
locations: Location[];
|
||||||
locationPicked: Location | null;
|
locationPicked: Location | null;
|
||||||
currentLocation: Location | null;
|
currentLocation: Location | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): LocationState => {
|
state: (): LocationState => {
|
||||||
return {
|
return {
|
||||||
locations: [],
|
locations: [],
|
||||||
locationPicked: null,
|
locationPicked: null,
|
||||||
currentLocation: null,
|
currentLocation: null,
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
getLocationById:
|
||||||
|
(state) =>
|
||||||
|
(id: number): Location | undefined => {
|
||||||
|
return state.locations.find((l) => l.id === id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setLocations(state, locations): void {
|
||||||
|
state.locations = locations;
|
||||||
},
|
},
|
||||||
getters: {
|
setLocationPicked(state, location: Location | null): void {
|
||||||
getLocationById:
|
if (null === location) {
|
||||||
(state) =>
|
state.locationPicked = null;
|
||||||
(id: number): Location | undefined => {
|
return;
|
||||||
return state.locations.find((l) => l.id === id);
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
setLocations(state, locations): void {
|
|
||||||
state.locations = locations;
|
|
||||||
},
|
|
||||||
setLocationPicked(state, location: Location | null): void {
|
|
||||||
if (null === location) {
|
|
||||||
state.locationPicked = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.locationPicked =
|
state.locationPicked =
|
||||||
state.locations.find((l) => l.id === location.id) || null;
|
state.locations.find((l) => l.id === location.id) || null;
|
||||||
},
|
},
|
||||||
setCurrentLocation(state, location: Location | null): void {
|
setCurrentLocation(state, location: Location | null): void {
|
||||||
if (null === location) {
|
if (null === location) {
|
||||||
state.currentLocation = null;
|
state.currentLocation = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.currentLocation =
|
state.currentLocation =
|
||||||
state.locations.find((l) => l.id === location.id) || null;
|
state.locations.find((l) => l.id === location.id) || null;
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
},
|
||||||
getLocations(ctx): Promise<void> {
|
actions: {
|
||||||
return getLocations().then((locations) => {
|
getLocations(ctx): Promise<void> {
|
||||||
ctx.commit("setLocations", locations);
|
return getLocations().then((locations) => {
|
||||||
return Promise.resolve();
|
ctx.commit("setLocations", locations);
|
||||||
});
|
return Promise.resolve();
|
||||||
},
|
});
|
||||||
getCurrentLocation(ctx): Promise<void> {
|
|
||||||
return whereami().then((location) => {
|
|
||||||
ctx.commit("setCurrentLocation", location);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
getCurrentLocation(ctx): Promise<void> {
|
||||||
|
return whereami().then((location) => {
|
||||||
|
ctx.commit("setCurrentLocation", location);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
} as Module<LocationState, State>;
|
} as Module<LocationState, State>;
|
||||||
|
|||||||
@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
|
|||||||
import { ActionContext } from "vuex";
|
import { ActionContext } from "vuex";
|
||||||
|
|
||||||
export interface MeState {
|
export interface MeState {
|
||||||
me: User | null;
|
me: User | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<MeState, State>;
|
type Context = ActionContext<MeState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): MeState => ({
|
state: (): MeState => ({
|
||||||
me: null,
|
me: null,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getMe: function (state: MeState): User | null {
|
getMe: function (state: MeState): User | null {
|
||||||
return state.me;
|
return state.me;
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mutations: {
|
},
|
||||||
setWhoAmi(state: MeState, me: User) {
|
mutations: {
|
||||||
state.me = me;
|
setWhoAmi(state: MeState, me: User) {
|
||||||
},
|
state.me = me;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="chill-red">
|
<h2 class="chill-red">
|
||||||
{{ $t("choose_your_calendar_user") }}
|
{{ $t("choose_your_calendar_user") }}
|
||||||
</h2>
|
</h2>
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="field"
|
name="field"
|
||||||
id="calendarUserSelector"
|
id="calendarUserSelector"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
label="value"
|
label="value"
|
||||||
:custom-label="transName"
|
:custom-label="transName"
|
||||||
:placeholder="$t('select_user')"
|
:placeholder="$t('select_user')"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:close-on-select="false"
|
:close-on-select="false"
|
||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
:model-value="value"
|
:model-value="value"
|
||||||
:select-label="$t('multiselect.select_label')"
|
:select-label="$t('multiselect.select_label')"
|
||||||
:deselect-label="$t('multiselect.deselect_label')"
|
:deselect-label="$t('multiselect.deselect_label')"
|
||||||
:selected-label="$t('multiselect.selected_label')"
|
:selected-label="$t('multiselect.selected_label')"
|
||||||
@select="selectUsers"
|
@select="selectUsers"
|
||||||
@remove="unSelectUsers"
|
@remove="unSelectUsers"
|
||||||
@close="coloriseSelectedValues"
|
@close="coloriseSelectedValues"
|
||||||
:options="options"
|
:options="options"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="myCalendar"
|
id="myCalendar"
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
v-model="showMyCalendarWidget"
|
v-model="showMyCalendarWidget"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" for="myCalendar">{{
|
<label class="form-check-label" for="myCalendar">{{
|
||||||
$t("show_my_calendar")
|
$t("show_my_calendar")
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="weekends"
|
id="weekends"
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
@click="toggleWeekends"
|
@click="toggleWeekends"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" for="weekends">{{
|
<label class="form-check-label" for="weekends">{{
|
||||||
$t("show_weekends")
|
$t("show_weekends")
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
|
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
|
||||||
@@ -53,206 +53,183 @@ import VueMultiselect from "vue-multiselect";
|
|||||||
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
|
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
|
||||||
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||||
"#8dd3c7",
|
"#8dd3c7",
|
||||||
"#ffffb3",
|
"#ffffb3",
|
||||||
"#bebada",
|
"#bebada",
|
||||||
"#fb8072",
|
"#fb8072",
|
||||||
"#80b1d3",
|
"#80b1d3",
|
||||||
"#fdb462",
|
"#fdb462",
|
||||||
"#b3de69",
|
"#b3de69",
|
||||||
"#fccde5",
|
"#fccde5",
|
||||||
"#d9d9d9",
|
"#d9d9d9",
|
||||||
"#bc80bd",
|
"#bc80bd",
|
||||||
"#ccebc5",
|
"#ccebc5",
|
||||||
"#ffed6f",
|
"#ffed6f",
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CalendarUserSelector",
|
name: "CalendarUserSelector",
|
||||||
components: { VueMultiselect },
|
components: { VueMultiselect },
|
||||||
props: [
|
props: [
|
||||||
"users",
|
"users",
|
||||||
"updateEventsSource",
|
"updateEventsSource",
|
||||||
"calendarEvents",
|
"calendarEvents",
|
||||||
"showMyCalendar",
|
"showMyCalendar",
|
||||||
"toggleMyCalendar",
|
"toggleMyCalendar",
|
||||||
"toggleWeekends",
|
"toggleWeekends",
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
errorMsg: [],
|
errorMsg: [],
|
||||||
value: [],
|
value: [],
|
||||||
options: [],
|
options: [],
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showMyCalendarWidget: {
|
||||||
|
set(value) {
|
||||||
|
this.toggleMyCalendar(value);
|
||||||
|
this.updateEventsSource();
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return this.showMyCalendar;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
},
|
||||||
showMyCalendarWidget: {
|
methods: {
|
||||||
set(value) {
|
init() {
|
||||||
this.toggleMyCalendar(value);
|
this.fetchData();
|
||||||
this.updateEventsSource();
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.showMyCalendar;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
fetchData() {
|
||||||
init() {
|
fetchCalendarRanges()
|
||||||
this.fetchData();
|
.then(
|
||||||
},
|
(calendarRanges) =>
|
||||||
fetchData() {
|
new Promise((resolve, reject) => {
|
||||||
fetchCalendarRanges()
|
let results = calendarRanges.results;
|
||||||
.then(
|
|
||||||
(calendarRanges) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
let results = calendarRanges.results;
|
|
||||||
|
|
||||||
let users = [];
|
let users = [];
|
||||||
|
|
||||||
results.forEach((i) => {
|
results.forEach((i) => {
|
||||||
if (!users.some((j) => i.user.id === j.id)) {
|
if (!users.some((j) => i.user.id === j.id)) {
|
||||||
let ratio = Math.floor(
|
let ratio = Math.floor(users.length / COLORS.length);
|
||||||
users.length / COLORS.length,
|
let colorIndex = users.length - ratio * COLORS.length;
|
||||||
);
|
users.push({
|
||||||
let colorIndex =
|
id: i.user.id,
|
||||||
users.length - ratio * COLORS.length;
|
username: i.user.username,
|
||||||
users.push({
|
color: COLORS[colorIndex],
|
||||||
id: i.user.id,
|
});
|
||||||
username: i.user.username,
|
|
||||||
color: COLORS[colorIndex],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let calendarEvents = [];
|
|
||||||
users.forEach((u) => {
|
|
||||||
let arr = results
|
|
||||||
.filter((i) => i.user.id === u.id)
|
|
||||||
.map((i) => ({
|
|
||||||
start: i.startDate.datetime,
|
|
||||||
end: i.endDate.datetime,
|
|
||||||
calendarRangeId: i.id,
|
|
||||||
sourceColor: u.color,
|
|
||||||
//display: 'background' // can be an option for the disponibility
|
|
||||||
}));
|
|
||||||
calendarEvents.push({
|
|
||||||
events: arr,
|
|
||||||
color: u.color,
|
|
||||||
textColor: "#444444",
|
|
||||||
editable: false,
|
|
||||||
id: u.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.users.loaded = users;
|
|
||||||
this.options = users;
|
|
||||||
|
|
||||||
this.calendarEvents.loaded = calendarEvents;
|
|
||||||
whoami().then(
|
|
||||||
(me) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
this.users.logged = me;
|
|
||||||
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 =
|
|
||||||
{
|
|
||||||
events: events,
|
|
||||||
color: "darkblue",
|
|
||||||
id: 1000,
|
|
||||||
editable: false,
|
|
||||||
};
|
|
||||||
this.calendarEvents.user =
|
|
||||||
calendarEventsCurrentUser;
|
|
||||||
|
|
||||||
this.selectUsers(
|
|
||||||
currentUser,
|
|
||||||
);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
this.errorMsg.push(error.message);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
transName(value) {
|
|
||||||
return `${value.username}`;
|
|
||||||
},
|
|
||||||
coloriseSelectedValues() {
|
|
||||||
let tags = document.querySelectorAll(
|
|
||||||
"div.multiselect__tags-wrap",
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
if (tags.hasChildNodes()) {
|
|
||||||
let children = tags.childNodes;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
let child = children[i];
|
|
||||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
this.users.selected.forEach((u) => {
|
|
||||||
if (child.hasChildNodes()) {
|
|
||||||
if (child.firstChild.innerText == u.username) {
|
|
||||||
child.style.background = u.color;
|
|
||||||
child.firstChild.style.color = "#444444";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
selectEvents() {
|
let calendarEvents = [];
|
||||||
let selectedUsersId = this.users.selected.map((a) => a.id);
|
users.forEach((u) => {
|
||||||
this.calendarEvents.selected = this.calendarEvents.loaded.filter(
|
let arr = results
|
||||||
(a) => selectedUsersId.includes(a.id),
|
.filter((i) => i.user.id === u.id)
|
||||||
);
|
.map((i) => ({
|
||||||
},
|
start: i.startDate.datetime,
|
||||||
selectUsers(value) {
|
end: i.endDate.datetime,
|
||||||
this.users.selected.push(value);
|
calendarRangeId: i.id,
|
||||||
this.coloriseSelectedValues();
|
sourceColor: u.color,
|
||||||
this.selectEvents();
|
//display: 'background' // can be an option for the disponibility
|
||||||
this.updateEventsSource();
|
}));
|
||||||
},
|
calendarEvents.push({
|
||||||
unSelectUsers(value) {
|
events: arr,
|
||||||
this.users.selected = this.users.selected.filter(
|
color: u.color,
|
||||||
(a) => a.id != value.id,
|
textColor: "#444444",
|
||||||
);
|
editable: false,
|
||||||
this.selectEvents();
|
id: u.id,
|
||||||
this.updateEventsSource();
|
});
|
||||||
},
|
});
|
||||||
|
|
||||||
|
this.users.loaded = users;
|
||||||
|
this.options = users;
|
||||||
|
|
||||||
|
this.calendarEvents.loaded = calendarEvents;
|
||||||
|
whoami().then(
|
||||||
|
(me) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
this.users.logged = me;
|
||||||
|
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 = {
|
||||||
|
events: events,
|
||||||
|
color: "darkblue",
|
||||||
|
id: 1000,
|
||||||
|
editable: false,
|
||||||
|
};
|
||||||
|
this.calendarEvents.user = calendarEventsCurrentUser;
|
||||||
|
|
||||||
|
this.selectUsers(currentUser);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
this.errorMsg.push(error.message);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
mounted() {
|
transName(value) {
|
||||||
this.init();
|
return `${value.username}`;
|
||||||
},
|
},
|
||||||
|
coloriseSelectedValues() {
|
||||||
|
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
|
||||||
|
|
||||||
|
if (tags.hasChildNodes()) {
|
||||||
|
let children = tags.childNodes;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
let child = children[i];
|
||||||
|
if (child.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
this.users.selected.forEach((u) => {
|
||||||
|
if (child.hasChildNodes()) {
|
||||||
|
if (child.firstChild.innerText == u.username) {
|
||||||
|
child.style.background = u.color;
|
||||||
|
child.firstChild.style.color = "#444444";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectEvents() {
|
||||||
|
let selectedUsersId = this.users.selected.map((a) => a.id);
|
||||||
|
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
|
||||||
|
selectedUsersId.includes(a.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
selectUsers(value) {
|
||||||
|
this.users.selected.push(value);
|
||||||
|
this.coloriseSelectedValues();
|
||||||
|
this.selectEvents();
|
||||||
|
this.updateEventsSource();
|
||||||
|
},
|
||||||
|
unSelectUsers(value) {
|
||||||
|
this.users.selected = this.users.selected.filter((a) => a.id != value.id);
|
||||||
|
this.selectEvents();
|
||||||
|
this.updateEventsSource();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
{# list used in context of person or accompanyingPeriod #}
|
{# list used in context of person, accompanyingPeriod or user #}
|
||||||
|
|
||||||
{% if calendarItems|length > 0 %}
|
<div class="item-bloc">
|
||||||
<div class="flex-table list-records context-accompanyingCourse">
|
<div class="item-row main">
|
||||||
|
<div class="item-col">
|
||||||
{% for calendar in calendarItems %}
|
<div class="wrap-header">
|
||||||
|
<div class="wl-row">
|
||||||
<div class="item-bloc">
|
{% if calendar.status == 'canceled' %}
|
||||||
<div class="item-row main">
|
<div class="badge rounded-pill bg-danger">
|
||||||
<div class="item-col">
|
<span>{{ 'chill_calendar.canceled'|trans }}: </span>
|
||||||
<div class="wrap-header">
|
<span>{{ calendar.cancelReason.name|localize_translatable_string }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="wl-row">
|
<div class="wl-row">
|
||||||
<div class="wl-col title">
|
<div class="wl-col title">
|
||||||
<p class="date-label">
|
<p class="date-label">
|
||||||
|
{% if calendar.status == 'canceled' %}
|
||||||
|
<del>
|
||||||
|
{% endif %}
|
||||||
{% if context == 'person' and calendar.context == 'accompanying_period' %}
|
{% if context == 'person' and calendar.context == 'accompanying_period' %}
|
||||||
<a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;">
|
<a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;">
|
||||||
<span class="badge bg-primary">
|
<span class="badge bg-primary">
|
||||||
@@ -19,6 +25,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if calendar.status == 'canceled' %}
|
||||||
|
<del>
|
||||||
|
{% endif %}
|
||||||
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
||||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||||
- {{ calendar.endDate|format_datetime('short', 'short') }}
|
- {{ calendar.endDate|format_datetime('short', 'short') }}
|
||||||
@@ -26,44 +35,46 @@
|
|||||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||||
- {{ calendar.endDate|format_datetime('none', 'short') }}
|
- {{ calendar.endDate|format_datetime('none', 'short') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
{% if calendar.status == 'canceled' %}
|
||||||
|
</del>
|
||||||
<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 %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<div class="duration short-message">
|
||||||
</div>
|
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||||
</div>
|
{{ calendar.duration|date('%H:%I') }}
|
||||||
|
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
||||||
<div class="item-col">
|
<!-- no sms will be sent -->
|
||||||
<ul class="list-content">
|
{% else %}
|
||||||
{% if calendar.mainUser is not empty %}
|
{% if calendar.smsStatus == 'sms_sent' %}
|
||||||
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
|
<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 %}
|
||||||
</ul>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if calendar.comment.comment is not empty
|
<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.users|length > 0
|
||||||
or calendar.thirdParties|length > 0
|
or calendar.thirdParties|length > 0
|
||||||
or calendar.users|length > 0 %}
|
or calendar.users|length > 0 %}
|
||||||
@@ -76,131 +87,133 @@
|
|||||||
} %}
|
} %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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 %}
|
||||||
|
|
||||||
|
{% if calendar.documents is not empty %}
|
||||||
|
<div class="item-row separator column">
|
||||||
|
<div>
|
||||||
|
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% 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>
|
||||||
|
|
||||||
{% 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>
|
||||||
</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>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if calendar.activity is not null %}
|
<div class="item-row separator">
|
||||||
<div class="item-row separator">
|
<ul class="record_actions">
|
||||||
<div class="item-col">
|
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
|
||||||
<div class="wrap-list">
|
{% if templates|length == 0 %}
|
||||||
<div class="wl-row">
|
<li>
|
||||||
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
|
<a class="btn btn-create"
|
||||||
<div class="wl-col list activity-linked">
|
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||||
<h2 class="badge-title">
|
{{ 'chill_calendar.Add a document'|trans }}
|
||||||
<span class="title_label"></span>
|
</a>
|
||||||
<span class="title_action">
|
</li>
|
||||||
{{ calendar.activity.type.name | localize_translatable_string }}
|
{% else %}
|
||||||
|
<li>
|
||||||
{% if calendar.activity.emergency %}
|
<div class="dropdown">
|
||||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
{% endif %}
|
{{ 'chill_calendar.Add a document'|trans }}
|
||||||
</span>
|
</button>
|
||||||
</h2>
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
<ul class="record_actions">
|
<a class="dropdown-item"
|
||||||
<li class="cancel">
|
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||||
<span class="createdBy">
|
{{ 'chill_calendar.Upload a document'|trans }}
|
||||||
{{ 'Created by'|trans }}
|
</a>
|
||||||
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
</li>
|
||||||
</span>
|
{% for template in templates %}
|
||||||
</li>
|
<li>
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
<a class="dropdown-item"
|
||||||
<li>
|
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
|
||||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
|
>
|
||||||
</li>
|
{{ template.name|localize_translatable_string }}
|
||||||
{% endif %}
|
</a>
|
||||||
</ul>
|
</li>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if calendar.activity is null and (
|
||||||
|
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
|
||||||
|
or
|
||||||
|
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
|
||||||
|
)
|
||||||
|
and calendar.status is not constant('STATUS_CANCELED', calendar)
|
||||||
|
%}
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-create"
|
||||||
|
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
||||||
|
{{ 'Transform to activity'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="item-row separator">
|
{% if calendar.isInvited(app.user) and not calendar.isCanceled %}
|
||||||
<ul class="record_actions">
|
|
||||||
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %}
|
|
||||||
{% if templates|length == 0 %}
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-create"
|
|
||||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
|
||||||
{{ 'chill_calendar.Add a document'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li>
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
{{ 'chill_calendar.Add a document'|trans }}
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item"
|
|
||||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
|
||||||
{{ 'chill_calendar.Upload a document'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% for template in templates %}
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item"
|
|
||||||
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
|
|
||||||
>
|
|
||||||
{{ template.name|localize_translatable_string }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if calendar.activity is null and (
|
|
||||||
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
|
|
||||||
or
|
|
||||||
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
|
|
||||||
)
|
|
||||||
%}
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-create"
|
|
||||||
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
|
||||||
{{ 'Transform to activity'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if (calendar.isInvited(app.user)) %}
|
|
||||||
{% set invite = calendar.inviteForUser(app.user) %}
|
{% set invite = calendar.inviteForUser(app.user) %}
|
||||||
<li>
|
<li>
|
||||||
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
|
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
|
||||||
@@ -213,12 +226,18 @@
|
|||||||
class="btn btn-show "></a>
|
class="btn btn-show "></a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %}
|
|
||||||
|
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
|
||||||
class="btn btn-update "></a>
|
class="btn btn-update "></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_cancel', { 'id': calendar.id } ) }}"
|
||||||
|
class="btn btn-action"><i class="bi bi-x-circle"></i> {{ 'Cancel'|trans }}</a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
|
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"
|
||||||
@@ -227,14 +246,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if calendarItems|length < paginator.getTotalItems %}
|
|
||||||
{{ chill_pagination(paginator) }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||||
|
|
||||||
|
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
|
||||||
|
|
||||||
|
{% block title 'chill_calendar.cancel_calendar_item'|trans %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{{ form_start(form) }}
|
||||||
|
|
||||||
|
{{ form_row(form.cancelReason) }}
|
||||||
|
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li class="cancel">
|
||||||
|
<a
|
||||||
|
class="btn btn-cancel"
|
||||||
|
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'id': accompanyingCourse.id } )}}"
|
||||||
|
>
|
||||||
|
{{ 'Cancel'|trans|chill_return_path_label }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-save' }, 'label': 'Save' } ) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(form) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "@ChillPerson/Person/layout.html.twig" %}
|
||||||
|
|
||||||
|
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
|
||||||
|
|
||||||
|
{% block title 'chill_calendar.cancel_calendar_item'|trans %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{{ form_start(form) }}
|
||||||
|
|
||||||
|
{{ form_row(form.cancelReason) }}
|
||||||
|
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li class="cancel">
|
||||||
|
<a
|
||||||
|
class="btn btn-cancel"
|
||||||
|
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'id': person.id } )}}"
|
||||||
|
>
|
||||||
|
{{ 'Cancel'|trans|chill_return_path_label }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-save' }, 'label': 'Save' } ) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(form) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -34,7 +34,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
|
{% if calendarItems|length > 0 %}
|
||||||
|
<div class="flex-table list-records context-accompanyingCourse">
|
||||||
|
{% for calendar in calendarItems %}
|
||||||
|
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if calendarItems|length < paginator.getTotalItems %}
|
||||||
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
|||||||
@@ -33,7 +33,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
|
{% if calendarItems|length > 0 %}
|
||||||
|
<div class="flex-table list-records context-person">
|
||||||
|
{% for calendar in calendarItems %}
|
||||||
|
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if calendarItems|length < paginator.getTotalItems %}
|
||||||
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{% block table_entities_thead_tr %}
|
{% block table_entities_thead_tr %}
|
||||||
<th>{{ 'Id'|trans }}</th>
|
<th>{{ 'Id'|trans }}</th>
|
||||||
<th>{{ 'Name'|trans }}</th>
|
<th>{{ 'Name'|trans }}</th>
|
||||||
<th>{{ 'canceledBy'|trans }}</th>
|
<th>{{ 'Canceled by'|trans }}</th>
|
||||||
<th>{{ 'active'|trans }}</th>
|
<th>{{ 'active'|trans }}</th>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -40,4 +40,4 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{% extends "@ChillMain/layout.html.twig" %}
|
||||||
|
|
||||||
|
{% set activeRouteKey = 'chill_calendar_invitations_list' %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'invite.list.title'|trans }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{{ 'invite.list.title'|trans }}</h1>
|
||||||
|
|
||||||
|
{% if invitations|length == 0 %}
|
||||||
|
<p class="chill-no-data-statement">
|
||||||
|
{{ "invite.list.none"|trans }}
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-table list-records">
|
||||||
|
{% for invitation in invitations %}
|
||||||
|
{% set calendar = invitation.getCalendar %}
|
||||||
|
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'user'}) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if invitations|length < paginator.getTotalItems %}
|
||||||
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_script_tags('mod_answer') }}
|
||||||
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('mod_answer') }}
|
||||||
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
|
{% endblock %}
|
||||||
@@ -19,6 +19,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Entity\CancelReason;
|
||||||
use libphonenumber\PhoneNumberFormat;
|
use libphonenumber\PhoneNumberFormat;
|
||||||
use libphonenumber\PhoneNumberUtil;
|
use libphonenumber\PhoneNumberUtil;
|
||||||
use Symfony\Component\Notifier\Message\SmsMessage;
|
use Symfony\Component\Notifier\Message\SmsMessage;
|
||||||
@@ -57,7 +58,7 @@ class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBu
|
|||||||
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
||||||
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]),
|
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]),
|
||||||
);
|
);
|
||||||
} elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus()) {
|
} elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus() && (null === $calendar->getCancelReason() || CancelReason::CANCELEDBY_PERSON !== $calendar->getCancelReason()->getCanceledBy())) {
|
||||||
$toUsers[] = new SmsMessage(
|
$toUsers[] = new SmsMessage(
|
||||||
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
||||||
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]),
|
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]),
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
<?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\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Controller\MyInvitationsController;
|
||||||
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\CalendarBundle\Entity\Invite;
|
||||||
|
use Chill\CalendarBundle\Repository\InviteRepository;
|
||||||
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class MyInvitationsControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
private MyInvitationsController $controller;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
// Create prophecies for dependencies
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
|
||||||
|
// Create controller instance
|
||||||
|
$this->controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up necessary services for AbstractController
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||||
|
$twig = $this->prophesize(Environment::class);
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($this->controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
|
||||||
|
// Create a mock container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
$container->has('security.token_storage')->willReturn(true);
|
||||||
|
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||||
|
$container->has('twig')->willReturn(true);
|
||||||
|
$container->get('twig')->willReturn($twig->reveal());
|
||||||
|
|
||||||
|
$containerProperty->setValue($this->controller, $container->reveal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMyInvitationsReturnsCorrectAmountOfInvitations(): void
|
||||||
|
{
|
||||||
|
// Create test user
|
||||||
|
$user = new User();
|
||||||
|
$user->setUsername('testuser');
|
||||||
|
|
||||||
|
// Create test invitations
|
||||||
|
$invite1 = new Invite();
|
||||||
|
$invite1->setUser($user);
|
||||||
|
$invite1->setStatus(Invite::PENDING);
|
||||||
|
|
||||||
|
$invite2 = new Invite();
|
||||||
|
$invite2->setUser($user);
|
||||||
|
$invite2->setStatus(Invite::ACCEPTED);
|
||||||
|
|
||||||
|
$invite3 = new Invite();
|
||||||
|
$invite3->setUser($user);
|
||||||
|
$invite3->setStatus(Invite::DECLINED);
|
||||||
|
|
||||||
|
$allInvitations = [$invite1, $invite2, $invite3];
|
||||||
|
$paginatedInvitations = [$invite1, $invite2]; // First page with 2 items per page
|
||||||
|
|
||||||
|
// Set up repository prophecies
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$inviteRepository->findBy(['user' => $user])->willReturn($allInvitations);
|
||||||
|
$inviteRepository->findBy(
|
||||||
|
['user' => $user],
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
2, // items per page
|
||||||
|
0 // offset
|
||||||
|
)->willReturn($paginatedInvitations);
|
||||||
|
|
||||||
|
// Set up paginator prophecies
|
||||||
|
$paginator = $this->prophesize(PaginatorInterface::class);
|
||||||
|
$paginator->getItemsPerPage()->willReturn(2);
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()->willReturn(0);
|
||||||
|
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$paginatorFactory->create(3)->willReturn($paginator->reveal());
|
||||||
|
|
||||||
|
// Set up doc generator repository
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
$docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]);
|
||||||
|
|
||||||
|
// Create controller with mocked dependencies
|
||||||
|
$controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up authorization checker to return true for ROLE_USER
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true);
|
||||||
|
|
||||||
|
// Set up token storage to return user
|
||||||
|
$token = $this->prophesize(TokenInterface::class);
|
||||||
|
$token->getUser()->willReturn($user);
|
||||||
|
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||||
|
$tokenStorage->getToken()->willReturn($token->reveal());
|
||||||
|
|
||||||
|
// Set up twig to return a response
|
||||||
|
$twig = $this->prophesize(Environment::class);
|
||||||
|
$twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [
|
||||||
|
'invitations' => $paginatedInvitations,
|
||||||
|
'paginator' => $paginator->reveal(),
|
||||||
|
'templates' => [],
|
||||||
|
])->willReturn('rendered content');
|
||||||
|
|
||||||
|
// Set up container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
$container->has('security.token_storage')->willReturn(true);
|
||||||
|
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||||
|
$container->has('twig')->willReturn(true);
|
||||||
|
$container->get('twig')->willReturn($twig->reveal());
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
$containerProperty->setValue($controller, $container->reveal());
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
// Execute the action
|
||||||
|
$response = $controller->myInvitations($request);
|
||||||
|
|
||||||
|
// Assert that response is successful
|
||||||
|
self::assertInstanceOf(Response::class, $response);
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
self::assertSame('rendered content', $response->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMyInvitationsPageLoads(): void
|
||||||
|
{
|
||||||
|
// Create test user
|
||||||
|
$user = new User();
|
||||||
|
$user->setUsername('testuser');
|
||||||
|
|
||||||
|
// Set up repository prophecies - no invitations
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$inviteRepository->findBy(['user' => $user])->willReturn([]);
|
||||||
|
$inviteRepository->findBy(
|
||||||
|
['user' => $user],
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
20, // default items per page
|
||||||
|
0 // offset
|
||||||
|
)->willReturn([]);
|
||||||
|
|
||||||
|
// Set up paginator prophecies
|
||||||
|
$paginator = $this->prophesize(PaginatorInterface::class);
|
||||||
|
$paginator->getItemsPerPage()->willReturn(20);
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()->willReturn(0);
|
||||||
|
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$paginatorFactory->create(0)->willReturn($paginator->reveal());
|
||||||
|
|
||||||
|
// Set up doc generator repository
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
$docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]);
|
||||||
|
|
||||||
|
// Create controller with mocked dependencies
|
||||||
|
$controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up authorization checker to return true for ROLE_USER
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true);
|
||||||
|
|
||||||
|
// Set up token storage to return user
|
||||||
|
$token = $this->prophesize(TokenInterface::class);
|
||||||
|
$token->getUser()->willReturn($user);
|
||||||
|
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||||
|
$tokenStorage->getToken()->willReturn($token->reveal());
|
||||||
|
|
||||||
|
// Set up twig to return a response
|
||||||
|
$twig = $this->prophesize(Environment::class);
|
||||||
|
$twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [
|
||||||
|
'invitations' => [],
|
||||||
|
'paginator' => $paginator->reveal(),
|
||||||
|
'templates' => [],
|
||||||
|
])->willReturn('empty page content');
|
||||||
|
|
||||||
|
// Set up container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
$container->has('security.token_storage')->willReturn(true);
|
||||||
|
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||||
|
$container->has('twig')->willReturn(true);
|
||||||
|
$container->get('twig')->willReturn($twig->reveal());
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
$containerProperty->setValue($controller, $container->reveal());
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
// Execute the action
|
||||||
|
$response = $controller->myInvitations($request);
|
||||||
|
|
||||||
|
// Assert that page loads successfully
|
||||||
|
self::assertInstanceOf(Response::class, $response);
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
self::assertSame('empty page content', $response->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMyInvitationsRequiresAuthentication(): void
|
||||||
|
{
|
||||||
|
// Create controller with minimal dependencies
|
||||||
|
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||||
|
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||||
|
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||||
|
|
||||||
|
$controller = new MyInvitationsController(
|
||||||
|
$inviteRepository->reveal(),
|
||||||
|
$paginatorFactory->reveal(),
|
||||||
|
$docGeneratorTemplateRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up authorization checker to return false for ROLE_USER
|
||||||
|
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER')->willReturn(false);
|
||||||
|
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(false);
|
||||||
|
|
||||||
|
// Set up container
|
||||||
|
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||||
|
$container->has('security.authorization_checker')->willReturn(true);
|
||||||
|
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||||
|
|
||||||
|
// Use reflection to set the container
|
||||||
|
$reflection = new \ReflectionClass($controller);
|
||||||
|
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||||
|
$containerProperty->setAccessible(true);
|
||||||
|
$containerProperty->setValue($controller, $container->reveal());
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
// Expect AccessDeniedException
|
||||||
|
$this->expectException(\Symfony\Component\Security\Core\Exception\AccessDeniedException::class);
|
||||||
|
|
||||||
|
// Execute the action
|
||||||
|
$controller->myInvitations($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,8 +31,7 @@ Will send SMS: Un SMS de rappel sera envoyé
|
|||||||
Will not send SMS: Aucun SMS de rappel ne sera envoyé
|
Will not send SMS: Aucun SMS de rappel ne sera envoyé
|
||||||
SMS already sent: Un SMS a été envoyé
|
SMS already sent: Un SMS a été envoyé
|
||||||
|
|
||||||
canceledBy: supprimé par
|
Canceled by: Annulé par
|
||||||
Canceled by: supprimé par
|
|
||||||
Calendar configuration: Gestion des rendez-vous
|
Calendar configuration: Gestion des rendez-vous
|
||||||
|
|
||||||
crud:
|
crud:
|
||||||
@@ -44,6 +43,14 @@ crud:
|
|||||||
title_edit: Modifier le motif d'annulation
|
title_edit: Modifier le motif d'annulation
|
||||||
|
|
||||||
chill_calendar:
|
chill_calendar:
|
||||||
|
canceled: Annulé
|
||||||
|
cancel_reason: Raison d'annulation
|
||||||
|
cancel_calendar_item: Annuler rendez-vous
|
||||||
|
calendar_canceled: Le rendez-vous a été annulé
|
||||||
|
canceled_by:
|
||||||
|
user: Utilisateur
|
||||||
|
person: Usager
|
||||||
|
other: Autre
|
||||||
Document: Document d'un rendez-vous
|
Document: Document d'un rendez-vous
|
||||||
form:
|
form:
|
||||||
The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement.
|
The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement.
|
||||||
@@ -86,6 +93,9 @@ invite:
|
|||||||
declined: Refusé
|
declined: Refusé
|
||||||
pending: En attente
|
pending: En attente
|
||||||
tentative: Accepté provisoirement
|
tentative: Accepté provisoirement
|
||||||
|
list:
|
||||||
|
none: Il n'y aucun invitation
|
||||||
|
title: Mes invitations
|
||||||
|
|
||||||
# exports
|
# exports
|
||||||
Exports of calendar: Exports des rendez-vous
|
Exports of calendar: Exports des rendez-vous
|
||||||
|
|||||||
@@ -20,4 +20,9 @@ use Doctrine\Persistence\ObjectRepository;
|
|||||||
interface DocGeneratorTemplateRepositoryInterface extends ObjectRepository
|
interface DocGeneratorTemplateRepositoryInterface extends ObjectRepository
|
||||||
{
|
{
|
||||||
public function countByEntity(string $entity): int;
|
public function countByEntity(string $entity): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|DocGeneratorTemplate[]
|
||||||
|
*/
|
||||||
|
public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template v-if="templates.length > 0">
|
<template v-if="templates.length > 0">
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
<h2>{{ $t("generate_document") }}</h2>
|
<h2>{{ $t("generate_document") }}</h2>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<slot name="label">
|
<slot name="label">
|
||||||
<label>{{ $t("select_a_template") }}</label>
|
<label>{{ $t("select_a_template") }}</label>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<select class="form-select" v-model="template">
|
<select class="form-select" v-model="template">
|
||||||
<option disabled selected value="">
|
<option disabled selected value="">
|
||||||
{{ $t("choose_a_template") }}
|
{{ $t("choose_a_template") }}
|
||||||
</option>
|
</option>
|
||||||
<template v-for="t in templates" :key="t.id">
|
<template v-for="t in templates" :key="t.id">
|
||||||
<option :value="t.id">
|
<option :value="t.id">
|
||||||
{{
|
{{ localizeString(t.name) || "Aucun nom défini" }}
|
||||||
localizeString(t.name) ||
|
</option>
|
||||||
"Aucun nom défini"
|
</template>
|
||||||
}}
|
</select>
|
||||||
</option>
|
<a
|
||||||
</template>
|
v-if="canGenerate"
|
||||||
</select>
|
class="btn btn-update btn-sm change-icon"
|
||||||
<a
|
:href="buildUrlGenerate"
|
||||||
v-if="canGenerate"
|
@click.prevent="clickGenerate($event, buildUrlGenerate)"
|
||||||
class="btn btn-update btn-sm change-icon"
|
><i class="fa fa-fw fa-cog"
|
||||||
:href="buildUrlGenerate"
|
/></a>
|
||||||
@click.prevent="
|
<a
|
||||||
clickGenerate($event, buildUrlGenerate)
|
v-else
|
||||||
"
|
class="btn btn-update btn-sm change-icon"
|
||||||
><i class="fa fa-fw fa-cog"
|
href="#"
|
||||||
/></a>
|
disabled
|
||||||
<a
|
><i class="fa fa-fw fa-cog"
|
||||||
v-else
|
/></a>
|
||||||
class="btn btn-update btn-sm change-icon"
|
|
||||||
href="#"
|
|
||||||
disabled
|
|
||||||
><i class="fa fa-fw fa-cog"
|
|
||||||
/></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row" v-if="hasDescription">
|
|
||||||
<div class="col-md-8 align-self-end">
|
|
||||||
<p>{{ getDescription }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row" v-if="hasDescription">
|
||||||
|
<div class="col-md-8 align-self-end">
|
||||||
|
<p>{{ getDescription }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -61,83 +56,83 @@ import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
|
|||||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PickTemplate",
|
name: "PickTemplate",
|
||||||
props: {
|
props: {
|
||||||
entityId: [String, Number],
|
entityId: [String, Number],
|
||||||
entityClass: {
|
entityClass: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
|
||||||
templates: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
preventDefaultMoveToGenerate: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ["goToGenerateDocument"],
|
templates: {
|
||||||
data() {
|
type: Array,
|
||||||
return {
|
required: true,
|
||||||
template: null,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
preventDefaultMoveToGenerate: {
|
||||||
canGenerate() {
|
type: Boolean,
|
||||||
return this.template != null;
|
required: false,
|
||||||
},
|
default: false,
|
||||||
hasDescription() {
|
},
|
||||||
if (this.template == null) {
|
},
|
||||||
return false;
|
emits: ["goToGenerateDocument"],
|
||||||
}
|
data() {
|
||||||
|
return {
|
||||||
|
template: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canGenerate() {
|
||||||
|
return this.template != null;
|
||||||
|
},
|
||||||
|
hasDescription() {
|
||||||
|
if (this.template == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
getDescription() {
|
getDescription() {
|
||||||
if (null === this.template) {
|
if (null === this.template) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
let desc = this.templates.find((t) => t.id === this.template);
|
let desc = this.templates.find((t) => t.id === this.template);
|
||||||
if (null === desc) {
|
if (null === desc) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return desc.description || "";
|
return desc.description || "";
|
||||||
},
|
},
|
||||||
buildUrlGenerate() {
|
buildUrlGenerate() {
|
||||||
if (null === this.template) {
|
if (null === this.template) {
|
||||||
return "#";
|
return "#";
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildLink(this.template, this.entityId, this.entityClass);
|
return buildLink(this.template, this.entityId, this.entityClass);
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
},
|
||||||
localizeString(str) {
|
methods: {
|
||||||
return localizeString(str);
|
localizeString(str) {
|
||||||
},
|
return localizeString(str);
|
||||||
clickGenerate(event, link) {
|
},
|
||||||
if (!this.preventDefaultMoveToGenerate) {
|
clickGenerate(event, link) {
|
||||||
window.location.assign(link);
|
if (!this.preventDefaultMoveToGenerate) {
|
||||||
}
|
window.location.assign(link);
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit("goToGenerateDocument", {
|
this.$emit("goToGenerateDocument", {
|
||||||
event,
|
event,
|
||||||
link,
|
link,
|
||||||
template: this.template,
|
template: this.template,
|
||||||
});
|
});
|
||||||
},
|
|
||||||
},
|
},
|
||||||
i18n: {
|
},
|
||||||
messages: {
|
i18n: {
|
||||||
fr: {
|
messages: {
|
||||||
generate_document: "Générer un document",
|
fr: {
|
||||||
select_a_template: "Choisir un modèle",
|
generate_document: "Générer un document",
|
||||||
choose_a_template: "Choisir",
|
select_a_template: "Choisir un modèle",
|
||||||
},
|
choose_a_template: "Choisir",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface
|
|||||||
/**
|
/**
|
||||||
* @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion>
|
* @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)]
|
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true, fetch: 'EAGER')]
|
||||||
private Collection&Selectable $versions;
|
private Collection&Selectable $versions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,20 +6,20 @@ const algo = "AES-CBC";
|
|||||||
const URL_POST = "/asyncupload/temp_url/generate/post";
|
const URL_POST = "/asyncupload/temp_url/generate/post";
|
||||||
|
|
||||||
const keyDefinition = {
|
const keyDefinition = {
|
||||||
name: algo,
|
name: algo,
|
||||||
length: 256,
|
length: 256,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFilename = (): string => {
|
const createFilename = (): string => {
|
||||||
let text = "";
|
let text = "";
|
||||||
const possible =
|
const possible =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,59 +30,59 @@ const createFilename = (): string => {
|
|||||||
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
|
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
|
||||||
*/
|
*/
|
||||||
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
|
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
|
||||||
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
|
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadVersion = async (
|
export const uploadVersion = async (
|
||||||
uploadFile: ArrayBuffer,
|
uploadFile: ArrayBuffer,
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append("expires_delay", "180");
|
params.append("expires_delay", "180");
|
||||||
params.append("submit_delay", "180");
|
params.append("submit_delay", "180");
|
||||||
const asyncData: PostStoreObjectSignature = await makeFetch(
|
const asyncData: PostStoreObjectSignature = await makeFetch(
|
||||||
"GET",
|
"GET",
|
||||||
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
||||||
"?" +
|
"?" +
|
||||||
params.toString(),
|
params.toString(),
|
||||||
);
|
);
|
||||||
const suffix = createFilename();
|
const suffix = createFilename();
|
||||||
const filename = asyncData.prefix + suffix;
|
const filename = asyncData.prefix + suffix;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("redirect", asyncData.redirect);
|
formData.append("redirect", asyncData.redirect);
|
||||||
formData.append("max_file_size", asyncData.max_file_size.toString());
|
formData.append("max_file_size", asyncData.max_file_size.toString());
|
||||||
formData.append("max_file_count", asyncData.max_file_count.toString());
|
formData.append("max_file_count", asyncData.max_file_count.toString());
|
||||||
formData.append("expires", asyncData.expires.toString());
|
formData.append("expires", asyncData.expires.toString());
|
||||||
formData.append("signature", asyncData.signature);
|
formData.append("signature", asyncData.signature);
|
||||||
formData.append(filename, new Blob([uploadFile]), suffix);
|
formData.append(filename, new Blob([uploadFile]), suffix);
|
||||||
|
|
||||||
const response = await window.fetch(asyncData.url, {
|
const response = await window.fetch(asyncData.url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Error while sending file to store", response);
|
console.error("Error while sending file to store", response);
|
||||||
throw new Error(response.statusText);
|
throw new Error(response.statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(filename);
|
return Promise.resolve(filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const encryptFile = async (
|
export const encryptFile = async (
|
||||||
originalFile: ArrayBuffer,
|
originalFile: ArrayBuffer,
|
||||||
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||||
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
||||||
"encrypt",
|
"encrypt",
|
||||||
"decrypt",
|
"decrypt",
|
||||||
]);
|
]);
|
||||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
{ name: algo, iv: iv },
|
{ name: algo, iv: iv },
|
||||||
key,
|
key,
|
||||||
originalFile,
|
originalFile,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.resolve([encrypted, iv, exportedKey]);
|
return Promise.resolve([encrypted, iv, exportedKey]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
|||||||
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
|
||||||
export function fetch_generic_docs_by_accompanying_period(
|
export function fetch_generic_docs_by_accompanying_period(
|
||||||
periodId: number,
|
periodId: number,
|
||||||
): Promise<GenericDocForAccompanyingPeriod[]> {
|
): Promise<GenericDocForAccompanyingPeriod[]> {
|
||||||
return fetchResults(
|
return fetchResults(
|
||||||
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,117 +6,116 @@ import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vue
|
|||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
const startApp = (
|
const startApp = (
|
||||||
divElement: HTMLDivElement,
|
divElement: HTMLDivElement,
|
||||||
collectionEntry: null | HTMLLIElement,
|
collectionEntry: null | HTMLLIElement,
|
||||||
): void => {
|
): void => {
|
||||||
console.log("app started", divElement);
|
console.log("app started", divElement);
|
||||||
|
|
||||||
const inputTitle = collectionEntry?.querySelector("input[type='text']");
|
const inputTitle = collectionEntry?.querySelector("input[type='text']");
|
||||||
|
|
||||||
const input_stored_object: HTMLInputElement | null =
|
const input_stored_object: HTMLInputElement | null = divElement.querySelector(
|
||||||
divElement.querySelector("input[data-stored-object]");
|
"input[data-stored-object]",
|
||||||
if (null === input_stored_object) {
|
);
|
||||||
throw new Error("input to stored object not found");
|
if (null === input_stored_object) {
|
||||||
}
|
throw new Error("input to stored object not found");
|
||||||
|
}
|
||||||
|
|
||||||
let existingDoc: StoredObject | null = null;
|
let existingDoc: StoredObject | null = null;
|
||||||
if (input_stored_object.value !== "") {
|
if (input_stored_object.value !== "") {
|
||||||
existingDoc = JSON.parse(input_stored_object.value);
|
existingDoc = JSON.parse(input_stored_object.value);
|
||||||
}
|
}
|
||||||
const app_container = document.createElement("div");
|
const app_container = document.createElement("div");
|
||||||
divElement.appendChild(app_container);
|
divElement.appendChild(app_container);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template:
|
template:
|
||||||
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
existingDoc: existingDoc,
|
existingDoc: existingDoc,
|
||||||
inputTitle: inputTitle,
|
inputTitle: inputTitle,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
DropFileWidget,
|
DropFileWidget,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addDocument: function ({
|
addDocument: function ({
|
||||||
stored_object,
|
stored_object,
|
||||||
stored_object_version,
|
stored_object_version,
|
||||||
file_name,
|
file_name,
|
||||||
}: {
|
}: {
|
||||||
stored_object: StoredObject;
|
stored_object: StoredObject;
|
||||||
stored_object_version: StoredObjectVersion;
|
stored_object_version: StoredObjectVersion;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
}): void {
|
}): void {
|
||||||
stored_object.title = file_name;
|
stored_object.title = file_name;
|
||||||
console.log("object added", stored_object);
|
console.log("object added", stored_object);
|
||||||
console.log("version added", stored_object_version);
|
console.log("version added", stored_object_version);
|
||||||
this.$data.existingDoc = stored_object;
|
this.$data.existingDoc = stored_object;
|
||||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||||
input_stored_object.value = JSON.stringify(
|
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
|
||||||
this.$data.existingDoc,
|
if (this.$data.inputTitle) {
|
||||||
);
|
if (!this.$data.inputTitle?.value) {
|
||||||
if (this.$data.inputTitle) {
|
this.$data.inputTitle.value = file_name;
|
||||||
if (!this.$data.inputTitle?.value) {
|
}
|
||||||
this.$data.inputTitle.value = file_name;
|
}
|
||||||
}
|
},
|
||||||
}
|
removeDocument: function (object: StoredObject): void {
|
||||||
},
|
console.log("catch remove document", object);
|
||||||
removeDocument: function (object: StoredObject): void {
|
input_stored_object.value = "";
|
||||||
console.log("catch remove document", object);
|
this.$data.existingDoc = undefined;
|
||||||
input_stored_object.value = "";
|
console.log("collectionEntry", collectionEntry);
|
||||||
this.$data.existingDoc = undefined;
|
|
||||||
console.log("collectionEntry", collectionEntry);
|
|
||||||
|
|
||||||
if (null !== collectionEntry) {
|
if (null !== collectionEntry) {
|
||||||
console.log("will remove collection");
|
console.log("will remove collection");
|
||||||
collectionEntry.remove();
|
collectionEntry.remove();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).mount(app_container);
|
app.use(i18n).mount(app_container);
|
||||||
};
|
};
|
||||||
window.addEventListener("collection-add-entry", ((
|
window.addEventListener("collection-add-entry", ((
|
||||||
e: CustomEvent<CollectionEventPayload>,
|
e: CustomEvent<CollectionEventPayload>,
|
||||||
) => {
|
) => {
|
||||||
const detail = e.detail;
|
const detail = e.detail;
|
||||||
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
||||||
"div[data-stored-object]",
|
"div[data-stored-object]",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (null === divElement) {
|
if (null === divElement) {
|
||||||
throw new Error("div[data-stored-object] not found");
|
throw new Error("div[data-stored-object] not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
startApp(divElement, detail.entry);
|
startApp(divElement, detail.entry);
|
||||||
}) as EventListener);
|
}) as EventListener);
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
||||||
"div[data-stored-object]",
|
"div[data-stored-object]",
|
||||||
);
|
);
|
||||||
|
|
||||||
upload_inputs.forEach((input: HTMLDivElement): void => {
|
upload_inputs.forEach((input: HTMLDivElement): void => {
|
||||||
// test for a parent to check if this is a collection entry
|
// test for a parent to check if this is a collection entry
|
||||||
let collectionEntry: null | HTMLLIElement = null;
|
let collectionEntry: null | HTMLLIElement = null;
|
||||||
const parent = input.parentElement;
|
const parent = input.parentElement;
|
||||||
console.log("parent", parent);
|
console.log("parent", parent);
|
||||||
if (null !== parent) {
|
if (null !== parent) {
|
||||||
const grandParent = parent.parentElement;
|
const grandParent = parent.parentElement;
|
||||||
console.log("grandParent", grandParent);
|
console.log("grandParent", grandParent);
|
||||||
if (null !== grandParent) {
|
if (null !== grandParent) {
|
||||||
if (
|
if (
|
||||||
grandParent.tagName.toLowerCase() === "li" &&
|
grandParent.tagName.toLowerCase() === "li" &&
|
||||||
grandParent.classList.contains("entry")
|
grandParent.classList.contains("entry")
|
||||||
) {
|
) {
|
||||||
collectionEntry = grandParent as HTMLLIElement;
|
collectionEntry = grandParent as HTMLLIElement;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
startApp(input, collectionEntry);
|
}
|
||||||
});
|
}
|
||||||
|
startApp(input, collectionEntry);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification";
|
|||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", function (e) {
|
window.addEventListener("DOMContentLoaded", function (e) {
|
||||||
document
|
document
|
||||||
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
||||||
.forEach((el) => {
|
.forEach((el) => {
|
||||||
const storedObject = JSON.parse(
|
const storedObject = JSON.parse(
|
||||||
el.dataset.storedObject as string,
|
el.dataset.storedObject as string,
|
||||||
) as StoredObject;
|
) as StoredObject;
|
||||||
const title = el.dataset.title as string;
|
const title = el.dataset.title as string;
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: { DownloadButton },
|
components: { DownloadButton },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
storedObject,
|
storedObject,
|
||||||
title,
|
title,
|
||||||
classes: { btn: true, "btn-outline-primary": true },
|
classes: { btn: true, "btn-outline-primary": true },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template:
|
template:
|
||||||
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).use(ToastPlugin).mount(el);
|
app.use(i18n).use(ToastPlugin).mount(el);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,66 +8,66 @@ import ToastPlugin from "vue-toast-notification";
|
|||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", function (e) {
|
window.addEventListener("DOMContentLoaded", function (e) {
|
||||||
document
|
document
|
||||||
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
||||||
.forEach((el) => {
|
.forEach((el) => {
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: { DocumentActionButtonsGroup },
|
components: { DocumentActionButtonsGroup },
|
||||||
data() {
|
data() {
|
||||||
const datasets = el.dataset as {
|
const datasets = el.dataset as {
|
||||||
filename: string;
|
filename: string;
|
||||||
canEdit: string;
|
canEdit: string;
|
||||||
storedObject: string;
|
storedObject: string;
|
||||||
buttonSmall: string;
|
buttonSmall: string;
|
||||||
davLink: string;
|
davLink: string;
|
||||||
davLinkExpiration: string;
|
davLinkExpiration: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const storedObject = JSON.parse(
|
const storedObject = JSON.parse(
|
||||||
datasets.storedObject,
|
datasets.storedObject,
|
||||||
) as StoredObject,
|
) as StoredObject,
|
||||||
filename = datasets.filename,
|
filename = datasets.filename,
|
||||||
canEdit = datasets.canEdit === "1",
|
canEdit = datasets.canEdit === "1",
|
||||||
small = datasets.buttonSmall === "1",
|
small = datasets.buttonSmall === "1",
|
||||||
davLink =
|
davLink =
|
||||||
"davLink" in datasets && datasets.davLink !== ""
|
"davLink" in datasets && datasets.davLink !== ""
|
||||||
? datasets.davLink
|
? datasets.davLink
|
||||||
: null,
|
: null,
|
||||||
davLinkExpiration =
|
davLinkExpiration =
|
||||||
"davLinkExpiration" in datasets
|
"davLinkExpiration" in datasets
|
||||||
? Number.parseInt(datasets.davLinkExpiration)
|
? Number.parseInt(datasets.davLinkExpiration)
|
||||||
: null;
|
: null;
|
||||||
return {
|
return {
|
||||||
storedObject,
|
storedObject,
|
||||||
filename,
|
filename,
|
||||||
canEdit,
|
canEdit,
|
||||||
small,
|
small,
|
||||||
davLink,
|
davLink,
|
||||||
davLinkExpiration,
|
davLinkExpiration,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template:
|
template:
|
||||||
'<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
'<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
||||||
methods: {
|
methods: {
|
||||||
onStoredObjectStatusChange: function (
|
onStoredObjectStatusChange: function (
|
||||||
newStatus: StoredObjectStatusChange,
|
newStatus: StoredObjectStatusChange,
|
||||||
): void {
|
): void {
|
||||||
this.$data.storedObject.status = newStatus.status;
|
this.$data.storedObject.status = newStatus.status;
|
||||||
this.$data.storedObject.filename = newStatus.filename;
|
this.$data.storedObject.filename = newStatus.filename;
|
||||||
this.$data.storedObject.type = newStatus.type;
|
this.$data.storedObject.type = newStatus.type;
|
||||||
|
|
||||||
// remove eventual div which inform pending status
|
// remove eventual div which inform pending status
|
||||||
document
|
document
|
||||||
.querySelectorAll(
|
.querySelectorAll(
|
||||||
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
||||||
)
|
)
|
||||||
.forEach(function (el) {
|
.forEach(function (el) {
|
||||||
el.remove();
|
el.remove();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).use(ToastPlugin).mount(el);
|
app.use(i18n).use(ToastPlugin).mount(el);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,73 +4,73 @@ import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpe
|
|||||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
||||||
|
|
||||||
export interface StoredObject {
|
export interface StoredObject {
|
||||||
id: number;
|
id: number;
|
||||||
title: string | null;
|
title: string | null;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
status: StoredObjectStatus;
|
status: StoredObjectStatus;
|
||||||
currentVersion:
|
currentVersion:
|
||||||
| null
|
| null
|
||||||
| StoredObjectVersionCreated
|
| StoredObjectVersionCreated
|
||||||
| StoredObjectVersionPersisted;
|
| StoredObjectVersionPersisted;
|
||||||
totalVersions: number;
|
totalVersions: number;
|
||||||
datas: object;
|
datas: object;
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
creationDate: DateTime;
|
creationDate: DateTime;
|
||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
createdBy: User | null;
|
createdBy: User | null;
|
||||||
_permissions: {
|
_permissions: {
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
canSee: boolean;
|
canSee: boolean;
|
||||||
};
|
};
|
||||||
_links?: {
|
_links?: {
|
||||||
dav_link?: {
|
dav_link?: {
|
||||||
href: string;
|
href: string;
|
||||||
expiration: number;
|
expiration: number;
|
||||||
};
|
|
||||||
downloadLink?: SignedUrlGet;
|
|
||||||
};
|
};
|
||||||
|
downloadLink?: SignedUrlGet;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersion {
|
export interface StoredObjectVersion {
|
||||||
/**
|
/**
|
||||||
* filename of the object in the object storage
|
* filename of the object in the object storage
|
||||||
*/
|
*/
|
||||||
filename: string;
|
filename: string;
|
||||||
iv: number[];
|
iv: number[];
|
||||||
keyInfos: JsonWebKey;
|
keyInfos: JsonWebKey;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersionCreated extends StoredObjectVersion {
|
export interface StoredObjectVersionCreated extends StoredObjectVersion {
|
||||||
persisted: false;
|
persisted: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersionPersisted
|
export interface StoredObjectVersionPersisted
|
||||||
extends StoredObjectVersionCreated {
|
extends StoredObjectVersionCreated {
|
||||||
version: number;
|
version: number;
|
||||||
id: number;
|
id: number;
|
||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
createdBy: User | null;
|
createdBy: User | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectStatusChange {
|
export interface StoredObjectStatusChange {
|
||||||
id: number;
|
id: number;
|
||||||
filename: string;
|
filename: string;
|
||||||
status: StoredObjectStatus;
|
status: StoredObjectStatus;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersionWithPointInTime
|
export interface StoredObjectVersionWithPointInTime
|
||||||
extends StoredObjectVersionPersisted {
|
extends StoredObjectVersionPersisted {
|
||||||
"point-in-times": StoredObjectPointInTime[];
|
"point-in-times": StoredObjectPointInTime[];
|
||||||
"from-restored": StoredObjectVersionPersisted | null;
|
"from-restored": StoredObjectVersionPersisted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectPointInTime {
|
export interface StoredObjectPointInTime {
|
||||||
id: number;
|
id: number;
|
||||||
byUser: User | null;
|
byUser: User | null;
|
||||||
reason: "keep-before-conversion" | "keep-by-user";
|
reason: "keep-before-conversion" | "keep-by-user";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,63 +82,63 @@ export type WopiEditButtonExecutableBeforeLeaveFunction = () => Promise<void>;
|
|||||||
* Object containing information for performering a POST request to a swift object store
|
* Object containing information for performering a POST request to a swift object store
|
||||||
*/
|
*/
|
||||||
export interface PostStoreObjectSignature {
|
export interface PostStoreObjectSignature {
|
||||||
method: "POST";
|
method: "POST";
|
||||||
max_file_size: number;
|
max_file_size: number;
|
||||||
max_file_count: 1;
|
max_file_count: 1;
|
||||||
expires: number;
|
expires: number;
|
||||||
submit_delay: 180;
|
submit_delay: 180;
|
||||||
redirect: string;
|
redirect: string;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
url: string;
|
url: string;
|
||||||
signature: string;
|
signature: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PDFPage {
|
export interface PDFPage {
|
||||||
index: number;
|
index: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
export interface SignatureZone {
|
export interface SignatureZone {
|
||||||
index: number | null;
|
index: number | null;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
PDFPage: PDFPage;
|
PDFPage: PDFPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Signature {
|
export interface Signature {
|
||||||
id: number;
|
id: number;
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
zones: SignatureZone[];
|
zones: SignatureZone[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SignedState =
|
export type SignedState =
|
||||||
| "pending"
|
| "pending"
|
||||||
| "signed"
|
| "signed"
|
||||||
| "rejected"
|
| "rejected"
|
||||||
| "canceled"
|
| "canceled"
|
||||||
| "error";
|
| "error";
|
||||||
|
|
||||||
export interface CheckSignature {
|
export interface CheckSignature {
|
||||||
state: SignedState;
|
state: SignedState;
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CanvasEvent = "select" | "add";
|
export type CanvasEvent = "select" | "add";
|
||||||
|
|
||||||
export interface ZoomLevel {
|
export interface ZoomLevel {
|
||||||
id: number;
|
id: number;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
label: {
|
label: {
|
||||||
fr?: string;
|
fr?: string;
|
||||||
nl?: string;
|
nl?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDoc {
|
export interface GenericDoc {
|
||||||
type: "doc_store_generic_doc";
|
type: "doc_store_generic_doc";
|
||||||
key: string;
|
key: string;
|
||||||
context: "person" | "accompanying-period";
|
context: "person" | "accompanying-period";
|
||||||
doc_date: DateTime;
|
doc_date: DateTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
||||||
<button
|
<button
|
||||||
:class="
|
:class="
|
||||||
Object.assign({
|
Object.assign({
|
||||||
btn: true,
|
btn: true,
|
||||||
'btn-outline-primary': true,
|
'btn-outline-primary': true,
|
||||||
'dropdown-toggle': true,
|
'dropdown-toggle': true,
|
||||||
'btn-sm': props.small,
|
'btn-sm': props.small,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
Actions
|
Actions
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li v-if="isEditableOnline">
|
<li v-if="isEditableOnline">
|
||||||
<wopi-edit-button
|
<wopi-edit-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
:execute-before-leave="props.executeBeforeLeave"
|
:execute-before-leave="props.executeBeforeLeave"
|
||||||
></wopi-edit-button>
|
></wopi-edit-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isEditableOnDesktop">
|
<li v-if="isEditableOnDesktop">
|
||||||
<desktop-edit-button
|
<desktop-edit-button
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
:edit-link="props.davLink"
|
:edit-link="props.davLink"
|
||||||
:expiration-link="props.davLinkExpiration"
|
:expiration-link="props.davLinkExpiration"
|
||||||
></desktop-edit-button>
|
></desktop-edit-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isConvertibleToPdf">
|
<li v-if="isConvertibleToPdf">
|
||||||
<convert-button
|
<convert-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:filename="filename"
|
:filename="filename"
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
></convert-button>
|
></convert-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isDownloadable">
|
<li v-if="isDownloadable">
|
||||||
<download-button
|
<download-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:at-version="props.storedObject.currentVersion"
|
:at-version="props.storedObject.currentVersion"
|
||||||
:filename="filename"
|
:filename="filename"
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
:display-action-string-in-button="true"
|
:display-action-string-in-button="true"
|
||||||
></download-button>
|
></download-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isHistoryViewable">
|
<li v-if="isHistoryViewable">
|
||||||
<history-button
|
<history-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:can-edit="
|
:can-edit="canEdit && props.storedObject._permissions.canEdit"
|
||||||
canEdit && props.storedObject._permissions.canEdit
|
></history-button>
|
||||||
"
|
</li>
|
||||||
></history-button>
|
</ul>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
<div v-else-if="'pending' === props.storedObject.status">
|
||||||
</div>
|
<div class="btn btn-outline-info">Génération en cours</div>
|
||||||
<div v-else-if="'pending' === props.storedObject.status">
|
</div>
|
||||||
<div class="btn btn-outline-info">Génération en cours</div>
|
<div v-else-if="'failure' === props.storedObject.status">
|
||||||
</div>
|
<div class="btn btn-outline-danger">La génération a échoué</div>
|
||||||
<div v-else-if="'failure' === props.storedObject.status">
|
</div>
|
||||||
<div class="btn btn-outline-danger">La génération a échoué</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -70,68 +68,66 @@ import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
|
|||||||
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
||||||
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
||||||
import {
|
import {
|
||||||
is_extension_editable,
|
is_extension_editable,
|
||||||
is_extension_viewable,
|
is_extension_viewable,
|
||||||
is_object_ready,
|
is_object_ready,
|
||||||
} from "./StoredObjectButton/helpers";
|
} from "./StoredObjectButton/helpers";
|
||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectStatusChange,
|
StoredObjectStatusChange,
|
||||||
StoredObjectVersion,
|
StoredObjectVersion,
|
||||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
|
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
|
||||||
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
|
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
|
||||||
|
|
||||||
interface DocumentActionButtonsGroupConfig {
|
interface DocumentActionButtonsGroupConfig {
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
canEdit?: boolean;
|
canEdit?: boolean;
|
||||||
canDownload?: boolean;
|
canDownload?: boolean;
|
||||||
canConvertPdf?: boolean;
|
canConvertPdf?: boolean;
|
||||||
returnPath?: string;
|
returnPath?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will be the filename displayed to the user when he·she download the document
|
* Will be the filename displayed to the user when he·she download the document
|
||||||
* (the document will be saved on his disk with this name)
|
* (the document will be saved on his disk with this name)
|
||||||
*
|
*
|
||||||
* If not set, 'document' will be used.
|
* If not set, 'document' will be used.
|
||||||
*/
|
*/
|
||||||
filename?: string;
|
filename?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set, will execute this function before leaving to the editor
|
* If set, will execute this function before leaving to the editor
|
||||||
*/
|
*/
|
||||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a link to download and edit file using webdav
|
* a link to download and edit file using webdav
|
||||||
*/
|
*/
|
||||||
davLink?: string;
|
davLink?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the expiration date of the download, as a unix timestamp
|
* the expiration date of the download, as a unix timestamp
|
||||||
*/
|
*/
|
||||||
davLinkExpiration?: number;
|
davLinkExpiration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit =
|
const emit =
|
||||||
defineEmits<
|
defineEmits<
|
||||||
(
|
(
|
||||||
e: "onStoredObjectStatusChange",
|
e: "onStoredObjectStatusChange",
|
||||||
newStatus: StoredObjectStatusChange,
|
newStatus: StoredObjectStatusChange,
|
||||||
) => void
|
) => void
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
||||||
small: false,
|
small: false,
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
canDownload: true,
|
canDownload: true,
|
||||||
canConvertPdf: true,
|
canConvertPdf: true,
|
||||||
returnPath:
|
returnPath:
|
||||||
window.location.pathname +
|
window.location.pathname + window.location.search + window.location.hash,
|
||||||
window.location.search +
|
|
||||||
window.location.hash,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,93 +141,93 @@ let tryiesForReady = 0;
|
|||||||
const maxTryiesForReady = 120;
|
const maxTryiesForReady = 120;
|
||||||
|
|
||||||
const isButtonGroupDisplayable = computed<boolean>(() => {
|
const isButtonGroupDisplayable = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
isDownloadable.value ||
|
isDownloadable.value ||
|
||||||
isEditableOnline.value ||
|
isEditableOnline.value ||
|
||||||
isEditableOnDesktop.value ||
|
isEditableOnDesktop.value ||
|
||||||
isConvertibleToPdf.value
|
isConvertibleToPdf.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDownloadable = computed<boolean>(() => {
|
const isDownloadable = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
props.storedObject.status === "ready" ||
|
props.storedObject.status === "ready" ||
|
||||||
// happens when the stored object version is just added, but not persisted
|
// happens when the stored object version is just added, but not persisted
|
||||||
(props.storedObject.currentVersion !== null &&
|
(props.storedObject.currentVersion !== null &&
|
||||||
props.storedObject.status === "empty")
|
props.storedObject.status === "empty")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEditableOnline = computed<boolean>(() => {
|
const isEditableOnline = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
props.storedObject.status === "ready" &&
|
props.storedObject.status === "ready" &&
|
||||||
props.storedObject._permissions.canEdit &&
|
props.storedObject._permissions.canEdit &&
|
||||||
props.canEdit &&
|
props.canEdit &&
|
||||||
props.storedObject.currentVersion !== null &&
|
props.storedObject.currentVersion !== null &&
|
||||||
is_extension_editable(props.storedObject.currentVersion.type) &&
|
is_extension_editable(props.storedObject.currentVersion.type) &&
|
||||||
props.storedObject.currentVersion.persisted !== false
|
props.storedObject.currentVersion.persisted !== false
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEditableOnDesktop = computed<boolean>(() => {
|
const isEditableOnDesktop = computed<boolean>(() => {
|
||||||
return isEditableOnline.value;
|
return isEditableOnline.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isConvertibleToPdf = computed<boolean>(() => {
|
const isConvertibleToPdf = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
props.storedObject.status === "ready" &&
|
props.storedObject.status === "ready" &&
|
||||||
props.storedObject._permissions.canSee &&
|
props.storedObject._permissions.canSee &&
|
||||||
props.canConvertPdf &&
|
props.canConvertPdf &&
|
||||||
props.storedObject.currentVersion !== null &&
|
props.storedObject.currentVersion !== null &&
|
||||||
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
||||||
props.storedObject.currentVersion.type !== "application/pdf" &&
|
props.storedObject.currentVersion.type !== "application/pdf" &&
|
||||||
props.storedObject.currentVersion.persisted !== false
|
props.storedObject.currentVersion.persisted !== false
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isHistoryViewable = computed<boolean>(() => {
|
const isHistoryViewable = computed<boolean>(() => {
|
||||||
return props.storedObject.status === "ready";
|
return props.storedObject.status === "ready";
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkForReady = function (): void {
|
const checkForReady = function (): void {
|
||||||
if (
|
if (
|
||||||
"ready" === props.storedObject.status ||
|
"ready" === props.storedObject.status ||
|
||||||
"empty" === props.storedObject.status ||
|
"empty" === props.storedObject.status ||
|
||||||
"failure" === props.storedObject.status ||
|
"failure" === props.storedObject.status ||
|
||||||
// stop reloading if the page stays opened for a long time
|
// stop reloading if the page stays opened for a long time
|
||||||
tryiesForReady > maxTryiesForReady
|
tryiesForReady > maxTryiesForReady
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tryiesForReady = tryiesForReady + 1;
|
tryiesForReady = tryiesForReady + 1;
|
||||||
|
|
||||||
setTimeout(onObjectNewStatusCallback, 5000);
|
setTimeout(onObjectNewStatusCallback, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onObjectNewStatusCallback = async function (): Promise<void> {
|
const onObjectNewStatusCallback = async function (): Promise<void> {
|
||||||
if (props.storedObject.status === "stored_object_created") {
|
if (props.storedObject.status === "stored_object_created") {
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const new_status = await is_object_ready(props.storedObject);
|
|
||||||
if (props.storedObject.status !== new_status.status) {
|
|
||||||
emit("onStoredObjectStatusChange", new_status);
|
|
||||||
return Promise.resolve();
|
|
||||||
} else if ("failure" === new_status.status) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("ready" !== new_status.status) {
|
|
||||||
// we check for new status, unless it is ready
|
|
||||||
checkForReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const new_status = await is_object_ready(props.storedObject);
|
||||||
|
if (props.storedObject.status !== new_status.status) {
|
||||||
|
emit("onStoredObjectStatusChange", new_status);
|
||||||
|
return Promise.resolve();
|
||||||
|
} else if ("failure" === new_status.status) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("ready" !== new_status.status) {
|
||||||
|
// we check for new status, unless it is ready
|
||||||
|
checkForReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkForReady();
|
checkForReady();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,36 +4,36 @@ import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
|||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
|
||||||
const appMessages = {
|
const appMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
yes: "Oui",
|
yes: "Oui",
|
||||||
are_you_sure: "Êtes-vous sûr·e?",
|
are_you_sure: "Êtes-vous sûr·e?",
|
||||||
you_are_going_to_sign: "Vous allez signer le document",
|
you_are_going_to_sign: "Vous allez signer le document",
|
||||||
signature_confirmation: "Confirmation de la signature",
|
signature_confirmation: "Confirmation de la signature",
|
||||||
sign: "Signer",
|
sign: "Signer",
|
||||||
choose_another_signature: "Choisir une autre zone",
|
choose_another_signature: "Choisir une autre zone",
|
||||||
cancel: "Annuler",
|
cancel: "Annuler",
|
||||||
last_sign_zone: "Zone de signature précédente",
|
last_sign_zone: "Zone de signature précédente",
|
||||||
next_sign_zone: "Zone de signature suivante",
|
next_sign_zone: "Zone de signature suivante",
|
||||||
add_sign_zone: "Ajouter une zone de signature",
|
add_sign_zone: "Ajouter une zone de signature",
|
||||||
click_on_document: "Cliquer sur le document",
|
click_on_document: "Cliquer sur le document",
|
||||||
last_zone: "Zone précédente",
|
last_zone: "Zone précédente",
|
||||||
next_zone: "Zone suivante",
|
next_zone: "Zone suivante",
|
||||||
add_zone: "Ajouter une zone",
|
add_zone: "Ajouter une zone",
|
||||||
another_zone: "Autre zone",
|
another_zone: "Autre zone",
|
||||||
electronic_signature_in_progress: "Signature électronique en cours...",
|
electronic_signature_in_progress: "Signature électronique en cours...",
|
||||||
loading: "Chargement...",
|
loading: "Chargement...",
|
||||||
remove_sign_zone: "Enlever la zone",
|
remove_sign_zone: "Enlever la zone",
|
||||||
return: "Retour",
|
return: "Retour",
|
||||||
see_all_pages: "Voir toutes les pages",
|
see_all_pages: "Voir toutes les pages",
|
||||||
all_pages: "Toutes les pages",
|
all_pages: "Toutes les pages",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const i18n = _createI18n(appMessages);
|
const i18n = _createI18n(appMessages);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app></app>`,
|
template: `<app></app>`,
|
||||||
})
|
})
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component("app", App)
|
.component("app", App)
|
||||||
.mount("#document-signature");
|
.mount("#document-signature");
|
||||||
|
|||||||
@@ -1,208 +1,206 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { StoredObject, StoredObjectVersionCreated } from "../../types";
|
import { StoredObject, StoredObjectVersionCreated } from "../../types";
|
||||||
import {
|
import {
|
||||||
encryptFile,
|
encryptFile,
|
||||||
fetchNewStoredObject,
|
fetchNewStoredObject,
|
||||||
uploadVersion,
|
uploadVersion,
|
||||||
} from "../../js/async-upload/uploader";
|
} from "../../js/async-upload/uploader";
|
||||||
import { computed, ref, Ref } from "vue";
|
import { computed, ref, Ref } from "vue";
|
||||||
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
|
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
|
||||||
|
|
||||||
interface DropFileConfig {
|
interface DropFileConfig {
|
||||||
existingDoc?: StoredObject;
|
existingDoc?: StoredObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||||
existingDoc: null,
|
existingDoc: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit =
|
const emit =
|
||||||
defineEmits<
|
defineEmits<
|
||||||
(
|
(
|
||||||
e: "addDocument",
|
e: "addDocument",
|
||||||
{
|
{
|
||||||
stored_object_version: StoredObjectVersionCreated,
|
stored_object_version: StoredObjectVersionCreated,
|
||||||
stored_object: StoredObject,
|
stored_object: StoredObject,
|
||||||
file_name: string,
|
file_name: string,
|
||||||
},
|
},
|
||||||
) => void
|
) => void
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const is_dragging: Ref<boolean> = ref(false);
|
const is_dragging: Ref<boolean> = ref(false);
|
||||||
const uploading: Ref<boolean> = ref(false);
|
const uploading: Ref<boolean> = ref(false);
|
||||||
const display_filename: Ref<string | null> = ref(null);
|
const display_filename: Ref<string | null> = ref(null);
|
||||||
|
|
||||||
const has_existing_doc = computed<boolean>(() => {
|
const has_existing_doc = computed<boolean>(() => {
|
||||||
return props.existingDoc !== undefined && props.existingDoc !== null;
|
return props.existingDoc !== undefined && props.existingDoc !== null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDragOver = (e: Event) => {
|
const onDragOver = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
is_dragging.value = true;
|
is_dragging.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragLeave = (e: Event) => {
|
const onDragLeave = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
is_dragging.value = false;
|
is_dragging.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDrop = (e: DragEvent) => {
|
const onDrop = (e: DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const files = e.dataTransfer?.files;
|
const files = e.dataTransfer?.files;
|
||||||
|
|
||||||
if (null === files || undefined === files) {
|
if (null === files || undefined === files) {
|
||||||
console.error("no files transferred", e.dataTransfer);
|
console.error("no files transferred", e.dataTransfer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
console.error("no files given");
|
console.error("no files given");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFile(files[0]);
|
handleFile(files[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onZoneClick = (e: Event) => {
|
const onZoneClick = (e: Event) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.addEventListener("change", onFileChange);
|
input.addEventListener("change", onFileChange);
|
||||||
|
|
||||||
input.click();
|
input.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFileChange = async (event: Event): Promise<void> => {
|
const onFileChange = async (event: Event): Promise<void> => {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
|
|
||||||
if (input.files && input.files[0]) {
|
if (input.files && input.files[0]) {
|
||||||
console.log("file added", input.files[0]);
|
console.log("file added", input.files[0]);
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
await handleFile(file);
|
await handleFile(file);
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw "No file given";
|
throw "No file given";
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFile = async (file: File): Promise<void> => {
|
const handleFile = async (file: File): Promise<void> => {
|
||||||
uploading.value = true;
|
uploading.value = true;
|
||||||
display_filename.value = file.name;
|
display_filename.value = file.name;
|
||||||
const type = file.type;
|
const type = file.type;
|
||||||
|
|
||||||
// create a stored_object if not exists
|
// create a stored_object if not exists
|
||||||
let stored_object;
|
let stored_object;
|
||||||
if (null === props.existingDoc) {
|
if (null === props.existingDoc) {
|
||||||
stored_object = await fetchNewStoredObject();
|
stored_object = await fetchNewStoredObject();
|
||||||
} else {
|
} else {
|
||||||
stored_object = props.existingDoc;
|
stored_object = props.existingDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await file.arrayBuffer();
|
const buffer = await file.arrayBuffer();
|
||||||
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
|
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
|
||||||
const filename = await uploadVersion(encrypted, stored_object);
|
const filename = await uploadVersion(encrypted, stored_object);
|
||||||
|
|
||||||
const stored_object_version: StoredObjectVersionCreated = {
|
const stored_object_version: StoredObjectVersionCreated = {
|
||||||
filename: filename,
|
filename: filename,
|
||||||
iv: Array.from(iv),
|
iv: Array.from(iv),
|
||||||
keyInfos: jsonWebKey,
|
keyInfos: jsonWebKey,
|
||||||
type: type,
|
type: type,
|
||||||
persisted: false,
|
persisted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileName = file.name;
|
const fileName = file.name;
|
||||||
let file_name = "Nouveau document";
|
let file_name = "Nouveau document";
|
||||||
const file_name_split = fileName.split(".");
|
const file_name_split = fileName.split(".");
|
||||||
if (file_name_split.length > 1) {
|
if (file_name_split.length > 1) {
|
||||||
const extension = file_name_split
|
const extension = file_name_split
|
||||||
? file_name_split[file_name_split.length - 1]
|
? file_name_split[file_name_split.length - 1]
|
||||||
: "";
|
: "";
|
||||||
file_name = fileName.replace(extension, "").slice(0, -1);
|
file_name = fileName.replace(extension, "").slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit("addDocument", {
|
emit("addDocument", {
|
||||||
stored_object,
|
stored_object,
|
||||||
stored_object_version,
|
stored_object_version,
|
||||||
file_name: file_name,
|
file_name: file_name,
|
||||||
});
|
});
|
||||||
uploading.value = false;
|
uploading.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="drop-file">
|
<div class="drop-file">
|
||||||
<div
|
<div
|
||||||
v-if="!uploading"
|
v-if="!uploading"
|
||||||
:class="{ area: true, dragging: is_dragging }"
|
:class="{ area: true, dragging: is_dragging }"
|
||||||
@click="onZoneClick"
|
@click="onZoneClick"
|
||||||
@dragover="onDragOver"
|
@dragover="onDragOver"
|
||||||
@dragleave="onDragLeave"
|
@dragleave="onDragLeave"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
>
|
>
|
||||||
<p v-if="has_existing_doc" class="file-icon">
|
<p v-if="has_existing_doc" class="file-icon">
|
||||||
<file-icon :type="props.existingDoc?.type"></file-icon>
|
<file-icon :type="props.existingDoc?.type"></file-icon>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="display_filename !== null" class="display-filename">
|
<p v-if="display_filename !== null" class="display-filename">
|
||||||
{{ display_filename }}
|
{{ display_filename }}
|
||||||
</p>
|
</p>
|
||||||
<!-- todo i18n -->
|
<!-- todo i18n -->
|
||||||
<p v-if="has_existing_doc">
|
<p v-if="has_existing_doc">
|
||||||
Déposez un document ou cliquez ici pour remplacer le document
|
Déposez un document ou cliquez ici pour remplacer le document existant
|
||||||
existant
|
</p>
|
||||||
</p>
|
<p v-else>
|
||||||
<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
|
</p>
|
||||||
fichier
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-else class="waiting">
|
|
||||||
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="waiting">
|
||||||
|
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.drop-file {
|
.drop-file {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-filename {
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .area,
|
||||||
|
& > .waiting {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 10rem;
|
||||||
|
|
||||||
.file-icon {
|
display: flex;
|
||||||
font-size: xx-large;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
// require for display in DropFileModal
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.display-filename {
|
& > .area {
|
||||||
font-variant: small-caps;
|
border: 4px dashed #ccc;
|
||||||
font-weight: 200;
|
|
||||||
}
|
&.dragging {
|
||||||
|
border: 4px dashed blue;
|
||||||
& > .area,
|
|
||||||
& > .waiting {
|
|
||||||
width: 100%;
|
|
||||||
height: 10rem;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
p {
|
|
||||||
// require for display in DropFileModal
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .area {
|
|
||||||
border: 4px dashed #ccc;
|
|
||||||
|
|
||||||
&.dragging {
|
|
||||||
border: 4px dashed blue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,97 +5,97 @@ import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue";
|
|||||||
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
||||||
|
|
||||||
interface DropFileConfig {
|
interface DropFileConfig {
|
||||||
allowRemove: boolean;
|
allowRemove: boolean;
|
||||||
existingDoc?: StoredObject;
|
existingDoc?: StoredObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||||
allowRemove: false,
|
allowRemove: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(
|
(
|
||||||
e: "addDocument",
|
e: "addDocument",
|
||||||
{
|
{
|
||||||
stored_object: StoredObject,
|
stored_object: StoredObject,
|
||||||
stored_object_version: StoredObjectVersion,
|
stored_object_version: StoredObjectVersion,
|
||||||
file_name: string,
|
file_name: string,
|
||||||
},
|
},
|
||||||
): void;
|
): void;
|
||||||
(e: "removeDocument"): void;
|
(e: "removeDocument"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const has_existing_doc = computed<boolean>(() => {
|
const has_existing_doc = computed<boolean>(() => {
|
||||||
return props.existingDoc !== undefined && props.existingDoc !== null;
|
return props.existingDoc !== undefined && props.existingDoc !== null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const dav_link_expiration = computed<number | undefined>(() => {
|
const dav_link_expiration = computed<number | undefined>(() => {
|
||||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (props.existingDoc.status !== "ready") {
|
if (props.existingDoc.status !== "ready") {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.existingDoc._links?.dav_link?.expiration;
|
return props.existingDoc._links?.dav_link?.expiration;
|
||||||
});
|
});
|
||||||
|
|
||||||
const dav_link_href = computed<string | undefined>(() => {
|
const dav_link_href = computed<string | undefined>(() => {
|
||||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (props.existingDoc.status !== "ready") {
|
if (props.existingDoc.status !== "ready") {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.existingDoc._links?.dav_link?.href;
|
return props.existingDoc._links?.dav_link?.href;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onAddDocument = ({
|
const onAddDocument = ({
|
||||||
stored_object,
|
stored_object,
|
||||||
stored_object_version,
|
stored_object_version,
|
||||||
file_name,
|
file_name,
|
||||||
}: {
|
}: {
|
||||||
stored_object: StoredObject;
|
stored_object: StoredObject;
|
||||||
stored_object_version: StoredObjectVersion;
|
stored_object_version: StoredObjectVersion;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
}): void => {
|
}): void => {
|
||||||
emit("addDocument", { stored_object, stored_object_version, file_name });
|
emit("addDocument", { stored_object, stored_object_version, file_name });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoveDocument = (e: Event): void => {
|
const onRemoveDocument = (e: Event): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
emit("removeDocument");
|
emit("removeDocument");
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<drop-file
|
<drop-file
|
||||||
:existingDoc="props.existingDoc"
|
:existingDoc="props.existingDoc"
|
||||||
@addDocument="onAddDocument"
|
@addDocument="onAddDocument"
|
||||||
></drop-file>
|
></drop-file>
|
||||||
|
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li v-if="has_existing_doc">
|
<li v-if="has_existing_doc">
|
||||||
<document-action-buttons-group
|
<document-action-buttons-group
|
||||||
:stored-object="props.existingDoc"
|
:stored-object="props.existingDoc"
|
||||||
:can-edit="props.existingDoc?.status === 'ready'"
|
:can-edit="props.existingDoc?.status === 'ready'"
|
||||||
:can-download="true"
|
:can-download="true"
|
||||||
:dav-link="dav_link_href"
|
:dav-link="dav_link_href"
|
||||||
:dav-link-expiration="dav_link_expiration"
|
:dav-link-expiration="dav_link_expiration"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
v-if="allowRemove"
|
v-if="allowRemove"
|
||||||
class="btn btn-delete"
|
class="btn btn-delete"
|
||||||
@click="onRemoveDocument($event)"
|
@click="onRemoveDocument($event)"
|
||||||
></button>
|
></button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
interface FileIconConfig {
|
interface FileIconConfig {
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<FileIconConfig>();
|
const props = defineProps<FileIconConfig>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
|
<i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-word-o"
|
class="fa fa-file-word-o"
|
||||||
v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
|
v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-word-o"
|
class="fa fa-file-word-o"
|
||||||
v-else-if="
|
v-else-if="
|
||||||
props.type ===
|
props.type ===
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||||
"
|
"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-word-o"
|
class="fa fa-file-word-o"
|
||||||
v-else-if="props.type === 'application/msword'"
|
v-else-if="props.type === 'application/msword'"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-excel-o"
|
class="fa fa-file-excel-o"
|
||||||
v-else-if="
|
v-else-if="
|
||||||
props.type ===
|
props.type ===
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
"
|
"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-excel-o"
|
class="fa fa-file-excel-o"
|
||||||
v-else-if="props.type === 'application/vnd.ms-excel'"
|
v-else-if="props.type === 'application/vnd.ms-excel'"
|
||||||
></i>
|
></i>
|
||||||
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i>
|
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i>
|
||||||
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i>
|
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-archive-o"
|
class="fa fa-file-archive-o"
|
||||||
v-else-if="props.type === 'application/x-zip-compressed'"
|
v-else-if="props.type === 'application/x-zip-compressed'"
|
||||||
></i>
|
></i>
|
||||||
<i class="fa fa-file-code-o" v-else></i>
|
<i class="fa fa-file-code-o" v-else></i>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<a :class="props.classes" @click="download_and_open($event)" ref="btn">
|
<a :class="props.classes" @click="download_and_open($event)" ref="btn">
|
||||||
<i class="fa fa-file-pdf-o"></i>
|
<i class="fa fa-file-pdf-o"></i>
|
||||||
Télécharger en pdf
|
Télécharger en pdf
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
build_convert_link,
|
build_convert_link,
|
||||||
download_and_decrypt_doc,
|
download_and_decrypt_doc,
|
||||||
download_doc,
|
download_doc,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import mime from "mime";
|
import mime from "mime";
|
||||||
import { reactive, ref } from "vue";
|
import { reactive, ref } from "vue";
|
||||||
import { StoredObject } from "../../types";
|
import { StoredObject } from "../../types";
|
||||||
|
|
||||||
interface ConvertButtonConfig {
|
interface ConvertButtonConfig {
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
classes: Record<string, boolean>;
|
classes: Record<string, boolean>;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DownloadButtonState {
|
interface DownloadButtonState {
|
||||||
content: null | string;
|
content: null | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<ConvertButtonConfig>();
|
const props = defineProps<ConvertButtonConfig>();
|
||||||
@@ -30,36 +30,34 @@ const state: DownloadButtonState = reactive({ content: null });
|
|||||||
const btn = ref<HTMLAnchorElement | null>(null);
|
const btn = ref<HTMLAnchorElement | null>(null);
|
||||||
|
|
||||||
async function download_and_open(event: Event): Promise<void> {
|
async function download_and_open(event: Event): Promise<void> {
|
||||||
const button = event.target as HTMLAnchorElement;
|
const button = event.target as HTMLAnchorElement;
|
||||||
|
|
||||||
if (null === state.content) {
|
if (null === state.content) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const raw = await download_doc(
|
const raw = await download_doc(build_convert_link(props.storedObject.uuid));
|
||||||
build_convert_link(props.storedObject.uuid),
|
state.content = window.URL.createObjectURL(raw);
|
||||||
);
|
|
||||||
state.content = window.URL.createObjectURL(raw);
|
|
||||||
|
|
||||||
button.href = window.URL.createObjectURL(raw);
|
button.href = window.URL.createObjectURL(raw);
|
||||||
button.type = "application/pdf";
|
button.type = "application/pdf";
|
||||||
|
|
||||||
button.download = props.filename + ".pdf" || "document.pdf";
|
button.download = props.filename + ".pdf" || "document.pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
button.click();
|
button.click();
|
||||||
const reset_pending = setTimeout(reset_state, 45000);
|
const reset_pending = setTimeout(reset_state, 45000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset_state(): void {
|
function reset_state(): void {
|
||||||
state.content = null;
|
state.content = null;
|
||||||
btn.value?.removeAttribute("download");
|
btn.value?.removeAttribute("download");
|
||||||
btn.value?.removeAttribute("href");
|
btn.value?.removeAttribute("href");
|
||||||
btn.value?.removeAttribute("type");
|
btn.value?.removeAttribute("type");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
i.fa::before {
|
i.fa::before {
|
||||||
color: var(--bs-dropdown-link-hover-color);
|
color: var(--bs-dropdown-link-hover-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
|||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
|
|
||||||
export interface DesktopEditButtonConfig {
|
export interface DesktopEditButtonConfig {
|
||||||
editLink: null;
|
editLink: null;
|
||||||
classes: Record<string, boolean>;
|
classes: Record<string, boolean>;
|
||||||
expirationLink: number | Date;
|
expirationLink: number | Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DesktopEditButtonState {
|
interface DesktopEditButtonState {
|
||||||
modalOpened: boolean;
|
modalOpened: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: DesktopEditButtonState = reactive({ modalOpened: false });
|
const state: DesktopEditButtonState = reactive({ modalOpened: false });
|
||||||
@@ -17,80 +17,76 @@ const state: DesktopEditButtonState = reactive({ modalOpened: false });
|
|||||||
const props = defineProps<DesktopEditButtonConfig>();
|
const props = defineProps<DesktopEditButtonConfig>();
|
||||||
|
|
||||||
const buildCommand = computed<string>(
|
const buildCommand = computed<string>(
|
||||||
() => "vnd.libreoffice.command:ofe|u|" + props.editLink,
|
() => "vnd.libreoffice.command:ofe|u|" + props.editLink,
|
||||||
);
|
);
|
||||||
|
|
||||||
const editionUntilFormatted = computed<string>(() => {
|
const editionUntilFormatted = computed<string>(() => {
|
||||||
let d;
|
let d;
|
||||||
|
|
||||||
if (props.expirationLink instanceof Date) {
|
if (props.expirationLink instanceof Date) {
|
||||||
d = props.expirationLink;
|
d = props.expirationLink;
|
||||||
} else {
|
} else {
|
||||||
d = new Date(props.expirationLink * 1000);
|
d = new Date(props.expirationLink * 1000);
|
||||||
}
|
}
|
||||||
console.log(props.expirationLink);
|
console.log(props.expirationLink);
|
||||||
|
|
||||||
return new Intl.DateTimeFormat(undefined, {
|
return new Intl.DateTimeFormat(undefined, {
|
||||||
dateStyle: "long",
|
dateStyle: "long",
|
||||||
timeStyle: "medium",
|
timeStyle: "medium",
|
||||||
}).format(d);
|
}).format(d);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<modal v-if="state.modalOpened" @close="state.modalOpened = false">
|
<modal v-if="state.modalOpened" @close="state.modalOpened = false">
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<div class="desktop-edit">
|
<div class="desktop-edit">
|
||||||
<p class="center">
|
<p class="center">Veuillez enregistrer vos modifications avant le</p>
|
||||||
Veuillez enregistrer vos modifications avant le
|
<p>
|
||||||
</p>
|
<strong>{{ editionUntilFormatted }}</strong>
|
||||||
<p>
|
</p>
|
||||||
<strong>{{ editionUntilFormatted }}</strong>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-primary" :href="buildCommand"
|
<a class="btn btn-primary" :href="buildCommand"
|
||||||
>Ouvrir le document pour édition</a
|
>Ouvrir le document pour édition</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<small
|
<small
|
||||||
>Le document peut être édité uniquement en utilisant
|
>Le document peut être édité uniquement en utilisant Libre
|
||||||
Libre Office.</small
|
Office.</small
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<small
|
<small
|
||||||
>En cas d'échec lors de l'enregistrement, sauver le
|
>En cas d'échec lors de l'enregistrement, sauver le document sur
|
||||||
document sur le poste de travail avant de le déposer
|
le poste de travail avant de le déposer à nouveau ici.</small
|
||||||
à nouveau ici.</small
|
>
|
||||||
>
|
</p>
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<small
|
<small
|
||||||
>Vous pouvez naviguez sur d'autres pages pendant
|
>Vous pouvez naviguez sur d'autres pages pendant l'édition.</small
|
||||||
l'édition.</small
|
>
|
||||||
>
|
</p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</modal>
|
||||||
</modal>
|
</teleport>
|
||||||
</teleport>
|
<a :class="props.classes" @click="state.modalOpened = true">
|
||||||
<a :class="props.classes" @click="state.modalOpened = true">
|
<i class="fa fa-desktop"></i>
|
||||||
<i class="fa fa-desktop"></i>
|
Éditer sur le bureau
|
||||||
Éditer sur le bureau
|
</a>
|
||||||
</a>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.desktop-edit {
|
.desktop-edit {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
i.fa::before {
|
i.fa::before {
|
||||||
color: var(--bs-dropdown-link-hover-color);
|
color: var(--bs-dropdown-link-hover-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
v-if="!state.is_ready"
|
v-if="!state.is_ready"
|
||||||
:class="props.classes"
|
:class="props.classes"
|
||||||
@click="download_and_open()"
|
@click="download_and_open()"
|
||||||
title="Télécharger"
|
title="Télécharger"
|
||||||
>
|
>
|
||||||
<i class="fa fa-download"></i>
|
<i class="fa fa-download"></i>
|
||||||
<template v-if="displayActionStringInButton">Télécharger</template>
|
<template v-if="displayActionStringInButton">Télécharger</template>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-else
|
v-else
|
||||||
:class="props.classes"
|
:class="props.classes"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
:type="props.atVersion.type"
|
:type="props.atVersion.type"
|
||||||
:download="buildDocumentName()"
|
:download="buildDocumentName()"
|
||||||
:href="state.href_url"
|
:href="state.href_url"
|
||||||
ref="open_button"
|
ref="open_button"
|
||||||
title="Ouvrir"
|
title="Ouvrir"
|
||||||
>
|
>
|
||||||
<i class="fa fa-external-link"></i>
|
<i class="fa fa-external-link"></i>
|
||||||
<template v-if="displayActionStringInButton">Ouvrir</template>
|
<template v-if="displayActionStringInButton">Ouvrir</template>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -30,112 +30,109 @@ import mime from "mime";
|
|||||||
import { StoredObject, StoredObjectVersion } from "../../types";
|
import { StoredObject, StoredObjectVersion } from "../../types";
|
||||||
|
|
||||||
interface DownloadButtonConfig {
|
interface DownloadButtonConfig {
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
atVersion: StoredObjectVersion;
|
atVersion: StoredObjectVersion;
|
||||||
classes: Record<string, boolean>;
|
classes: Record<string, boolean>;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
/**
|
/**
|
||||||
* if true, display the action string into the button. If false, displays only
|
* if true, display the action string into the button. If false, displays only
|
||||||
* the icon
|
* the icon
|
||||||
*/
|
*/
|
||||||
displayActionStringInButton?: boolean;
|
displayActionStringInButton?: boolean;
|
||||||
/**
|
/**
|
||||||
* if true, will download directly the file on load
|
* if true, will download directly the file on load
|
||||||
*/
|
*/
|
||||||
directDownload?: boolean;
|
directDownload?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DownloadButtonState {
|
interface DownloadButtonState {
|
||||||
is_ready: boolean;
|
is_ready: boolean;
|
||||||
is_running: boolean;
|
is_running: boolean;
|
||||||
href_url: string;
|
href_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DownloadButtonConfig>(), {
|
const props = withDefaults(defineProps<DownloadButtonConfig>(), {
|
||||||
displayActionStringInButton: true,
|
displayActionStringInButton: true,
|
||||||
directDownload: false,
|
directDownload: false,
|
||||||
});
|
});
|
||||||
const state: DownloadButtonState = reactive({
|
const state: DownloadButtonState = reactive({
|
||||||
is_ready: false,
|
is_ready: false,
|
||||||
is_running: false,
|
is_running: false,
|
||||||
href_url: "#",
|
href_url: "#",
|
||||||
});
|
});
|
||||||
|
|
||||||
const open_button = ref<HTMLAnchorElement | null>(null);
|
const open_button = ref<HTMLAnchorElement | null>(null);
|
||||||
|
|
||||||
function buildDocumentName(): string {
|
function buildDocumentName(): string {
|
||||||
let document_name = props.filename ?? props.storedObject.title;
|
let document_name = props.filename ?? props.storedObject.title;
|
||||||
|
|
||||||
if ("" === document_name || null === document_name) {
|
if ("" === document_name || null === document_name) {
|
||||||
document_name = "document";
|
document_name = "document";
|
||||||
}
|
}
|
||||||
|
|
||||||
const ext = mime.getExtension(props.atVersion.type);
|
const ext = mime.getExtension(props.atVersion.type);
|
||||||
|
|
||||||
if (null !== ext) {
|
if (null !== ext) {
|
||||||
return document_name + "." + ext;
|
return document_name + "." + ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
return document_name;
|
return document_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function download_and_open(): Promise<void> {
|
async function download_and_open(): Promise<void> {
|
||||||
if (state.is_running) {
|
if (state.is_running) {
|
||||||
console.log("state is running, aborting");
|
console.log("state is running, aborting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.is_running = true;
|
state.is_running = true;
|
||||||
|
|
||||||
if (state.is_ready) {
|
if (state.is_ready) {
|
||||||
console.log("state is ready. This should not happens");
|
console.log("state is ready. This should not happens");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw;
|
let raw;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
raw = await download_and_decrypt_doc(
|
raw = await download_and_decrypt_doc(props.storedObject, props.atVersion);
|
||||||
props.storedObject,
|
} catch (e) {
|
||||||
props.atVersion,
|
console.error("error while downloading and decrypting document");
|
||||||
);
|
console.error(e);
|
||||||
} catch (e) {
|
throw e;
|
||||||
console.error("error while downloading and decrypting document");
|
}
|
||||||
console.error(e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.href_url = window.URL.createObjectURL(raw);
|
state.href_url = window.URL.createObjectURL(raw);
|
||||||
state.is_running = false;
|
state.is_running = false;
|
||||||
state.is_ready = true;
|
state.is_ready = true;
|
||||||
|
|
||||||
if (!props.directDownload) {
|
if (!props.directDownload) {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
open_button.value?.click();
|
open_button.value?.click();
|
||||||
|
|
||||||
console.log("open button should have been clicked");
|
console.log("open button should have been clicked");
|
||||||
setTimeout(reset_state, 45000);
|
setTimeout(reset_state, 45000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset_state(): void {
|
function reset_state(): void {
|
||||||
state.href_url = "#";
|
state.href_url = "#";
|
||||||
state.is_ready = false;
|
state.is_ready = false;
|
||||||
state.is_running = false;
|
state.is_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.directDownload) {
|
if (props.directDownload) {
|
||||||
download_and_open();
|
download_and_open();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
i.fa::before {
|
i.fa::before {
|
||||||
color: var(--bs-dropdown-link-hover-color);
|
color: var(--bs-dropdown-link-hover-color);
|
||||||
}
|
}
|
||||||
i.fa {
|
i.fa {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HistoryButtonModal from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue";
|
import HistoryButtonModal from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue";
|
||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectVersionWithPointInTime,
|
StoredObjectVersionWithPointInTime,
|
||||||
} from "./../../types";
|
} from "./../../types";
|
||||||
import { computed, reactive, ref, useTemplateRef } from "vue";
|
import { computed, reactive, ref, useTemplateRef } from "vue";
|
||||||
import { get_versions } from "./HistoryButton/api";
|
import { get_versions } from "./HistoryButton/api";
|
||||||
|
|
||||||
interface HistoryButtonConfig {
|
interface HistoryButtonConfig {
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HistoryButtonState {
|
interface HistoryButtonState {
|
||||||
versions: StoredObjectVersionWithPointInTime[];
|
versions: StoredObjectVersionWithPointInTime[];
|
||||||
loaded: boolean;
|
loaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<HistoryButtonConfig>();
|
const props = defineProps<HistoryButtonConfig>();
|
||||||
@@ -22,47 +22,47 @@ const state = reactive<HistoryButtonState>({ versions: [], loaded: false });
|
|||||||
const modal = useTemplateRef<typeof HistoryButtonModal>("modal");
|
const modal = useTemplateRef<typeof HistoryButtonModal>("modal");
|
||||||
|
|
||||||
const download_version_and_open_modal = async function (): Promise<void> {
|
const download_version_and_open_modal = async function (): Promise<void> {
|
||||||
if (null !== modal.value) {
|
if (null !== modal.value) {
|
||||||
modal.value.open();
|
modal.value.open();
|
||||||
} else {
|
} else {
|
||||||
console.log("modal is null");
|
console.log("modal is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.loaded) {
|
if (!state.loaded) {
|
||||||
const versions = await get_versions(props.storedObject);
|
const versions = await get_versions(props.storedObject);
|
||||||
|
|
||||||
for (const version of versions) {
|
for (const version of versions) {
|
||||||
state.versions.push(version);
|
state.versions.push(version);
|
||||||
}
|
|
||||||
state.loaded = true;
|
|
||||||
}
|
}
|
||||||
|
state.loaded = true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRestoreVersion = ({
|
const onRestoreVersion = ({
|
||||||
newVersion,
|
newVersion,
|
||||||
}: {
|
}: {
|
||||||
newVersion: StoredObjectVersionWithPointInTime;
|
newVersion: StoredObjectVersionWithPointInTime;
|
||||||
}) => {
|
}) => {
|
||||||
state.versions.unshift(newVersion);
|
state.versions.unshift(newVersion);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a @click="download_version_and_open_modal" class="dropdown-item">
|
<a @click="download_version_and_open_modal" class="dropdown-item">
|
||||||
<history-button-modal
|
<history-button-modal
|
||||||
ref="modal"
|
ref="modal"
|
||||||
:versions="state.versions"
|
:versions="state.versions"
|
||||||
:stored-object="storedObject"
|
:stored-object="storedObject"
|
||||||
:can-edit="canEdit"
|
:can-edit="canEdit"
|
||||||
@restore-version="onRestoreVersion"
|
@restore-version="onRestoreVersion"
|
||||||
></history-button-modal>
|
></history-button-modal>
|
||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
Historique
|
Historique
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
i.fa::before {
|
i.fa::before {
|
||||||
color: var(--bs-dropdown-link-hover-color);
|
color: var(--bs-dropdown-link-hover-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectVersionWithPointInTime,
|
StoredObjectVersionWithPointInTime,
|
||||||
} from "./../../../types";
|
} from "./../../../types";
|
||||||
import HistoryButtonListItem from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue";
|
import HistoryButtonListItem from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
|
|
||||||
interface HistoryButtonListConfig {
|
interface HistoryButtonListConfig {
|
||||||
versions: StoredObjectVersionWithPointInTime[];
|
versions: StoredObjectVersionWithPointInTime[];
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
interface HistoryButtonListState {
|
interface HistoryButtonListState {
|
||||||
/**
|
/**
|
||||||
* Contains the number of the newly created version when a version is restored.
|
* Contains the number of the newly created version when a version is restored.
|
||||||
*/
|
*/
|
||||||
restored: number;
|
restored: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<HistoryButtonListConfig>();
|
const props = defineProps<HistoryButtonListConfig>();
|
||||||
@@ -28,11 +28,11 @@ const props = defineProps<HistoryButtonListConfig>();
|
|||||||
const state = reactive<HistoryButtonListState>({ restored: -1 });
|
const state = reactive<HistoryButtonListState>({ restored: -1 });
|
||||||
|
|
||||||
const higher_version = computed<number>(() =>
|
const higher_version = computed<number>(() =>
|
||||||
props.versions.reduce(
|
props.versions.reduce(
|
||||||
(accumulator: number, version: StoredObjectVersionWithPointInTime) =>
|
(accumulator: number, version: StoredObjectVersionWithPointInTime) =>
|
||||||
Math.max(accumulator, version.version),
|
Math.max(accumulator, version.version),
|
||||||
-1,
|
-1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,32 +41,32 @@ const higher_version = computed<number>(() =>
|
|||||||
* internally, keep track of the newly restored version
|
* internally, keep track of the newly restored version
|
||||||
*/
|
*/
|
||||||
const onRestored = ({
|
const onRestored = ({
|
||||||
newVersion,
|
newVersion,
|
||||||
}: {
|
}: {
|
||||||
newVersion: StoredObjectVersionWithPointInTime;
|
newVersion: StoredObjectVersionWithPointInTime;
|
||||||
}) => {
|
}) => {
|
||||||
state.restored = newVersion.version;
|
state.restored = newVersion.version;
|
||||||
emit("restoreVersion", { newVersion });
|
emit("restoreVersion", { newVersion });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="props.versions.length > 0">
|
<template v-if="props.versions.length > 0">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<template v-for="v in props.versions" :key="v.id">
|
<template v-for="v in props.versions" :key="v.id">
|
||||||
<history-button-list-item
|
<history-button-list-item
|
||||||
:version="v"
|
:version="v"
|
||||||
:can-edit="canEdit"
|
:can-edit="canEdit"
|
||||||
:is-current="higher_version === v.version"
|
:is-current="higher_version === v.version"
|
||||||
:stored-object="storedObject"
|
:stored-object="storedObject"
|
||||||
@restore-version="onRestored"
|
@restore-version="onRestored"
|
||||||
></history-button-list-item>
|
></history-button-list-item>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<p>Chargement des versions</p>
|
<p>Chargement des versions</p>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
@@ -3,54 +3,54 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
|||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue";
|
import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue";
|
||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectVersionWithPointInTime,
|
StoredObjectVersionWithPointInTime,
|
||||||
} from "./../../../types";
|
} from "./../../../types";
|
||||||
|
|
||||||
interface HistoryButtonListConfig {
|
interface HistoryButtonListConfig {
|
||||||
versions: StoredObjectVersionWithPointInTime[];
|
versions: StoredObjectVersionWithPointInTime[];
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
interface HistoryButtonModalState {
|
interface HistoryButtonModalState {
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<HistoryButtonListConfig>();
|
const props = defineProps<HistoryButtonListConfig>();
|
||||||
const state = reactive<HistoryButtonModalState>({ opened: false });
|
const state = reactive<HistoryButtonModalState>({ opened: false });
|
||||||
|
|
||||||
const open = () => {
|
const open = () => {
|
||||||
state.opened = true;
|
state.opened = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRestoreVersion = (payload: {
|
const onRestoreVersion = (payload: {
|
||||||
newVersion: StoredObjectVersionWithPointInTime;
|
newVersion: StoredObjectVersionWithPointInTime;
|
||||||
}) => emit("restoreVersion", payload);
|
}) => emit("restoreVersion", payload);
|
||||||
|
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<modal v-if="state.opened" @close="state.opened = false">
|
<modal v-if="state.opened" @close="state.opened = false">
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h3>Historique des versions du document</h3>
|
<h3>Historique des versions du document</h3>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<p>Les versions sont conservées pendant 90 jours.</p>
|
<p>Les versions sont conservées pendant 90 jours.</p>
|
||||||
<history-button-list
|
<history-button-list
|
||||||
:versions="props.versions"
|
:versions="props.versions"
|
||||||
:can-edit="canEdit"
|
:can-edit="canEdit"
|
||||||
:stored-object="storedObject"
|
:stored-object="storedObject"
|
||||||
@restore-version="onRestoreVersion"
|
@restore-version="onRestoreVersion"
|
||||||
></history-button-list>
|
></history-button-list>
|
||||||
</template>
|
</template>
|
||||||
</modal>
|
</modal>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
StoredObjectVersionPersisted,
|
StoredObjectVersionPersisted,
|
||||||
StoredObjectVersionWithPointInTime,
|
StoredObjectVersionWithPointInTime,
|
||||||
} from "../../../types";
|
} from "../../../types";
|
||||||
import { useToast } from "vue-toast-notification";
|
import { useToast } from "vue-toast-notification";
|
||||||
import { restore_version } from "./api";
|
import { restore_version } from "./api";
|
||||||
|
|
||||||
interface RestoreVersionButtonProps {
|
interface RestoreVersionButtonProps {
|
||||||
storedObjectVersion: StoredObjectVersionPersisted;
|
storedObjectVersion: StoredObjectVersionPersisted;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<RestoreVersionButtonProps>();
|
const props = defineProps<RestoreVersionButtonProps>();
|
||||||
@@ -19,21 +19,21 @@ const props = defineProps<RestoreVersionButtonProps>();
|
|||||||
const $toast = useToast();
|
const $toast = useToast();
|
||||||
|
|
||||||
const restore_version_fn = async () => {
|
const restore_version_fn = async () => {
|
||||||
const newVersion = await restore_version(props.storedObjectVersion);
|
const newVersion = await restore_version(props.storedObjectVersion);
|
||||||
|
|
||||||
$toast.success("Version restaurée");
|
$toast.success("Version restaurée");
|
||||||
emit("restoreVersion", { newVersion });
|
emit("restoreVersion", { newVersion });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-action"
|
class="btn btn-outline-action"
|
||||||
@click="restore_version_fn"
|
@click="restore_version_fn"
|
||||||
title="Restaurer"
|
title="Restaurer"
|
||||||
>
|
>
|
||||||
<i class="fa fa-rotate-left"></i> Restaurer
|
<i class="fa fa-rotate-left"></i> Restaurer
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectVersionPersisted,
|
StoredObjectVersionPersisted,
|
||||||
StoredObjectVersionWithPointInTime,
|
StoredObjectVersionWithPointInTime,
|
||||||
} from "../../../types";
|
} from "../../../types";
|
||||||
import {
|
import {
|
||||||
fetchResults,
|
fetchResults,
|
||||||
makeFetch,
|
makeFetch,
|
||||||
} from "../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
} from "../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
|
|
||||||
export const get_versions = async (
|
export const get_versions = async (
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
): Promise<StoredObjectVersionWithPointInTime[]> => {
|
): Promise<StoredObjectVersionWithPointInTime[]> => {
|
||||||
const versions = await fetchResults<StoredObjectVersionWithPointInTime>(
|
const versions = await fetchResults<StoredObjectVersionWithPointInTime>(
|
||||||
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`,
|
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return versions.sort(
|
return versions.sort(
|
||||||
(
|
(
|
||||||
a: StoredObjectVersionWithPointInTime,
|
a: StoredObjectVersionWithPointInTime,
|
||||||
b: StoredObjectVersionWithPointInTime,
|
b: StoredObjectVersionWithPointInTime,
|
||||||
) => b.version - a.version,
|
) => b.version - a.version,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const restore_version = async (
|
export const restore_version = async (
|
||||||
version: StoredObjectVersionPersisted,
|
version: StoredObjectVersionPersisted,
|
||||||
): Promise<StoredObjectVersionWithPointInTime> => {
|
): Promise<StoredObjectVersionWithPointInTime> => {
|
||||||
return await makeFetch<null, StoredObjectVersionWithPointInTime>(
|
return await makeFetch<null, StoredObjectVersionWithPointInTime>(
|
||||||
"POST",
|
"POST",
|
||||||
`/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`,
|
`/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
:class="Object.assign(props.classes, { btn: true })"
|
:class="Object.assign(props.classes, { btn: true })"
|
||||||
@click="beforeLeave($event)"
|
@click="beforeLeave($event)"
|
||||||
:href="
|
:href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)"
|
||||||
build_wopi_editor_link(props.storedObject.uuid, props.returnPath)
|
>
|
||||||
"
|
<i class="fa fa-paragraph"></i>
|
||||||
>
|
Editer en ligne
|
||||||
<i class="fa fa-paragraph"></i>
|
</a>
|
||||||
Editer en ligne
|
|
||||||
</a>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import WopiEditButton from "./WopiEditButton.vue";
|
import WopiEditButton from "./WopiEditButton.vue";
|
||||||
import { build_wopi_editor_link } from "./helpers";
|
import { build_wopi_editor_link } from "./helpers";
|
||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||||
} from "../../types";
|
} from "../../types";
|
||||||
|
|
||||||
interface WopiEditButtonConfig {
|
interface WopiEditButtonConfig {
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
returnPath?: string;
|
returnPath?: string;
|
||||||
classes: Record<string, boolean>;
|
classes: Record<string, boolean>;
|
||||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<WopiEditButtonConfig>();
|
const props = defineProps<WopiEditButtonConfig>();
|
||||||
@@ -31,24 +29,24 @@ const props = defineProps<WopiEditButtonConfig>();
|
|||||||
let executed = false;
|
let executed = false;
|
||||||
|
|
||||||
async function beforeLeave(event: Event): Promise<true> {
|
async function beforeLeave(event: Event): Promise<true> {
|
||||||
if (props.executeBeforeLeave === undefined || executed === true) {
|
if (props.executeBeforeLeave === undefined || executed === true) {
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
await props.executeBeforeLeave();
|
|
||||||
executed = true;
|
|
||||||
|
|
||||||
const link = event.target as HTMLAnchorElement;
|
|
||||||
link.click();
|
|
||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
await props.executeBeforeLeave();
|
||||||
|
executed = true;
|
||||||
|
|
||||||
|
const link = event.target as HTMLAnchorElement;
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
i.fa::before {
|
i.fa::before {
|
||||||
color: var(--bs-dropdown-link-hover-color);
|
color: var(--bs-dropdown-link-hover-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user