mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-22 13:12:49 +00:00
Compare commits
224 Commits
v4.4.2
...
ticket/scr
| Author | SHA1 | Date | |
|---|---|---|---|
|
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-20250904-181032.yaml
Normal file
6
.changes/unreleased/Feature-20250904-181032.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Feature
|
||||||
|
body: Add a command to generate a list of permissions
|
||||||
|
time: 2025-09-04T18:10:32.334524026+02:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
||||||
|
SchemaChange: No schema change
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
## v4.3.0 - 2025-09-08
|
|
||||||
### Feature
|
|
||||||
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
|
|
||||||
* Add a command to generate a list of permissions
|
|
||||||
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date
|
|
||||||
|
|
||||||
**Schema Change**: Add columns or tables
|
|
||||||
### Fixed
|
|
||||||
* fix date formatting in calendar range display
|
|
||||||
* Change route URL to avoid clash with person duplicate controller method
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
## v4.4.0 - 2025-09-11
|
|
||||||
### Feature
|
|
||||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
|
||||||
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation
|
|
||||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works
|
|
||||||
### Fixed
|
|
||||||
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates
|
|
||||||
* Fix saving notification preferences in user's profile
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
## v4.4.1 - 2025-09-11
|
|
||||||
### Fixed
|
|
||||||
* fix translations in duplicate evaluation document modal and realign close modal button
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
## v4.4.2 - 2025-09-12
|
|
||||||
### Fixed
|
|
||||||
* Fix document generation and workflow generation do not work on accompanying period work documents
|
|
||||||
@@ -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,15 +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 tests for a specific bundle
|
|
||||||
vendor/bin/phpunit --testsuite NameBundle
|
|
||||||
|
|
||||||
# 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -6,34 +6,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
|||||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||||
|
|
||||||
|
|
||||||
## v4.4.2 - 2025-09-12
|
|
||||||
### Fixed
|
|
||||||
* Fix document generation and workflow generation do not work on accompanying period work documents
|
|
||||||
|
|
||||||
## v4.4.1 - 2025-09-11
|
|
||||||
### Fixed
|
|
||||||
* fix translations in duplicate evaluation document modal and realign close modal button
|
|
||||||
|
|
||||||
## v4.4.0 - 2025-09-11
|
|
||||||
### Feature
|
|
||||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
|
||||||
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation
|
|
||||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works
|
|
||||||
### Fixed
|
|
||||||
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates
|
|
||||||
* Fix saving notification preferences in user's profile
|
|
||||||
|
|
||||||
## v4.3.0 - 2025-09-08
|
|
||||||
### Feature
|
|
||||||
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
|
|
||||||
* Add a command to generate a list of permissions
|
|
||||||
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date
|
|
||||||
|
|
||||||
**Schema Change**: Add columns or tables
|
|
||||||
### Fixed
|
|
||||||
* fix date formatting in calendar range display
|
|
||||||
* Change route URL to avoid clash with person duplicate controller method
|
|
||||||
|
|
||||||
## v4.2.1 - 2025-09-03
|
## v4.2.1 - 2025-09-03
|
||||||
### Fixed
|
### Fixed
|
||||||
* Fix exports to work with DirectExportInterface
|
* Fix exports to work with DirectExportInterface
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,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],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
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.
|
|
||||||
|
|||||||
@@ -79,12 +79,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,103 +1,98 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#social-issues-acc">
|
<teleport to="#social-issues-acc">
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialIssuesClassList">{{
|
<label :class="socialIssuesClassList">{{
|
||||||
trans(ACTIVITY_SOCIAL_ISSUES)
|
trans(ACTIVITY_SOCIAL_ISSUES)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<check-social-issue
|
<check-social-issue
|
||||||
v-for="issue in socialIssuesList"
|
v-for="issue in socialIssuesList"
|
||||||
:key="issue.id"
|
:key="issue.id"
|
||||||
:issue="issue"
|
:issue="issue"
|
||||||
:selection="socialIssuesSelected"
|
:selection="socialIssuesSelected"
|
||||||
@updateSelected="updateIssuesSelected"
|
@updateSelected="updateIssuesSelected"
|
||||||
>
|
>
|
||||||
</check-social-issue>
|
</check-social-issue>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="otherIssues"
|
name="otherIssues"
|
||||||
label="text"
|
label="text"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
open-direction="bottom"
|
open-direction="bottom"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
:preserve-search="false"
|
:preserve-search="false"
|
||||||
:reset-after="true"
|
:reset-after="true"
|
||||||
:hide-selected="true"
|
:hide-selected="true"
|
||||||
:taggable="false"
|
:taggable="false"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
:show-labels="false"
|
:show-labels="false"
|
||||||
:loading="issueIsLoading"
|
:loading="issueIsLoading"
|
||||||
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||||
:options="socialIssuesOther"
|
:options="socialIssuesOther"
|
||||||
@select="addIssueInList"
|
@select="addIssueInList"
|
||||||
>
|
>
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<div class="col-4">
|
||||||
|
<label :class="socialActionsClassList">{{
|
||||||
|
trans(ACTIVITY_SOCIAL_ACTIONS)
|
||||||
|
}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<div v-if="actionIsLoading === true">
|
||||||
|
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 row">
|
<span
|
||||||
<div class="col-4">
|
v-else-if="socialIssuesSelected.length === 0"
|
||||||
<label :class="socialActionsClassList">{{
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
trans(ACTIVITY_SOCIAL_ACTIONS)
|
>
|
||||||
}}</label>
|
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||||
</div>
|
</span>
|
||||||
<div class="col-8">
|
|
||||||
<div v-if="actionIsLoading === true">
|
|
||||||
<i
|
|
||||||
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
|
|
||||||
></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span
|
<template
|
||||||
v-else-if="socialIssuesSelected.length === 0"
|
v-else-if="
|
||||||
class="inline-choice chill-no-data-statement mt-3"
|
socialActionsList.length > 0 &&
|
||||||
>
|
(socialIssuesSelected.length || socialActionsSelected.length)
|
||||||
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
"
|
||||||
</span>
|
>
|
||||||
|
<div
|
||||||
|
id="actionsList"
|
||||||
|
v-for="group in socialActionsList"
|
||||||
|
:key="group.issue"
|
||||||
|
>
|
||||||
|
<span class="badge bg-chill-l-gray text-dark">{{
|
||||||
|
group.issue
|
||||||
|
}}</span>
|
||||||
|
<check-social-action
|
||||||
|
v-for="action in group.actions"
|
||||||
|
:key="action.id"
|
||||||
|
:action="action"
|
||||||
|
:selection="socialActionsSelected"
|
||||||
|
@updateSelected="updateActionsSelected"
|
||||||
|
>
|
||||||
|
</check-social-action>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template
|
<span
|
||||||
v-else-if="
|
v-else-if="actionAreLoaded && socialActionsList.length === 0"
|
||||||
socialActionsList.length > 0 &&
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
(socialIssuesSelected.length ||
|
>
|
||||||
socialActionsSelected.length)
|
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
||||||
"
|
</span>
|
||||||
>
|
</div>
|
||||||
<div
|
</div>
|
||||||
id="actionsList"
|
</teleport>
|
||||||
v-for="group in socialActionsList"
|
|
||||||
:key="group.issue"
|
|
||||||
>
|
|
||||||
<span class="badge bg-chill-l-gray text-dark">{{
|
|
||||||
group.issue
|
|
||||||
}}</span>
|
|
||||||
<check-social-action
|
|
||||||
v-for="action in group.actions"
|
|
||||||
:key="action.id"
|
|
||||||
:action="action"
|
|
||||||
:selection="socialActionsSelected"
|
|
||||||
@updateSelected="updateActionsSelected"
|
|
||||||
>
|
|
||||||
</check-social-action>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-else-if="
|
|
||||||
actionAreLoaded && socialActionsList.length === 0
|
|
||||||
"
|
|
||||||
class="inline-choice chill-no-data-statement mt-3"
|
|
||||||
>
|
|
||||||
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</teleport>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -106,154 +101,153 @@ import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
|
|||||||
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
||||||
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
||||||
import {
|
import {
|
||||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||||
ACTIVITY_SOCIAL_ACTIONS,
|
ACTIVITY_SOCIAL_ACTIONS,
|
||||||
ACTIVITY_SOCIAL_ISSUES,
|
ACTIVITY_SOCIAL_ISSUES,
|
||||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||||
trans,
|
trans,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SocialIssuesAcc",
|
name: "SocialIssuesAcc",
|
||||||
components: {
|
components: {
|
||||||
CheckSocialIssue,
|
CheckSocialIssue,
|
||||||
CheckSocialAction,
|
CheckSocialAction,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||||
|
ACTIVITY_SOCIAL_ACTIONS,
|
||||||
|
ACTIVITY_SOCIAL_ISSUES,
|
||||||
|
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issueIsLoading: false,
|
||||||
|
actionIsLoading: false,
|
||||||
|
actionAreLoaded: false,
|
||||||
|
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
||||||
|
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
socialIssuesList() {
|
||||||
|
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
||||||
},
|
},
|
||||||
setup() {
|
socialIssuesSelected() {
|
||||||
return {
|
return this.$store.state.activity.socialIssues;
|
||||||
trans,
|
|
||||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
|
||||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
|
||||||
ACTIVITY_SOCIAL_ACTIONS,
|
|
||||||
ACTIVITY_SOCIAL_ISSUES,
|
|
||||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
data() {
|
socialIssuesOther() {
|
||||||
return {
|
return this.$store.state.socialIssuesOther;
|
||||||
issueIsLoading: false,
|
|
||||||
actionIsLoading: false,
|
|
||||||
actionAreLoaded: false,
|
|
||||||
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
|
||||||
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
socialActionsList() {
|
||||||
socialIssuesList() {
|
return this.$store.getters.socialActionsListSorted;
|
||||||
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
|
||||||
},
|
|
||||||
socialIssuesSelected() {
|
|
||||||
return this.$store.state.activity.socialIssues;
|
|
||||||
},
|
|
||||||
socialIssuesOther() {
|
|
||||||
return this.$store.state.socialIssuesOther;
|
|
||||||
},
|
|
||||||
socialActionsList() {
|
|
||||||
return this.$store.getters.socialActionsListSorted;
|
|
||||||
},
|
|
||||||
socialActionsSelected() {
|
|
||||||
return this.$store.state.activity.socialActions;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
socialActionsSelected() {
|
||||||
/* Load other issues in multiselect */
|
return this.$store.state.activity.socialActions;
|
||||||
this.issueIsLoading = true;
|
|
||||||
this.actionAreLoaded = false;
|
|
||||||
|
|
||||||
getSocialIssues().then((response) => {
|
|
||||||
/* Add issues to the store */
|
|
||||||
this.$store.commit("updateIssuesOther", response);
|
|
||||||
|
|
||||||
/* Add in list the issues already associated (if not yet listed) */
|
|
||||||
this.socialIssuesSelected.forEach((issue) => {
|
|
||||||
if (
|
|
||||||
this.socialIssuesList.filter((i) => i.id === issue.id)
|
|
||||||
.length !== 1
|
|
||||||
) {
|
|
||||||
this.$store.commit("addIssueInList", issue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Remove from multiselect the issues that are not yet in the checkbox list */
|
|
||||||
this.socialIssuesList.forEach((issue) => {
|
|
||||||
this.$store.commit("removeIssueInOther", issue);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Filter issues */
|
|
||||||
this.$store.commit("filterList", "issues");
|
|
||||||
|
|
||||||
/* Add in list the actions already associated (if not yet listed) */
|
|
||||||
this.socialActionsSelected.forEach((action) => {
|
|
||||||
this.$store.commit("addActionInList", action);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Filter actions */
|
|
||||||
this.$store.commit("filterList", "actions");
|
|
||||||
|
|
||||||
this.issueIsLoading = false;
|
|
||||||
this.actionAreLoaded = true;
|
|
||||||
this.updateActionsList();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
methods: {
|
},
|
||||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
mounted() {
|
||||||
|
/* Load other issues in multiselect */
|
||||||
|
this.issueIsLoading = true;
|
||||||
|
this.actionAreLoaded = false;
|
||||||
|
|
||||||
|
getSocialIssues().then((response) => {
|
||||||
|
/* Add issues to the store */
|
||||||
|
this.$store.commit("updateIssuesOther", response);
|
||||||
|
|
||||||
|
/* Add in list the issues already associated (if not yet listed) */
|
||||||
|
this.socialIssuesSelected.forEach((issue) => {
|
||||||
|
if (
|
||||||
|
this.socialIssuesList.filter((i) => i.id === issue.id).length !== 1
|
||||||
|
) {
|
||||||
|
this.$store.commit("addIssueInList", issue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Remove from multiselect the issues that are not yet in the checkbox list */
|
||||||
|
this.socialIssuesList.forEach((issue) => {
|
||||||
|
this.$store.commit("removeIssueInOther", issue);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Filter issues */
|
||||||
|
this.$store.commit("filterList", "issues");
|
||||||
|
|
||||||
|
/* Add in list the actions already associated (if not yet listed) */
|
||||||
|
this.socialActionsSelected.forEach((action) => {
|
||||||
|
this.$store.commit("addActionInList", action);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Filter actions */
|
||||||
|
this.$store.commit("filterList", "actions");
|
||||||
|
|
||||||
|
this.issueIsLoading = false;
|
||||||
|
this.actionAreLoaded = true;
|
||||||
|
this.updateActionsList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||||
remove it from multiselect, and add socialActions concerned
|
remove it from multiselect, and add socialActions concerned
|
||||||
*/
|
*/
|
||||||
addIssueInList(value) {
|
addIssueInList(value) {
|
||||||
//console.log('addIssueInList', value);
|
//console.log('addIssueInList', value);
|
||||||
this.$store.commit("addIssueInList", value);
|
this.$store.commit("addIssueInList", value);
|
||||||
this.$store.commit("removeIssueInOther", value);
|
this.$store.commit("removeIssueInOther", value);
|
||||||
this.$store.dispatch("addIssueSelected", value);
|
this.$store.dispatch("addIssueSelected", value);
|
||||||
this.updateActionsList();
|
this.updateActionsList();
|
||||||
},
|
},
|
||||||
/* Update value for selected issues checkboxes
|
/* Update value for selected issues checkboxes
|
||||||
*/
|
*/
|
||||||
updateIssuesSelected(issues) {
|
updateIssuesSelected(issues) {
|
||||||
//console.log('updateIssuesSelected', issues);
|
//console.log('updateIssuesSelected', issues);
|
||||||
this.$store.dispatch("updateIssuesSelected", issues);
|
this.$store.dispatch("updateIssuesSelected", issues);
|
||||||
this.updateActionsList();
|
this.updateActionsList();
|
||||||
},
|
},
|
||||||
/* Update value for selected actions checkboxes
|
/* Update value for selected actions checkboxes
|
||||||
*/
|
*/
|
||||||
updateActionsSelected(actions) {
|
updateActionsSelected(actions) {
|
||||||
//console.log('updateActionsSelected', actions);
|
//console.log('updateActionsSelected', actions);
|
||||||
this.$store.dispatch("updateActionsSelected", actions);
|
this.$store.dispatch("updateActionsSelected", actions);
|
||||||
},
|
},
|
||||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||||
to get social actions concerned
|
to get social actions concerned
|
||||||
*/
|
*/
|
||||||
updateActionsList() {
|
updateActionsList() {
|
||||||
this.resetActionsList();
|
this.resetActionsList();
|
||||||
this.socialIssuesSelected.forEach((item) => {
|
this.socialIssuesSelected.forEach((item) => {
|
||||||
this.actionIsLoading = true;
|
this.actionIsLoading = true;
|
||||||
getSocialActionByIssue(item.id).then(
|
getSocialActionByIssue(item.id).then(
|
||||||
(actions) =>
|
(actions) =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
actions.results.forEach((action) => {
|
actions.results.forEach((action) => {
|
||||||
this.$store.commit("addActionInList", action);
|
this.$store.commit("addActionInList", action);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.$store.commit("filterList", "actions");
|
this.$store.commit("filterList", "actions");
|
||||||
|
|
||||||
this.actionIsLoading = false;
|
this.actionIsLoading = false;
|
||||||
this.actionAreLoaded = true;
|
this.actionAreLoaded = true;
|
||||||
resolve();
|
resolve();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}, this);
|
}, this);
|
||||||
},
|
|
||||||
/* Reset socialActions List: flush list and restore selected actions
|
|
||||||
*/
|
|
||||||
resetActionsList() {
|
|
||||||
this.$store.commit("resetActionsList");
|
|
||||||
this.actionAreLoaded = false;
|
|
||||||
this.socialActionsSelected.forEach((item) => {
|
|
||||||
this.$store.commit("addActionInList", item);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
/* Reset socialActions List: flush list and restore selected actions
|
||||||
|
*/
|
||||||
|
resetActionsList() {
|
||||||
|
this.$store.commit("resetActionsList");
|
||||||
|
this.actionAreLoaded = false;
|
||||||
|
this.socialActionsSelected.forEach((item) => {
|
||||||
|
this.$store.commit("addActionInList", item);
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -263,18 +257,18 @@ export default {
|
|||||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
|
|
||||||
span.multiselect__single {
|
span.multiselect__single {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actionsList {
|
#actionsList {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
background-color: whitesmoke;
|
background-color: whitesmoke;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.badge {
|
span.badge {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
@include badge_social($social-issue-color);
|
@include badge_social($social-issue-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -55,6 +55,5 @@
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_view_actions_duplicate_link %}{% endblock %}
|
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -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,166 +1,146 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#mainUser">
|
<teleport to="#mainUser">
|
||||||
<h2 class="chill-red">Utilisateur principal</h2>
|
<h2 class="chill-red">Utilisateur principal</h2>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="null !== this.$store.getters.getMainUser">
|
<div v-if="null !== this.$store.getters.getMainUser">
|
||||||
<calendar-active :user="this.$store.getters.getMainUser" />
|
<calendar-active :user="this.$store.getters.getMainUser" />
|
||||||
</div>
|
|
||||||
<pick-entity
|
|
||||||
:multiple="false"
|
|
||||||
:types="['user']"
|
|
||||||
:uniqid="'main_user_calendar'"
|
|
||||||
:picked="
|
|
||||||
null !== this.$store.getters.getMainUser
|
|
||||||
? [this.$store.getters.getMainUser]
|
|
||||||
: []
|
|
||||||
"
|
|
||||||
:removable-if-set="false"
|
|
||||||
:display-picked="false"
|
|
||||||
:suggested="this.suggestedUsers"
|
|
||||||
:label="'main_user'"
|
|
||||||
@add-new-entity="setMainUser"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
<pick-entity
|
||||||
|
:multiple="false"
|
||||||
|
:types="['user']"
|
||||||
|
:uniqid="'main_user_calendar'"
|
||||||
|
:picked="
|
||||||
|
null !== this.$store.getters.getMainUser
|
||||||
|
? [this.$store.getters.getMainUser]
|
||||||
|
: []
|
||||||
|
"
|
||||||
|
:removable-if-set="false"
|
||||||
|
:display-picked="false"
|
||||||
|
:suggested="this.suggestedUsers"
|
||||||
|
:label="'main_user'"
|
||||||
|
@add-new-entity="setMainUser"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
|
||||||
<concerned-groups />
|
<concerned-groups />
|
||||||
|
|
||||||
<teleport to="#schedule">
|
<teleport to="#schedule">
|
||||||
<div class="row mb-3" v-if="activity.startDate !== null">
|
<div class="row mb-3" v-if="activity.startDate !== null">
|
||||||
<label class="col-form-label col-sm-4">Date</label>
|
<label class="col-form-label col-sm-4">Date</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
{{ $d(activity.startDate, "long") }} -
|
{{ $d(activity.startDate, "long") }} -
|
||||||
{{ $d(activity.endDate, "hoursOnly") }}
|
{{ $d(activity.endDate, "hoursOnly") }}
|
||||||
<span v-if="activity.calendarRange === null"
|
<span v-if="activity.calendarRange === null"
|
||||||
>(Pas de plage de disponibilité sélectionnée)</span
|
>(Pas de plage de disponibilité sélectionnée)</span
|
||||||
>
|
|
||||||
<span v-else>(Une plage de disponibilité sélectionnée)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</teleport>
|
|
||||||
|
|
||||||
<location />
|
|
||||||
|
|
||||||
<teleport to="#fullCalendar">
|
|
||||||
<div class="calendar-actives">
|
|
||||||
<template v-for="u in getActiveUsers" :key="u.id">
|
|
||||||
<calendar-active
|
|
||||||
:user="u"
|
|
||||||
:invite="this.$store.getters.getInviteForUser(u)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="display-options row justify-content-between"
|
|
||||||
style="margin-top: 1rem"
|
|
||||||
>
|
>
|
||||||
<div class="col-sm-9 col-xs-12">
|
<span v-else>(Une plage de disponibilité sélectionnée)</span>
|
||||||
<div class="input-group mb-3">
|
</div>
|
||||||
<label class="input-group-text" for="slotDuration"
|
</div>
|
||||||
>Durée des créneaux</label
|
</teleport>
|
||||||
>
|
|
||||||
<select
|
<location />
|
||||||
v-model="slotDuration"
|
|
||||||
id="slotDuration"
|
<teleport to="#fullCalendar">
|
||||||
class="form-select"
|
<div class="calendar-actives">
|
||||||
>
|
<template v-for="u in getActiveUsers" :key="u.id">
|
||||||
<option value="00:05:00">5 minutes</option>
|
<calendar-active
|
||||||
<option value="00:10:00">10 minutes</option>
|
:user="u"
|
||||||
<option value="00:15:00">15 minutes</option>
|
:invite="this.$store.getters.getInviteForUser(u)"
|
||||||
<option value="00:30:00">30 minutes</option>
|
/>
|
||||||
<option value="00:45:00">45 minutes</option>
|
</template>
|
||||||
<option value="00:60:00">60 minutes</option>
|
</div>
|
||||||
</select>
|
<div
|
||||||
<label class="input-group-text" for="slotMinTime">De</label>
|
class="display-options row justify-content-between"
|
||||||
<select
|
style="margin-top: 1rem"
|
||||||
v-model="slotMinTime"
|
>
|
||||||
id="slotMinTime"
|
<div class="col-sm-9 col-xs-12">
|
||||||
class="form-select"
|
<div class="input-group mb-3">
|
||||||
>
|
<label class="input-group-text" for="slotDuration"
|
||||||
<option value="00:00:00">0h</option>
|
>Durée des créneaux</label
|
||||||
<option value="01:00:00">1h</option>
|
>
|
||||||
<option value="02:00:00">2h</option>
|
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
||||||
<option value="03:00:00">3h</option>
|
<option value="00:05:00">5 minutes</option>
|
||||||
<option value="04:00:00">4h</option>
|
<option value="00:10:00">10 minutes</option>
|
||||||
<option value="05:00:00">5h</option>
|
<option value="00:15:00">15 minutes</option>
|
||||||
<option value="06:00:00">6h</option>
|
<option value="00:30:00">30 minutes</option>
|
||||||
<option value="07:00:00">7h</option>
|
</select>
|
||||||
<option value="08:00:00">8h</option>
|
<label class="input-group-text" for="slotMinTime">De</label>
|
||||||
<option value="09:00:00">9h</option>
|
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
||||||
<option value="10:00:00">10h</option>
|
<option value="00:00:00">0h</option>
|
||||||
<option value="11:00:00">11h</option>
|
<option value="01:00:00">1h</option>
|
||||||
<option value="12:00:00">12h</option>
|
<option value="02:00:00">2h</option>
|
||||||
</select>
|
<option value="03:00:00">3h</option>
|
||||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
<option value="04:00:00">4h</option>
|
||||||
<select
|
<option value="05:00:00">5h</option>
|
||||||
v-model="slotMaxTime"
|
<option value="06:00:00">6h</option>
|
||||||
id="slotMaxTime"
|
<option value="07:00:00">7h</option>
|
||||||
class="form-select"
|
<option value="08:00:00">8h</option>
|
||||||
>
|
<option value="09:00:00">9h</option>
|
||||||
<option value="12:00:00">12h</option>
|
<option value="10:00:00">10h</option>
|
||||||
<option value="13:00:00">13h</option>
|
<option value="11:00:00">11h</option>
|
||||||
<option value="14:00:00">14h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="15:00:00">15h</option>
|
</select>
|
||||||
<option value="16:00:00">16h</option>
|
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||||
<option value="17:00:00">17h</option>
|
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
||||||
<option value="18:00:00">18h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="19:00:00">19h</option>
|
<option value="13:00:00">13h</option>
|
||||||
<option value="20:00:00">20h</option>
|
<option value="14:00:00">14h</option>
|
||||||
<option value="21:00:00">21h</option>
|
<option value="15:00:00">15h</option>
|
||||||
<option value="22:00:00">22h</option>
|
<option value="16:00:00">16h</option>
|
||||||
<option value="23:00:00">23h</option>
|
<option value="17:00:00">17h</option>
|
||||||
<option value="23:59:59">24h</option>
|
<option value="18:00:00">18h</option>
|
||||||
</select>
|
<option value="19:00:00">19h</option>
|
||||||
</div>
|
<option value="20:00:00">20h</option>
|
||||||
</div>
|
<option value="21:00:00">21h</option>
|
||||||
<div class="col-sm-3 col-xs-12">
|
<option value="22:00:00">22h</option>
|
||||||
<div class="float-end">
|
<option value="23:00:00">23h</option>
|
||||||
<div class="form-check input-group">
|
<option value="23:59:59">24h</option>
|
||||||
<span class="input-group-text">
|
</select>
|
||||||
<input
|
|
||||||
id="showHideWE"
|
|
||||||
class="mt-0"
|
|
||||||
type="checkbox"
|
|
||||||
v-model="hideWeekends"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<label
|
|
||||||
for="showHideWE"
|
|
||||||
class="form-check-label input-group-text"
|
|
||||||
>Week-ends</label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
</div>
|
||||||
<template #eventContent="arg">
|
<div class="col-sm-3 col-xs-12">
|
||||||
<span>
|
<div class="float-end">
|
||||||
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
<div class="form-check input-group">
|
||||||
arg.event.title
|
<span class="input-group-text">
|
||||||
}}</b>
|
<input
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
id="showHideWE"
|
||||||
>{{ arg.timeText }}
|
class="mt-0"
|
||||||
{{ arg.event.extendedProps.locationName }}
|
type="checkbox"
|
||||||
<small>{{
|
v-model="hideWeekends"
|
||||||
arg.event.extendedProps.userLabel
|
/>
|
||||||
}}</small></b
|
</span>
|
||||||
>
|
<label for="showHideWE" class="form-check-label input-group-text"
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'current'"
|
>Week-ends</label
|
||||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
>
|
||||||
</b>
|
</div>
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
</div>
|
||||||
arg.event.title
|
</div>
|
||||||
}}</b>
|
</div>
|
||||||
<b v-else
|
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
<template #eventContent="arg">
|
||||||
</b>
|
<span>
|
||||||
</span>
|
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
||||||
</template>
|
arg.event.title
|
||||||
</FullCalendar>
|
}}</b>
|
||||||
</teleport>
|
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
||||||
|
>{{ arg.timeText }}
|
||||||
|
{{ arg.event.extendedProps.locationName }}
|
||||||
|
<small>{{ arg.event.extendedProps.userLabel }}</small></b
|
||||||
|
>
|
||||||
|
<b v-else-if="arg.event.extendedProps.is === 'current'"
|
||||||
|
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||||
|
</b>
|
||||||
|
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||||
|
arg.event.title
|
||||||
|
}}</b>
|
||||||
|
<b v-else>{{ arg.timeText }} {{ $t("current_selected") }} </b>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</FullCalendar>
|
||||||
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -177,219 +157,210 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
|||||||
import { mapGetters, mapState } from "vuex";
|
import { mapGetters, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
ConcernedGroups,
|
ConcernedGroups,
|
||||||
Location,
|
Location,
|
||||||
FullCalendar,
|
FullCalendar,
|
||||||
CalendarActive,
|
CalendarActive,
|
||||||
PickEntity,
|
PickEntity,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
errorMsg: [],
|
||||||
|
showMyCalendar: false,
|
||||||
|
slotDuration: "00:05:00",
|
||||||
|
slotMinTime: "09:00:00",
|
||||||
|
slotMaxTime: "18:00:00",
|
||||||
|
hideWeekEnds: true,
|
||||||
|
previousUser: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(["getMainUser"]),
|
||||||
|
...mapState(["activity"]),
|
||||||
|
events() {
|
||||||
|
return this.$store.getters.getEventSources;
|
||||||
},
|
},
|
||||||
data() {
|
calendarOptions() {
|
||||||
return {
|
return {
|
||||||
errorMsg: [],
|
locale: frLocale,
|
||||||
showMyCalendar: false,
|
plugins: [
|
||||||
slotDuration: "00:05:00",
|
dayGridPlugin,
|
||||||
slotMinTime: "09:00:00",
|
interactionPlugin,
|
||||||
slotMaxTime: "18:00:00",
|
timeGridPlugin,
|
||||||
hideWeekEnds: true,
|
dayGridPlugin,
|
||||||
previousUser: [],
|
listPlugin,
|
||||||
};
|
],
|
||||||
|
initialView: "timeGridWeek",
|
||||||
|
initialDate: this.$store.getters.getInitialDate,
|
||||||
|
eventSources: this.events,
|
||||||
|
selectable: true,
|
||||||
|
slotMinTime: this.slotMinTime,
|
||||||
|
slotMaxTime: this.slotMaxTime,
|
||||||
|
scrollTimeReset: false,
|
||||||
|
datesSet: this.onDatesSet,
|
||||||
|
select: this.onDateSelect,
|
||||||
|
eventChange: this.onEventChange,
|
||||||
|
eventClick: this.onEventClick,
|
||||||
|
selectMirror: true,
|
||||||
|
editable: true,
|
||||||
|
weekends: !this.hideWeekEnds,
|
||||||
|
headerToolbar: {
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "timeGridWeek,timeGridDay,listWeek",
|
||||||
|
},
|
||||||
|
views: {
|
||||||
|
timeGrid: {
|
||||||
|
slotEventOverlap: false,
|
||||||
|
slotDuration: this.slotDuration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
getActiveUsers() {
|
||||||
...mapGetters(["getMainUser"]),
|
const users = [];
|
||||||
...mapState(["activity"]),
|
for (const id of this.$store.state.currentView.users.keys()) {
|
||||||
events() {
|
users.push(this.$store.getters.getUserDataById(id).user);
|
||||||
return this.$store.getters.getEventSources;
|
}
|
||||||
},
|
return users;
|
||||||
calendarOptions() {
|
|
||||||
return {
|
|
||||||
locale: frLocale,
|
|
||||||
plugins: [
|
|
||||||
dayGridPlugin,
|
|
||||||
interactionPlugin,
|
|
||||||
timeGridPlugin,
|
|
||||||
dayGridPlugin,
|
|
||||||
listPlugin,
|
|
||||||
],
|
|
||||||
initialView: "timeGridWeek",
|
|
||||||
initialDate: this.$store.getters.getInitialDate,
|
|
||||||
eventSources: this.events,
|
|
||||||
selectable: true,
|
|
||||||
slotMinTime: this.slotMinTime,
|
|
||||||
slotMaxTime: this.slotMaxTime,
|
|
||||||
scrollTimeReset: false,
|
|
||||||
datesSet: this.onDatesSet,
|
|
||||||
select: this.onDateSelect,
|
|
||||||
eventChange: this.onEventChange,
|
|
||||||
eventClick: this.onEventClick,
|
|
||||||
selectMirror: true,
|
|
||||||
editable: true,
|
|
||||||
weekends: !this.hideWeekEnds,
|
|
||||||
headerToolbar: {
|
|
||||||
left: "prev,next today",
|
|
||||||
center: "title",
|
|
||||||
right: "timeGridWeek,timeGridDay,listWeek",
|
|
||||||
},
|
|
||||||
views: {
|
|
||||||
timeGrid: {
|
|
||||||
slotEventOverlap: false,
|
|
||||||
slotDuration: this.slotDuration,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getActiveUsers() {
|
|
||||||
const users = [];
|
|
||||||
for (const id of this.$store.state.currentView.users.keys()) {
|
|
||||||
users.push(this.$store.getters.getUserDataById(id).user);
|
|
||||||
}
|
|
||||||
return users;
|
|
||||||
},
|
|
||||||
suggestedUsers() {
|
|
||||||
const suggested = [];
|
|
||||||
|
|
||||||
this.$data.previousUser.forEach((u) => {
|
|
||||||
if (u.id !== this.$store.getters.getMainUser.id) {
|
|
||||||
suggested.push(u);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return suggested;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
suggestedUsers() {
|
||||||
setMainUser({ entity }) {
|
const suggested = [];
|
||||||
const user = entity;
|
|
||||||
console.log("setMainUser APP", entity);
|
|
||||||
|
|
||||||
if (
|
this.$data.previousUser.forEach((u) => {
|
||||||
user.id !== this.$store.getters.getMainUser &&
|
if (u.id !== this.$store.getters.getMainUser.id) {
|
||||||
(this.$store.state.activity.calendarRange !== null ||
|
suggested.push(u);
|
||||||
this.$store.state.activity.startDate !== null ||
|
}
|
||||||
this.$store.state.activity.endDate !== null)
|
});
|
||||||
) {
|
|
||||||
if (
|
|
||||||
!window.confirm(
|
|
||||||
this.$t("change_main_user_will_reset_event_data"),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the previous user, if any, in the previous user list (in use for suggestion)
|
return suggested;
|
||||||
if (null !== this.$store.getters.getMainUser) {
|
|
||||||
const suggestedUids = new Set(
|
|
||||||
this.$data.previousUser.map((u) => u.id),
|
|
||||||
);
|
|
||||||
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
|
||||||
this.$data.previousUser.push(
|
|
||||||
this.$store.getters.getMainUser,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch("setMainUser", user);
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user,
|
|
||||||
ranges: true,
|
|
||||||
remotes: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeMainUser(user) {
|
|
||||||
console.log("removeMainUser APP", user);
|
|
||||||
|
|
||||||
window.alert(this.$t("main_user_is_mandatory"));
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
onDatesSet(event) {
|
|
||||||
console.log("onDatesSet", event);
|
|
||||||
this.$store.dispatch("setCurrentDatesView", {
|
|
||||||
start: event.start,
|
|
||||||
end: event.end,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onDateSelect(payload) {
|
|
||||||
console.log("onDateSelect", payload);
|
|
||||||
|
|
||||||
// show an alert if changing mainUser
|
|
||||||
if (
|
|
||||||
(this.$store.getters.getMainUser !== null &&
|
|
||||||
this.$store.state.me.id !==
|
|
||||||
this.$store.getters.getMainUser.id) ||
|
|
||||||
this.$store.getters.getMainUser === null
|
|
||||||
) {
|
|
||||||
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user: this.$store.state.me,
|
|
||||||
remotes: true,
|
|
||||||
ranges: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch("setEventTimes", {
|
|
||||||
start: payload.start,
|
|
||||||
end: payload.end,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onEventChange(payload) {
|
|
||||||
console.log("onEventChange", payload);
|
|
||||||
if (this.$store.state.activity.calendarRange !== null) {
|
|
||||||
throw new Error(
|
|
||||||
"not allowed to edit a calendar associated with a calendar range",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.$store.dispatch("setEventTimes", {
|
|
||||||
start: payload.event.start,
|
|
||||||
end: payload.event.end,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onEventClick(payload) {
|
|
||||||
if (payload.event.extendedProps.is !== "range") {
|
|
||||||
// do nothing when clicking on remote
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// show an alert if changing mainUser
|
|
||||||
if (
|
|
||||||
this.$store.getters.getMainUser !== null &&
|
|
||||||
payload.event.extendedProps.userId !==
|
|
||||||
this.$store.getters.getMainUser.id
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
!window.confirm(
|
|
||||||
this.$t("this_calendar_range_will_change_main_user"),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch("associateCalendarToRange", {
|
|
||||||
range: payload.event,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setMainUser({ entity }) {
|
||||||
|
const user = entity;
|
||||||
|
console.log("setMainUser APP", entity);
|
||||||
|
|
||||||
|
if (
|
||||||
|
user.id !== this.$store.getters.getMainUser &&
|
||||||
|
(this.$store.state.activity.calendarRange !== null ||
|
||||||
|
this.$store.state.activity.startDate !== null ||
|
||||||
|
this.$store.state.activity.endDate !== null)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!window.confirm(this.$t("change_main_user_will_reset_event_data"))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the previous user, if any, in the previous user list (in use for suggestion)
|
||||||
|
if (null !== this.$store.getters.getMainUser) {
|
||||||
|
const suggestedUids = new Set(this.$data.previousUser.map((u) => u.id));
|
||||||
|
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
||||||
|
this.$data.previousUser.push(this.$store.getters.getMainUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch("setMainUser", user);
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user,
|
||||||
|
ranges: true,
|
||||||
|
remotes: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeMainUser(user) {
|
||||||
|
console.log("removeMainUser APP", user);
|
||||||
|
|
||||||
|
window.alert(this.$t("main_user_is_mandatory"));
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onDatesSet(event) {
|
||||||
|
console.log("onDatesSet", event);
|
||||||
|
this.$store.dispatch("setCurrentDatesView", {
|
||||||
|
start: event.start,
|
||||||
|
end: event.end,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDateSelect(payload) {
|
||||||
|
console.log("onDateSelect", payload);
|
||||||
|
|
||||||
|
// show an alert if changing mainUser
|
||||||
|
if (
|
||||||
|
(this.$store.getters.getMainUser !== null &&
|
||||||
|
this.$store.state.me.id !== this.$store.getters.getMainUser.id) ||
|
||||||
|
this.$store.getters.getMainUser === null
|
||||||
|
) {
|
||||||
|
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.$store.state.me,
|
||||||
|
remotes: true,
|
||||||
|
ranges: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch("setEventTimes", {
|
||||||
|
start: payload.start,
|
||||||
|
end: payload.end,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEventChange(payload) {
|
||||||
|
console.log("onEventChange", payload);
|
||||||
|
if (this.$store.state.activity.calendarRange !== null) {
|
||||||
|
throw new Error(
|
||||||
|
"not allowed to edit a calendar associated with a calendar range",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.$store.dispatch("setEventTimes", {
|
||||||
|
start: payload.event.start,
|
||||||
|
end: payload.event.end,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEventClick(payload) {
|
||||||
|
if (payload.event.extendedProps.is !== "range") {
|
||||||
|
// do nothing when clicking on remote
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// show an alert if changing mainUser
|
||||||
|
if (
|
||||||
|
this.$store.getters.getMainUser !== null &&
|
||||||
|
payload.event.extendedProps.userId !==
|
||||||
|
this.$store.getters.getMainUser.id
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!window.confirm(this.$t("this_calendar_range_will_change_main_user"))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch("associateCalendarToRange", {
|
||||||
|
range: payload.event,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.calendar-actives {
|
.calendar-actives {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-options {
|
.display-options {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for events which are range */
|
/* for events which are range */
|
||||||
.fc-event.isrange {
|
.fc-event.isrange {
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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,228 +1,177 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
||||||
<vue-multiselect
|
<vue-multiselect
|
||||||
v-model="pickedLocation"
|
v-model="pickedLocation"
|
||||||
:options="locations"
|
:options="locations"
|
||||||
:label="'name'"
|
:label="'name'"
|
||||||
:track-by="'id'"
|
:track-by="'id'"
|
||||||
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
||||||
:selectedLabel="'Choisir'"
|
:selectedLabel="'Choisir'"
|
||||||
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
||||||
:placeholder="'Choisir'"
|
:placeholder="'Choisir'"
|
||||||
></vue-multiselect>
|
></vue-multiselect>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
class="display-options row justify-content-between"
|
<div
|
||||||
style="margin-top: 1rem"
|
class="display-options row justify-content-between"
|
||||||
>
|
style="margin-top: 1rem"
|
||||||
<div class="col-sm-9 col-xs-12">
|
>
|
||||||
<div class="input-group mb-3">
|
<div class="col-sm-9 col-xs-12">
|
||||||
<label class="input-group-text" for="slotDuration"
|
<div class="input-group mb-3">
|
||||||
>Durée des créneaux</label
|
<label class="input-group-text" for="slotDuration"
|
||||||
>
|
>Durée des créneaux</label
|
||||||
<select
|
>
|
||||||
v-model="slotDuration"
|
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
||||||
id="slotDuration"
|
<option value="00:05:00">5 minutes</option>
|
||||||
class="form-select"
|
<option value="00:10:00">10 minutes</option>
|
||||||
>
|
<option value="00:15:00">15 minutes</option>
|
||||||
<option value="00:05:00">5 minutes</option>
|
<option value="00:30:00">30 minutes</option>
|
||||||
<option value="00:10:00">10 minutes</option>
|
</select>
|
||||||
<option value="00:15:00">15 minutes</option>
|
<label class="input-group-text" for="slotMinTime">De</label>
|
||||||
<option value="00:30:00">30 minutes</option>
|
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
||||||
<option value="00:45:00">45 minutes</option>
|
<option value="00:00:00">0h</option>
|
||||||
<option value="00:60:00">60 minutes</option>
|
<option value="01:00:00">1h</option>
|
||||||
</select>
|
<option value="02:00:00">2h</option>
|
||||||
<label class="input-group-text" for="slotMinTime">De</label>
|
<option value="03:00:00">3h</option>
|
||||||
<select
|
<option value="04:00:00">4h</option>
|
||||||
v-model="slotMinTime"
|
<option value="05:00:00">5h</option>
|
||||||
id="slotMinTime"
|
<option value="06:00:00">6h</option>
|
||||||
class="form-select"
|
<option value="07:00:00">7h</option>
|
||||||
>
|
<option value="08:00:00">8h</option>
|
||||||
<option value="00:00:00">0h</option>
|
<option value="09:00:00">9h</option>
|
||||||
<option value="01:00:00">1h</option>
|
<option value="10:00:00">10h</option>
|
||||||
<option value="02:00:00">2h</option>
|
<option value="11:00:00">11h</option>
|
||||||
<option value="03:00:00">3h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="04:00:00">4h</option>
|
</select>
|
||||||
<option value="05:00:00">5h</option>
|
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||||
<option value="06:00:00">6h</option>
|
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
||||||
<option value="07:00:00">7h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="08:00:00">8h</option>
|
<option value="13:00:00">13h</option>
|
||||||
<option value="09:00:00">9h</option>
|
<option value="14:00:00">14h</option>
|
||||||
<option value="10:00:00">10h</option>
|
<option value="15:00:00">15h</option>
|
||||||
<option value="11:00:00">11h</option>
|
<option value="16:00:00">16h</option>
|
||||||
<option value="12:00:00">12h</option>
|
<option value="17:00:00">17h</option>
|
||||||
</select>
|
<option value="18:00:00">18h</option>
|
||||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
<option value="19:00:00">19h</option>
|
||||||
<select
|
<option value="20:00:00">20h</option>
|
||||||
v-model="slotMaxTime"
|
<option value="21:00:00">21h</option>
|
||||||
id="slotMaxTime"
|
<option value="22:00:00">22h</option>
|
||||||
class="form-select"
|
<option value="23:00:00">23h</option>
|
||||||
>
|
<option value="23:59:59">24h</option>
|
||||||
<option value="12:00:00">12h</option>
|
</select>
|
||||||
<option value="13:00:00">13h</option>
|
</div>
|
||||||
<option value="14:00:00">14h</option>
|
|
||||||
<option value="15:00:00">15h</option>
|
|
||||||
<option value="16:00:00">16h</option>
|
|
||||||
<option value="17:00:00">17h</option>
|
|
||||||
<option value="18:00:00">18h</option>
|
|
||||||
<option value="19:00:00">19h</option>
|
|
||||||
<option value="20:00:00">20h</option>
|
|
||||||
<option value="21:00:00">21h</option>
|
|
||||||
<option value="22:00:00">22h</option>
|
|
||||||
<option value="23:00:00">23h</option>
|
|
||||||
<option value="23:59:59">24h</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-3">
|
|
||||||
<div class="float-end">
|
|
||||||
<div class="form-check input-group">
|
|
||||||
<span class="input-group-text">
|
|
||||||
<input
|
|
||||||
id="showHideWE"
|
|
||||||
class="mt-0"
|
|
||||||
type="checkbox"
|
|
||||||
v-model="showWeekends"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<label
|
|
||||||
for="showHideWE"
|
|
||||||
class="form-check-label input-group-text"
|
|
||||||
>Week-ends</label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
<div class="col-xs-12 col-sm-3">
|
||||||
<template v-slot:eventContent="{ event }: { event: EventApi }">
|
<div class="float-end">
|
||||||
<span :class="eventClasses">
|
<div class="form-check input-group">
|
||||||
<b v-if="event.extendedProps.is === 'remote'">{{
|
<span class="input-group-text">
|
||||||
event.title
|
<input
|
||||||
}}</b>
|
id="showHideWE"
|
||||||
<b v-else-if="event.extendedProps.is === 'range'"
|
class="mt-0"
|
||||||
>{{ formatDate(event.startStr, "time") }} -
|
type="checkbox"
|
||||||
{{ formatDate(event.endStr, "time") }}:
|
v-model="showWeekends"
|
||||||
{{ event.extendedProps.locationName }}</b
|
/>
|
||||||
>
|
</span>
|
||||||
<b v-else-if="event.extendedProps.is === 'local'">{{
|
<label for="showHideWE" class="form-check-label input-group-text"
|
||||||
event.title
|
>Week-ends</label
|
||||||
}}</b>
|
>
|
||||||
<b v-else>no 'is'</b>
|
</div>
|
||||||
<a
|
</div>
|
||||||
v-if="event.extendedProps.is === 'range'"
|
</div>
|
||||||
class="fa fa-fw fa-times delete"
|
</div>
|
||||||
@click.prevent="onClickDelete(event)"
|
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
||||||
>
|
<template v-slot:eventContent="{ event }: { event: EventApi }">
|
||||||
</a>
|
<span :class="eventClasses">
|
||||||
</span>
|
<b v-if="event.extendedProps.is === 'remote'">{{ event.title }}</b>
|
||||||
|
<b v-else-if="event.extendedProps.is === 'range'"
|
||||||
|
>{{ formatDate(event.startStr) }} -
|
||||||
|
{{ event.extendedProps.locationName }}</b
|
||||||
|
>
|
||||||
|
<b v-else-if="event.extendedProps.is === 'local'">{{ event.title }}</b>
|
||||||
|
<b v-else>no 'is'</b>
|
||||||
|
<a
|
||||||
|
v-if="event.extendedProps.is === 'range'"
|
||||||
|
class="fa fa-fw fa-times delete"
|
||||||
|
@click.prevent="onClickDelete(event)"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</FullCalendar>
|
||||||
|
|
||||||
|
<div id="copy-widget">
|
||||||
|
<div class="container mt-2 mb-2">
|
||||||
|
<div class="row justify-content-between align-items-center mb-4">
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-2">
|
||||||
|
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||||
|
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
|
||||||
|
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||||
|
<option value="week">
|
||||||
|
{{ $t("from_week_to_week") }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<template v-if="dayOrWeek === 'day'">
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
|
<input class="form-control" type="date" v-model="copyFrom" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||||
|
<i class="fa fa-angle-double-right"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
|
<input class="form-control" type="date" v-model="copyTo" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||||
|
<button class="btn btn-action float-end" @click="copyDay">
|
||||||
|
{{ $t("copy_range") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</FullCalendar>
|
<template v-else>
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
<div id="copy-widget">
|
<select
|
||||||
<div class="container mt-2 mb-2">
|
v-model="copyFromWeek"
|
||||||
<div class="row justify-content-between align-items-center mb-4">
|
id="copyFromWeek"
|
||||||
<div class="col-xs-12 col-sm-3 col-md-2">
|
class="form-select"
|
||||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
>
|
||||||
</div>
|
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
|
||||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
{{ w.text }}
|
||||||
<select
|
</option>
|
||||||
v-model="dayOrWeek"
|
</select>
|
||||||
id="dayOrWeek"
|
</div>
|
||||||
class="form-select"
|
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||||
>
|
<i class="fa fa-angle-double-right"></i>
|
||||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
</div>
|
||||||
<option value="week">
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
{{ $t("from_week_to_week") }}
|
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
|
||||||
</option>
|
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
|
||||||
</select>
|
{{ w.text }}
|
||||||
</div>
|
</option>
|
||||||
<template v-if="dayOrWeek === 'day'">
|
</select>
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
</div>
|
||||||
<input
|
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||||
class="form-control"
|
<button class="btn btn-action float-end" @click="copyWeek">
|
||||||
type="date"
|
{{ $t("copy_range") }}
|
||||||
v-model="copyFrom"
|
</button>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
</div>
|
||||||
<i class="fa fa-angle-double-right"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
|
||||||
<input
|
|
||||||
class="form-control"
|
|
||||||
type="date"
|
|
||||||
v-model="copyTo"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
|
||||||
<button
|
|
||||||
class="btn btn-action float-end"
|
|
||||||
@click="copyDay"
|
|
||||||
>
|
|
||||||
{{ $t("copy_range") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
|
||||||
<select
|
|
||||||
v-model="copyFromWeek"
|
|
||||||
id="copyFromWeek"
|
|
||||||
class="form-select"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="w in lastWeeks"
|
|
||||||
:value="w.value"
|
|
||||||
:key="w.value"
|
|
||||||
>
|
|
||||||
{{ w.text }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
|
||||||
<i class="fa fa-angle-double-right"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
|
||||||
<select
|
|
||||||
v-model="copyToWeek"
|
|
||||||
id="copyToWeek"
|
|
||||||
class="form-select"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="w in nextWeeks"
|
|
||||||
:value="w.value"
|
|
||||||
:key="w.value"
|
|
||||||
>
|
|
||||||
{{ w.text }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
|
||||||
<button
|
|
||||||
class="btn btn-action float-end"
|
|
||||||
@click="copyWeek"
|
|
||||||
>
|
|
||||||
{{ $t("copy_range") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- not directly seen, but include in a modal -->
|
<!-- not directly seen, but include in a modal -->
|
||||||
<edit-location ref="editLocation"></edit-location>
|
<edit-location ref="editLocation"></edit-location>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {
|
import type {
|
||||||
CalendarOptions,
|
CalendarOptions,
|
||||||
DatesSetArg,
|
DatesSetArg,
|
||||||
EventInput,
|
EventInput,
|
||||||
} from "@fullcalendar/core";
|
} from "@fullcalendar/core";
|
||||||
import { computed, ref, onMounted } from "vue";
|
import { computed, ref, onMounted } from "vue";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
@@ -230,14 +179,14 @@ import { key } from "./store";
|
|||||||
import FullCalendar from "@fullcalendar/vue3";
|
import FullCalendar from "@fullcalendar/vue3";
|
||||||
import frLocale from "@fullcalendar/core/locales/fr";
|
import frLocale from "@fullcalendar/core/locales/fr";
|
||||||
import interactionPlugin, {
|
import interactionPlugin, {
|
||||||
EventResizeDoneArg,
|
EventResizeDoneArg,
|
||||||
} from "@fullcalendar/interaction";
|
} from "@fullcalendar/interaction";
|
||||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
import {
|
import {
|
||||||
EventApi,
|
EventApi,
|
||||||
DateSelectArg,
|
DateSelectArg,
|
||||||
EventDropArg,
|
EventDropArg,
|
||||||
EventClickArg,
|
EventClickArg,
|
||||||
} from "@fullcalendar/core";
|
} from "@fullcalendar/core";
|
||||||
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
|
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
@@ -258,113 +207,96 @@ const copyFromWeek = ref<string | null>(null);
|
|||||||
const copyToWeek = ref<string | null>(null);
|
const copyToWeek = ref<string | null>(null);
|
||||||
|
|
||||||
interface Weeks {
|
interface Weeks {
|
||||||
value: string | null;
|
value: string | null;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMonday = (week: number): Date => {
|
const getMonday = (week: number): Date => {
|
||||||
const lastMonday = new Date();
|
const lastMonday = new Date();
|
||||||
lastMonday.setDate(
|
lastMonday.setDate(
|
||||||
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
||||||
);
|
);
|
||||||
return lastMonday;
|
return lastMonday;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
};
|
};
|
||||||
|
|
||||||
const lastWeeks = computed((): Weeks[] =>
|
const lastWeeks = computed((): Weeks[] =>
|
||||||
Array.from(Array(30).keys()).map((w) => {
|
Array.from(Array(30).keys()).map((w) => {
|
||||||
const lastMonday = getMonday(15 - w);
|
const lastMonday = getMonday(15 - w);
|
||||||
return {
|
return {
|
||||||
value: dateToISO(lastMonday),
|
value: dateToISO(lastMonday),
|
||||||
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextWeeks = computed((): Weeks[] =>
|
const nextWeeks = computed((): Weeks[] =>
|
||||||
Array.from(Array(52).keys()).map((w) => {
|
Array.from(Array(52).keys()).map((w) => {
|
||||||
const nextMonday = getMonday(w + 1);
|
const nextMonday = getMonday(w + 1);
|
||||||
return {
|
return {
|
||||||
value: dateToISO(nextMonday),
|
value: dateToISO(nextMonday),
|
||||||
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatDate = (datetime: string, format: null | "time" = null) => {
|
const formatDate = (datetime: string) => {
|
||||||
const date = ISOToDate(datetime);
|
console.log(typeof datetime);
|
||||||
if (!date) return "";
|
return ISOToDate(datetime);
|
||||||
|
|
||||||
if (format === "time") {
|
|
||||||
return date.toLocaleTimeString("fr-FR", {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// French date formatting
|
|
||||||
return date.toLocaleDateString("fr-FR", {
|
|
||||||
weekday: "short",
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseOptions = ref<CalendarOptions>({
|
const baseOptions = ref<CalendarOptions>({
|
||||||
locale: frLocale,
|
locale: frLocale,
|
||||||
plugins: [interactionPlugin, timeGridPlugin],
|
plugins: [interactionPlugin, timeGridPlugin],
|
||||||
initialView: "timeGridWeek",
|
initialView: "timeGridWeek",
|
||||||
initialDate: new Date(),
|
initialDate: new Date(),
|
||||||
scrollTimeReset: false,
|
scrollTimeReset: false,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
// when the dates are changes in the fullcalendar view OR when new events are added
|
// when the dates are changes in the fullcalendar view OR when new events are added
|
||||||
datesSet: onDatesSet,
|
datesSet: onDatesSet,
|
||||||
// when a date is selected
|
// when a date is selected
|
||||||
select: onDateSelect,
|
select: onDateSelect,
|
||||||
// when a event is resized
|
// when a event is resized
|
||||||
eventResize: onEventDropOrResize,
|
eventResize: onEventDropOrResize,
|
||||||
// when an event is moved
|
// when an event is moved
|
||||||
eventDrop: onEventDropOrResize,
|
eventDrop: onEventDropOrResize,
|
||||||
// when an event si clicked
|
// when an event si clicked
|
||||||
eventClick: onEventClick,
|
eventClick: onEventClick,
|
||||||
selectMirror: false,
|
selectMirror: false,
|
||||||
editable: true,
|
editable: true,
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
left: "prev,next today",
|
left: "prev,next today",
|
||||||
center: "title",
|
center: "title",
|
||||||
right: "timeGridWeek,timeGridDay",
|
right: "timeGridWeek,timeGridDay",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ranges = computed<EventInput[]>(() => {
|
const ranges = computed<EventInput[]>(() => {
|
||||||
return store.state.calendarRanges.ranges;
|
return store.state.calendarRanges.ranges;
|
||||||
});
|
});
|
||||||
|
|
||||||
const locations = computed<Location[]>(() => {
|
const locations = computed<Location[]>(() => {
|
||||||
return store.state.locations.locations;
|
return store.state.locations.locations;
|
||||||
});
|
});
|
||||||
|
|
||||||
const pickedLocation = computed<Location | null>({
|
const pickedLocation = computed<Location | null>({
|
||||||
get(): Location | null {
|
get(): Location | null {
|
||||||
return (
|
return (
|
||||||
store.state.locations.locationPicked ||
|
store.state.locations.locationPicked ||
|
||||||
store.state.locations.currentLocation
|
store.state.locations.currentLocation
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
set(newLocation: Location | null): void {
|
set(newLocation: Location | null): void {
|
||||||
store.commit("locations/setLocationPicked", newLocation, {
|
store.commit("locations/setLocationPicked", newLocation, {
|
||||||
root: true,
|
root: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -393,116 +325,116 @@ const sources = computed<EventSourceInput[]>(() => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const calendarOptions = computed((): CalendarOptions => {
|
const calendarOptions = computed((): CalendarOptions => {
|
||||||
return {
|
return {
|
||||||
...baseOptions.value,
|
...baseOptions.value,
|
||||||
weekends: showWeekends.value,
|
weekends: showWeekends.value,
|
||||||
slotDuration: slotDuration.value,
|
slotDuration: slotDuration.value,
|
||||||
events: ranges.value,
|
events: ranges.value,
|
||||||
slotMinTime: slotMinTime.value,
|
slotMinTime: slotMinTime.value,
|
||||||
slotMaxTime: slotMaxTime.value,
|
slotMaxTime: slotMaxTime.value,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* launched when the calendar range date change
|
* launched when the calendar range date change
|
||||||
*/
|
*/
|
||||||
function onDatesSet(event: DatesSetArg): void {
|
function onDatesSet(event: DatesSetArg): void {
|
||||||
store.dispatch("fullCalendar/setCurrentDatesView", {
|
store.dispatch("fullCalendar/setCurrentDatesView", {
|
||||||
start: event.start,
|
start: event.start,
|
||||||
end: event.end,
|
end: event.end,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDateSelect(event: DateSelectArg): void {
|
function onDateSelect(event: DateSelectArg): void {
|
||||||
if (null === pickedLocation.value) {
|
if (null === pickedLocation.value) {
|
||||||
window.alert(
|
window.alert(
|
||||||
"Indiquez une localisation avant de créer une période de disponibilité.",
|
"Indiquez une localisation avant de créer une période de disponibilité.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch("calendarRanges/createRange", {
|
store.dispatch("calendarRanges/createRange", {
|
||||||
start: event.start,
|
start: event.start,
|
||||||
end: event.end,
|
end: event.end,
|
||||||
location: pickedLocation.value,
|
location: pickedLocation.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a calendar range is deleted
|
* When a calendar range is deleted
|
||||||
*/
|
*/
|
||||||
function onClickDelete(event: EventApi): void {
|
function onClickDelete(event: EventApi): void {
|
||||||
if (event.extendedProps.is !== "range") {
|
if (event.extendedProps.is !== "range") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
"calendarRanges/deleteRange",
|
"calendarRanges/deleteRange",
|
||||||
event.extendedProps.calendarRangeId,
|
event.extendedProps.calendarRangeId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
|
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
|
||||||
if (payload.event.extendedProps.is !== "range") {
|
if (payload.event.extendedProps.is !== "range") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch("calendarRanges/patchRangeTime", {
|
store.dispatch("calendarRanges/patchRangeTime", {
|
||||||
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
||||||
start: payload.event.start,
|
start: payload.event.start,
|
||||||
end: payload.event.end,
|
end: payload.event.end,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEventClick(payload: EventClickArg): void {
|
function onEventClick(payload: EventClickArg): void {
|
||||||
// @ts-ignore TS does not recognize the target. But it does exists.
|
// @ts-ignore TS does not recognize the target. But it does exists.
|
||||||
if (payload.jsEvent.target.classList.contains("delete")) {
|
if (payload.jsEvent.target.classList.contains("delete")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (payload.event.extendedProps.is !== "range") {
|
if (payload.event.extendedProps.is !== "range") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editLocation.value?.startEdit(payload.event);
|
editLocation.value?.startEdit(payload.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyDay() {
|
function copyDay() {
|
||||||
if (null === copyFrom.value || null === copyTo.value) {
|
if (null === copyFrom.value || null === copyTo.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
||||||
from: ISOToDate(copyFrom.value),
|
from: ISOToDate(copyFrom.value),
|
||||||
to: ISOToDate(copyTo.value),
|
to: ISOToDate(copyTo.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyWeek() {
|
function copyWeek() {
|
||||||
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
||||||
fromMonday: ISOToDate(copyFromWeek.value),
|
fromMonday: ISOToDate(copyFromWeek.value),
|
||||||
toMonday: ISOToDate(copyToWeek.value),
|
toMonday: ISOToDate(copyToWeek.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
copyFromWeek.value = dateToISO(getMonday(0));
|
copyFromWeek.value = dateToISO(getMonday(0));
|
||||||
copyToWeek.value = dateToISO(getMonday(1));
|
copyToWeek.value = dateToISO(getMonday(1));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#copy-widget {
|
#copy-widget {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
z-index: 9999999999;
|
z-index: 9999999999;
|
||||||
padding: 0.25rem 0 0.25rem;
|
padding: 0.25rem 0 0.25rem;
|
||||||
}
|
}
|
||||||
div.copy-chevron {
|
div.copy-chevron {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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,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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DateTime } from "ChillMainAssets/types";
|
|||||||
import { StoredObject } from "ChillDocStoreAssets/types/index";
|
import { StoredObject } from "ChillDocStoreAssets/types/index";
|
||||||
|
|
||||||
export interface GenericDocMetadata {
|
export interface GenericDocMetadata {
|
||||||
isPresent: boolean;
|
isPresent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,57 +15,57 @@ export interface EmptyMetadata extends GenericDocMetadata {}
|
|||||||
* Minimal Metadata for a GenericDoc with a normalizer
|
* Minimal Metadata for a GenericDoc with a normalizer
|
||||||
*/
|
*/
|
||||||
export interface BaseMetadata extends GenericDocMetadata {
|
export interface BaseMetadata extends GenericDocMetadata {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
||||||
*/
|
*/
|
||||||
export interface GenericDoc {
|
export interface GenericDoc {
|
||||||
type: "doc_store_generic_doc";
|
type: "doc_store_generic_doc";
|
||||||
uniqueKey: string;
|
uniqueKey: string;
|
||||||
key: string;
|
key: string;
|
||||||
identifiers: object;
|
identifiers: object;
|
||||||
context: "person" | "accompanying-period";
|
context: "person" | "accompanying-period";
|
||||||
doc_date: DateTime;
|
doc_date: DateTime;
|
||||||
metadata: GenericDocMetadata;
|
metadata: GenericDocMetadata;
|
||||||
storedObject: StoredObject | null;
|
storedObject: StoredObject | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
||||||
context: "accompanying-period";
|
context: "accompanying-period";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseMetadataWithHtml extends BaseMetadata {
|
interface BaseMetadataWithHtml extends BaseMetadata {
|
||||||
html: string;
|
html: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseDocument
|
export interface GenericDocForAccompanyingCourseDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_course_document";
|
key: "accompanying_course_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseActivityDocument
|
export interface GenericDocForAccompanyingCourseActivityDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_course_activity_document";
|
key: "accompanying_course_activity_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseCalendarDocument
|
export interface GenericDocForAccompanyingCourseCalendarDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_course_calendar_document";
|
key: "accompanying_course_calendar_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCoursePersonDocument
|
export interface GenericDocForAccompanyingCoursePersonDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "person_document";
|
key: "person_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_period_work_evaluation_document";
|
key: "accompanying_period_work_evaluation_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -4,27 +4,26 @@ import { StoredObject, StoredObjectVersion } from "../../types";
|
|||||||
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
|
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
import { useToast } from "vue-toast-notification";
|
import { useToast } from "vue-toast-notification";
|
||||||
import { DOCUMENT_ADD, trans } from "translator";
|
|
||||||
|
|
||||||
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 $toast = useToast();
|
const $toast = useToast();
|
||||||
@@ -34,65 +33,67 @@ const state = reactive({ showModal: false });
|
|||||||
const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
|
const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
|
||||||
|
|
||||||
const buttonState = computed<"add" | "replace">(() => {
|
const buttonState = computed<"add" | "replace">(() => {
|
||||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||||
return "add";
|
return "add";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "replace";
|
return "replace";
|
||||||
});
|
});
|
||||||
|
|
||||||
function onAddDocument({
|
function 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 {
|
||||||
const message =
|
const message =
|
||||||
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
|
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
|
||||||
$toast.success(message);
|
$toast.success(message);
|
||||||
emit("addDocument", { stored_object_version, stored_object, file_name });
|
emit("addDocument", { stored_object_version, stored_object, file_name });
|
||||||
state.showModal = false;
|
state.showModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRemoveDocument(): void {
|
function onRemoveDocument(): void {
|
||||||
emit("removeDocument");
|
emit("removeDocument");
|
||||||
}
|
}
|
||||||
|
|
||||||
function openModal(): void {
|
function openModal(): void {
|
||||||
state.showModal = true;
|
state.showModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal(): void {
|
function closeModal(): void {
|
||||||
state.showModal = false;
|
state.showModal = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-if="buttonState === 'add'"
|
v-if="buttonState === 'add'"
|
||||||
@click="openModal"
|
@click="openModal"
|
||||||
class="btn btn-create"
|
class="btn btn-create"
|
||||||
>
|
>
|
||||||
{{ trans(DOCUMENT_ADD) }}
|
Ajouter un document
|
||||||
</button>
|
</button>
|
||||||
<button v-else @click="openModal" class="btn btn-edit"></button>
|
<button v-else @click="openModal" class="btn btn-edit">
|
||||||
<modal
|
Remplacer le document
|
||||||
v-if="state.showModal"
|
</button>
|
||||||
:modal-dialog-class="modalClasses"
|
<modal
|
||||||
@close="closeModal"
|
v-if="state.showModal"
|
||||||
>
|
:modal-dialog-class="modalClasses"
|
||||||
<template v-slot:body>
|
@close="closeModal"
|
||||||
<drop-file-widget
|
>
|
||||||
:existing-doc="existingDoc"
|
<template v-slot:body>
|
||||||
:allow-remove="allowRemove"
|
<drop-file-widget
|
||||||
@add-document="onAddDocument"
|
:existing-doc="existingDoc"
|
||||||
@remove-document="onRemoveDocument"
|
:allow-remove="allowRemove"
|
||||||
></drop-file-widget>
|
@add-document="onAddDocument"
|
||||||
</template>
|
@remove-document="onRemoveDocument"
|
||||||
</modal>
|
></drop-file-widget>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></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>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectPointInTime,
|
StoredObjectPointInTime,
|
||||||
StoredObjectVersionWithPointInTime,
|
StoredObjectVersionWithPointInTime,
|
||||||
} from "./../../../types";
|
} from "./../../../types";
|
||||||
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
|
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
|
||||||
import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
@@ -12,185 +12,173 @@ import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/Downloa
|
|||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
interface HistoryButtonListItemConfig {
|
interface HistoryButtonListItemConfig {
|
||||||
version: StoredObjectVersionWithPointInTime;
|
version: StoredObjectVersionWithPointInTime;
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<HistoryButtonListItemConfig>();
|
const props = defineProps<HistoryButtonListItemConfig>();
|
||||||
|
|
||||||
const onRestore = ({
|
const onRestore = ({
|
||||||
newVersion,
|
newVersion,
|
||||||
}: {
|
}: {
|
||||||
newVersion: StoredObjectVersionWithPointInTime;
|
newVersion: StoredObjectVersionWithPointInTime;
|
||||||
}) => {
|
}) => {
|
||||||
emit("restoreVersion", { newVersion });
|
emit("restoreVersion", { newVersion });
|
||||||
};
|
};
|
||||||
|
|
||||||
const isKeptBeforeConversion = computed<boolean>(() => {
|
const isKeptBeforeConversion = computed<boolean>(() => {
|
||||||
if ("point-in-times" in props.version) {
|
if ("point-in-times" in props.version) {
|
||||||
return props.version["point-in-times"].reduce(
|
return props.version["point-in-times"].reduce(
|
||||||
(accumulator: boolean, pit: StoredObjectPointInTime) =>
|
(accumulator: boolean, pit: StoredObjectPointInTime) =>
|
||||||
accumulator || "keep-before-conversion" === pit.reason,
|
accumulator || "keep-before-conversion" === pit.reason,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isRestored = computed<boolean>(
|
const isRestored = computed<boolean>(
|
||||||
() => props.version.version > 0 && null !== props.version["from-restored"],
|
() => props.version.version > 0 && null !== props.version["from-restored"],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isDuplicated = computed<boolean>(
|
const isDuplicated = computed<boolean>(
|
||||||
() =>
|
() => props.version.version === 0 && null !== props.version["from-restored"],
|
||||||
props.version.version === 0 && null !== props.version["from-restored"],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const classes = computed<{
|
const classes = computed<{
|
||||||
row: true;
|
row: true;
|
||||||
"row-hover": true;
|
"row-hover": true;
|
||||||
"blinking-1": boolean;
|
"blinking-1": boolean;
|
||||||
"blinking-2": boolean;
|
"blinking-2": boolean;
|
||||||
}>(() => ({
|
}>(() => ({
|
||||||
row: true,
|
row: true,
|
||||||
"row-hover": true,
|
"row-hover": true,
|
||||||
"blinking-1": props.isRestored && 0 === props.version.version % 2,
|
"blinking-1": props.isRestored && 0 === props.version.version % 2,
|
||||||
"blinking-2": props.isRestored && 1 === props.version.version % 2,
|
"blinking-2": props.isRestored && 1 === props.version.version % 2,
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="classes">
|
<div :class="classes">
|
||||||
<div
|
<div
|
||||||
class="col-12 tags"
|
class="col-12 tags"
|
||||||
v-if="
|
v-if="isCurrent || isKeptBeforeConversion || isRestored || isDuplicated"
|
||||||
isCurrent ||
|
>
|
||||||
isKeptBeforeConversion ||
|
<span class="badge bg-success" v-if="isCurrent">Version actuelle</span>
|
||||||
isRestored ||
|
<span class="badge bg-info" v-if="isKeptBeforeConversion"
|
||||||
isDuplicated
|
>Conservée avant conversion dans un autre format</span
|
||||||
"
|
>
|
||||||
>
|
<span class="badge bg-info" v-if="isRestored"
|
||||||
<span class="badge bg-success" v-if="isCurrent"
|
>Restaurée depuis la version
|
||||||
>Version actuelle</span
|
{{ version["from-restored"]?.version + 1 }}</span
|
||||||
>
|
>
|
||||||
<span class="badge bg-info" v-if="isKeptBeforeConversion"
|
<span class="badge bg-info" v-if="isDuplicated"
|
||||||
>Conservée avant conversion dans un autre format</span
|
>Dupliqué depuis un autre document</span
|
||||||
>
|
>
|
||||||
<span class="badge bg-info" v-if="isRestored"
|
|
||||||
>Restaurée depuis la version
|
|
||||||
{{ version["from-restored"]?.version + 1 }}</span
|
|
||||||
>
|
|
||||||
<span class="badge bg-info" v-if="isDuplicated"
|
|
||||||
>Dupliqué depuis un autre document</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<file-icon :type="version.type"></file-icon>
|
|
||||||
<span
|
|
||||||
><strong> #{{ version.version + 1 }} </strong></span
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
v-if="version.createdBy !== null && version.createdAt !== null"
|
|
||||||
><strong v-if="version.version == 0">créé par</strong
|
|
||||||
><strong v-else>modifié par</strong>
|
|
||||||
<span class="badge-user"
|
|
||||||
><UserRenderBoxBadge
|
|
||||||
:user="version.createdBy"
|
|
||||||
></UserRenderBoxBadge
|
|
||||||
></span>
|
|
||||||
<strong>à</strong>
|
|
||||||
{{
|
|
||||||
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
|
||||||
}}</template
|
|
||||||
><template
|
|
||||||
v-if="version.createdBy === null && version.createdAt !== null"
|
|
||||||
><strong v-if="version.version == 0">Créé le</strong
|
|
||||||
><strong v-else>modifié le</strong>
|
|
||||||
{{
|
|
||||||
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
|
||||||
}}</template
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<ul class="record_actions small slim on-version-actions">
|
|
||||||
<li v-if="canEdit && !isCurrent">
|
|
||||||
<restore-version-button
|
|
||||||
:stored-object-version="props.version"
|
|
||||||
@restore-version="onRestore"
|
|
||||||
></restore-version-button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<download-button
|
|
||||||
:stored-object="storedObject"
|
|
||||||
:at-version="version"
|
|
||||||
:classes="{
|
|
||||||
btn: true,
|
|
||||||
'btn-outline-primary': true,
|
|
||||||
'btn-sm': true,
|
|
||||||
}"
|
|
||||||
:display-action-string-in-button="false"
|
|
||||||
></download-button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<file-icon :type="version.type"></file-icon>
|
||||||
|
<span
|
||||||
|
><strong> #{{ version.version + 1 }} </strong></span
|
||||||
|
>
|
||||||
|
<template v-if="version.createdBy !== null && version.createdAt !== null"
|
||||||
|
><strong v-if="version.version == 0">créé par</strong
|
||||||
|
><strong v-else>modifié par</strong>
|
||||||
|
<span class="badge-user"
|
||||||
|
><UserRenderBoxBadge :user="version.createdBy"></UserRenderBoxBadge
|
||||||
|
></span>
|
||||||
|
<strong>à</strong>
|
||||||
|
{{
|
||||||
|
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
||||||
|
}}</template
|
||||||
|
><template v-if="version.createdBy === null && version.createdAt !== null"
|
||||||
|
><strong v-if="version.version == 0">Créé le</strong
|
||||||
|
><strong v-else>modifié le</strong>
|
||||||
|
{{
|
||||||
|
$d(ISOToDatetime(version.createdAt.datetime8601), "long")
|
||||||
|
}}</template
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<ul class="record_actions small slim on-version-actions">
|
||||||
|
<li v-if="canEdit && !isCurrent">
|
||||||
|
<restore-version-button
|
||||||
|
:stored-object-version="props.version"
|
||||||
|
@restore-version="onRestore"
|
||||||
|
></restore-version-button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<download-button
|
||||||
|
:stored-object="storedObject"
|
||||||
|
:at-version="version"
|
||||||
|
:classes="{
|
||||||
|
btn: true,
|
||||||
|
'btn-outline-primary': true,
|
||||||
|
'btn-sm': true,
|
||||||
|
}"
|
||||||
|
:display-action-string-in-button="false"
|
||||||
|
></download-button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
div.tags {
|
div.tags {
|
||||||
span.badge:not(:last-child) {
|
span.badge:not(:last-child) {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// to make the animation restart, we have the same animation twice,
|
// to make the animation restart, we have the same animation twice,
|
||||||
// and alternate between both
|
// and alternate between both
|
||||||
.blinking-1 {
|
.blinking-1 {
|
||||||
animation-name: backgroundColorPalette-1;
|
animation-name: backgroundColorPalette-1;
|
||||||
animation-duration: 8s;
|
animation-duration: 8s;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
animation-direction: normal;
|
animation-direction: normal;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
@keyframes backgroundColorPalette-1 {
|
@keyframes backgroundColorPalette-1 {
|
||||||
0% {
|
0% {
|
||||||
background: var(--bs-chill-green-dark);
|
background: var(--bs-chill-green-dark);
|
||||||
}
|
}
|
||||||
25% {
|
25% {
|
||||||
background: var(--bs-chill-green);
|
background: var(--bs-chill-green);
|
||||||
}
|
}
|
||||||
65% {
|
65% {
|
||||||
background: var(--bs-chill-beige);
|
background: var(--bs-chill-beige);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background: unset;
|
background: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.blinking-2 {
|
.blinking-2 {
|
||||||
animation-name: backgroundColorPalette-2;
|
animation-name: backgroundColorPalette-2;
|
||||||
animation-duration: 8s;
|
animation-duration: 8s;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
animation-direction: normal;
|
animation-direction: normal;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
@keyframes backgroundColorPalette-2 {
|
@keyframes backgroundColorPalette-2 {
|
||||||
0% {
|
0% {
|
||||||
background: var(--bs-chill-green-dark);
|
background: var(--bs-chill-green-dark);
|
||||||
}
|
}
|
||||||
25% {
|
25% {
|
||||||
background: var(--bs-chill-green);
|
background: var(--bs-chill-green);
|
||||||
}
|
}
|
||||||
65% {
|
65% {
|
||||||
background: var(--bs-chill-beige);
|
background: var(--bs-chill-beige);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background: unset;
|
background: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
||||||
|
|||||||
@@ -1,235 +1,230 @@
|
|||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectStatus,
|
StoredObjectStatus,
|
||||||
StoredObjectStatusChange,
|
StoredObjectStatusChange,
|
||||||
StoredObjectVersion,
|
StoredObjectVersion,
|
||||||
} from "../../types";
|
} from "../../types";
|
||||||
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
|
|
||||||
const MIMES_EDIT = new Set([
|
const MIMES_EDIT = new Set([
|
||||||
"application/vnd.ms-powerpoint",
|
"application/vnd.ms-powerpoint",
|
||||||
"application/vnd.ms-excel",
|
"application/vnd.ms-excel",
|
||||||
"application/vnd.oasis.opendocument.text",
|
"application/vnd.oasis.opendocument.text",
|
||||||
"application/vnd.oasis.opendocument.text-flat-xml",
|
"application/vnd.oasis.opendocument.text-flat-xml",
|
||||||
"application/vnd.oasis.opendocument.spreadsheet",
|
"application/vnd.oasis.opendocument.spreadsheet",
|
||||||
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
|
"application/vnd.oasis.opendocument.spreadsheet-flat-xml",
|
||||||
"application/vnd.oasis.opendocument.presentation",
|
"application/vnd.oasis.opendocument.presentation",
|
||||||
"application/vnd.oasis.opendocument.presentation-flat-xml",
|
"application/vnd.oasis.opendocument.presentation-flat-xml",
|
||||||
"application/vnd.oasis.opendocument.graphics",
|
"application/vnd.oasis.opendocument.graphics",
|
||||||
"application/vnd.oasis.opendocument.graphics-flat-xml",
|
"application/vnd.oasis.opendocument.graphics-flat-xml",
|
||||||
"application/vnd.oasis.opendocument.chart",
|
"application/vnd.oasis.opendocument.chart",
|
||||||
"application/msword",
|
"application/msword",
|
||||||
"application/vnd.ms-excel",
|
"application/vnd.ms-excel",
|
||||||
"application/vnd.ms-powerpoint",
|
"application/vnd.ms-powerpoint",
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
"application/vnd.ms-word.document.macroEnabled.12",
|
"application/vnd.ms-word.document.macroEnabled.12",
|
||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||||
"application/vnd.ms-excel.sheet.macroEnabled.12",
|
"application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||||
"application/x-dif-document",
|
"application/x-dif-document",
|
||||||
"text/spreadsheet",
|
"text/spreadsheet",
|
||||||
"text/csv",
|
"text/csv",
|
||||||
"application/x-dbase",
|
"application/x-dbase",
|
||||||
"text/rtf",
|
"text/rtf",
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const MIMES_VIEW = new Set([
|
const MIMES_VIEW = new Set([
|
||||||
...MIMES_EDIT,
|
...MIMES_EDIT,
|
||||||
[
|
[
|
||||||
"image/svg+xml",
|
"image/svg+xml",
|
||||||
"application/vnd.sun.xml.writer",
|
"application/vnd.sun.xml.writer",
|
||||||
"application/vnd.sun.xml.calc",
|
"application/vnd.sun.xml.calc",
|
||||||
"application/vnd.sun.xml.impress",
|
"application/vnd.sun.xml.impress",
|
||||||
"application/vnd.sun.xml.draw",
|
"application/vnd.sun.xml.draw",
|
||||||
"application/vnd.sun.xml.writer.global",
|
"application/vnd.sun.xml.writer.global",
|
||||||
"application/vnd.sun.xml.writer.template",
|
"application/vnd.sun.xml.writer.template",
|
||||||
"application/vnd.sun.xml.calc.template",
|
"application/vnd.sun.xml.calc.template",
|
||||||
"application/vnd.sun.xml.impress.template",
|
"application/vnd.sun.xml.impress.template",
|
||||||
"application/vnd.sun.xml.draw.template",
|
"application/vnd.sun.xml.draw.template",
|
||||||
"application/vnd.oasis.opendocument.text-master",
|
"application/vnd.oasis.opendocument.text-master",
|
||||||
"application/vnd.oasis.opendocument.text-template",
|
"application/vnd.oasis.opendocument.text-template",
|
||||||
"application/vnd.oasis.opendocument.text-master-template",
|
"application/vnd.oasis.opendocument.text-master-template",
|
||||||
"application/vnd.oasis.opendocument.spreadsheet-template",
|
"application/vnd.oasis.opendocument.spreadsheet-template",
|
||||||
"application/vnd.oasis.opendocument.presentation-template",
|
"application/vnd.oasis.opendocument.presentation-template",
|
||||||
"application/vnd.oasis.opendocument.graphics-template",
|
"application/vnd.oasis.opendocument.graphics-template",
|
||||||
"application/vnd.ms-word.template.macroEnabled.12",
|
"application/vnd.ms-word.template.macroEnabled.12",
|
||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||||
"application/vnd.ms-excel.template.macroEnabled.12",
|
"application/vnd.ms-excel.template.macroEnabled.12",
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||||
"application/vnd.ms-powerpoint.template.macroEnabled.12",
|
"application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||||
"application/vnd.wordperfect",
|
"application/vnd.wordperfect",
|
||||||
"application/x-aportisdoc",
|
"application/x-aportisdoc",
|
||||||
"application/x-hwp",
|
"application/x-hwp",
|
||||||
"application/vnd.ms-works",
|
"application/vnd.ms-works",
|
||||||
"application/x-mswrite",
|
"application/x-mswrite",
|
||||||
"application/vnd.lotus-1-2-3",
|
"application/vnd.lotus-1-2-3",
|
||||||
"image/cgm",
|
"image/cgm",
|
||||||
"image/vnd.dxf",
|
"image/vnd.dxf",
|
||||||
"image/x-emf",
|
"image/x-emf",
|
||||||
"image/x-wmf",
|
"image/x-wmf",
|
||||||
"application/coreldraw",
|
"application/coreldraw",
|
||||||
"application/vnd.visio2013",
|
"application/vnd.visio2013",
|
||||||
"application/vnd.visio",
|
"application/vnd.visio",
|
||||||
"application/vnd.ms-visio.drawing",
|
"application/vnd.ms-visio.drawing",
|
||||||
"application/x-mspublisher",
|
"application/x-mspublisher",
|
||||||
"application/x-sony-bbeb",
|
"application/x-sony-bbeb",
|
||||||
"application/x-gnumeric",
|
"application/x-gnumeric",
|
||||||
"application/macwriteii",
|
"application/macwriteii",
|
||||||
"application/x-iwork-numbers-sffnumbers",
|
"application/x-iwork-numbers-sffnumbers",
|
||||||
"application/vnd.oasis.opendocument.text-web",
|
"application/vnd.oasis.opendocument.text-web",
|
||||||
"application/x-pagemaker",
|
"application/x-pagemaker",
|
||||||
"application/x-fictionbook+xml",
|
"application/x-fictionbook+xml",
|
||||||
"application/clarisworks",
|
"application/clarisworks",
|
||||||
"image/x-wpg",
|
"image/x-wpg",
|
||||||
"application/x-iwork-pages-sffpages",
|
"application/x-iwork-pages-sffpages",
|
||||||
"application/x-iwork-keynote-sffkey",
|
"application/x-iwork-keynote-sffkey",
|
||||||
"application/x-abiword",
|
"application/x-abiword",
|
||||||
"image/x-freehand",
|
"image/x-freehand",
|
||||||
"application/vnd.sun.xml.chart",
|
"application/vnd.sun.xml.chart",
|
||||||
"application/x-t602",
|
"application/x-t602",
|
||||||
"image/bmp",
|
"image/bmp",
|
||||||
"image/png",
|
"image/png",
|
||||||
"image/gif",
|
"image/gif",
|
||||||
"image/tiff",
|
"image/tiff",
|
||||||
"image/jpg",
|
"image/jpg",
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"application/pdf",
|
"application/pdf",
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export interface SignedUrlGet {
|
export interface SignedUrlGet {
|
||||||
method: "GET" | "HEAD";
|
method: "GET" | "HEAD";
|
||||||
url: string;
|
url: string;
|
||||||
expires: number;
|
expires: number;
|
||||||
object_name: string;
|
object_name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_extension_editable(mimeType: string): boolean {
|
function is_extension_editable(mimeType: string): boolean {
|
||||||
return MIMES_EDIT.has(mimeType);
|
return MIMES_EDIT.has(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_extension_viewable(mimeType: string): boolean {
|
function is_extension_viewable(mimeType: string): boolean {
|
||||||
return MIMES_VIEW.has(mimeType);
|
return MIMES_VIEW.has(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function build_convert_link(uuid: string) {
|
function build_convert_link(uuid: string) {
|
||||||
return `/chill/wopi/convert/${uuid}`;
|
return `/chill/wopi/convert/${uuid}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function build_download_info_link(
|
function build_download_info_link(
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
atVersion: null | StoredObjectVersion,
|
atVersion: null | StoredObjectVersion,
|
||||||
): string {
|
): string {
|
||||||
const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;
|
const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;
|
||||||
|
|
||||||
if (null !== atVersion) {
|
if (null !== atVersion) {
|
||||||
const params = new URLSearchParams({ version: atVersion.filename });
|
const params = new URLSearchParams({ version: atVersion.filename });
|
||||||
|
|
||||||
return url + "?" + params.toString();
|
return url + "?" + params.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function download_info_link(
|
async function download_info_link(
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
atVersion: null | StoredObjectVersion,
|
atVersion: null | StoredObjectVersion,
|
||||||
): Promise<SignedUrlGet> {
|
): Promise<SignedUrlGet> {
|
||||||
return makeFetch("GET", build_download_info_link(storedObject, atVersion));
|
return makeFetch("GET", build_download_info_link(storedObject, atVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
function build_wopi_editor_link(uuid: string, returnPath?: string) {
|
function build_wopi_editor_link(uuid: string, returnPath?: string) {
|
||||||
if (returnPath === undefined) {
|
if (returnPath === undefined) {
|
||||||
returnPath =
|
returnPath =
|
||||||
window.location.pathname +
|
window.location.pathname + window.location.search + window.location.hash;
|
||||||
window.location.search +
|
}
|
||||||
window.location.hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath)
|
`/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function download_doc(url: string): Promise<Blob> {
|
function download_doc(url: string): Promise<Blob> {
|
||||||
return window.fetch(url).then((r) => {
|
return window.fetch(url).then((r) => {
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
return r.blob();
|
return r.blob();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Could not download document");
|
throw new Error("Could not download document");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function download_and_decrypt_doc(
|
async function download_and_decrypt_doc(
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
atVersion: null | StoredObjectVersion,
|
atVersion: null | StoredObjectVersion,
|
||||||
): Promise<Blob> {
|
): Promise<Blob> {
|
||||||
const algo = "AES-CBC";
|
const algo = "AES-CBC";
|
||||||
|
|
||||||
const atVersionToDownload = atVersion ?? storedObject.currentVersion;
|
const atVersionToDownload = atVersion ?? storedObject.currentVersion;
|
||||||
|
|
||||||
if (null === atVersionToDownload) {
|
if (null === atVersionToDownload) {
|
||||||
throw new Error("no version associated to stored object");
|
throw new Error("no version associated to stored object");
|
||||||
}
|
}
|
||||||
|
|
||||||
// sometimes, the downloadInfo may be embedded into the storedObject
|
// sometimes, the downloadInfo may be embedded into the storedObject
|
||||||
console.log("storedObject", storedObject);
|
console.log("storedObject", storedObject);
|
||||||
let downloadInfo;
|
let downloadInfo;
|
||||||
if (
|
if (
|
||||||
typeof storedObject._links !== "undefined" &&
|
typeof storedObject._links !== "undefined" &&
|
||||||
typeof storedObject._links.downloadLink !== "undefined"
|
typeof storedObject._links.downloadLink !== "undefined"
|
||||||
) {
|
) {
|
||||||
downloadInfo = storedObject._links.downloadLink;
|
downloadInfo = storedObject._links.downloadLink;
|
||||||
} else {
|
} else {
|
||||||
downloadInfo = await download_info_link(
|
downloadInfo = await download_info_link(storedObject, atVersionToDownload);
|
||||||
storedObject,
|
}
|
||||||
atVersionToDownload,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawResponse = await window.fetch(downloadInfo.url);
|
const rawResponse = await window.fetch(downloadInfo.url);
|
||||||
|
|
||||||
if (!rawResponse.ok) {
|
if (!rawResponse.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"error while downloading raw file " +
|
"error while downloading raw file " +
|
||||||
rawResponse.status +
|
rawResponse.status +
|
||||||
" " +
|
" " +
|
||||||
rawResponse.statusText,
|
rawResponse.statusText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atVersionToDownload.iv.length === 0) {
|
if (atVersionToDownload.iv.length === 0) {
|
||||||
return rawResponse.blob();
|
return rawResponse.blob();
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawBuffer = await rawResponse.arrayBuffer();
|
const rawBuffer = await rawResponse.arrayBuffer();
|
||||||
try {
|
try {
|
||||||
const key = await window.crypto.subtle.importKey(
|
const key = await window.crypto.subtle.importKey(
|
||||||
"jwk",
|
"jwk",
|
||||||
atVersionToDownload.keyInfos,
|
atVersionToDownload.keyInfos,
|
||||||
{ name: algo },
|
{ name: algo },
|
||||||
false,
|
false,
|
||||||
["decrypt"],
|
["decrypt"],
|
||||||
);
|
);
|
||||||
const iv = Uint8Array.from(atVersionToDownload.iv);
|
const iv = Uint8Array.from(atVersionToDownload.iv);
|
||||||
const decrypted = await window.crypto.subtle.decrypt(
|
const decrypted = await window.crypto.subtle.decrypt(
|
||||||
{ name: algo, iv: iv },
|
{ name: algo, iv: iv },
|
||||||
key,
|
key,
|
||||||
rawBuffer,
|
rawBuffer,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.resolve(new Blob([decrypted]));
|
return Promise.resolve(new Blob([decrypted]));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("encounter error while keys and decrypt operations");
|
console.error("encounter error while keys and decrypt operations");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -239,48 +234,45 @@ async function download_and_decrypt_doc(
|
|||||||
* storage.
|
* storage.
|
||||||
*/
|
*/
|
||||||
async function download_doc_as_pdf(storedObject: StoredObject): Promise<Blob> {
|
async function download_doc_as_pdf(storedObject: StoredObject): Promise<Blob> {
|
||||||
if (null === storedObject.currentVersion) {
|
if (null === storedObject.currentVersion) {
|
||||||
throw new Error("the stored object does not count any version");
|
throw new Error("the stored object does not count any version");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storedObject.currentVersion?.type === "application/pdf") {
|
if (storedObject.currentVersion?.type === "application/pdf") {
|
||||||
return download_and_decrypt_doc(
|
return download_and_decrypt_doc(storedObject, storedObject.currentVersion);
|
||||||
storedObject,
|
}
|
||||||
storedObject.currentVersion,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const convertLink = build_convert_link(storedObject.uuid);
|
const convertLink = build_convert_link(storedObject.uuid);
|
||||||
const response = await fetch(convertLink);
|
const response = await fetch(convertLink);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Could not convert the document: " + response.status);
|
throw new Error("Could not convert the document: " + response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.blob();
|
return response.blob();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function is_object_ready(
|
async function is_object_ready(
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
): Promise<StoredObjectStatusChange> {
|
): Promise<StoredObjectStatusChange> {
|
||||||
const new_status_response = await window.fetch(
|
const new_status_response = await window.fetch(
|
||||||
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`,
|
`/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!new_status_response.ok) {
|
if (!new_status_response.ok) {
|
||||||
throw new Error("could not fetch the new status");
|
throw new Error("could not fetch the new status");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await new_status_response.json();
|
return await new_status_response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
build_convert_link,
|
build_convert_link,
|
||||||
build_wopi_editor_link,
|
build_wopi_editor_link,
|
||||||
download_and_decrypt_doc,
|
download_and_decrypt_doc,
|
||||||
download_doc,
|
download_doc,
|
||||||
download_doc_as_pdf,
|
download_doc_as_pdf,
|
||||||
is_extension_editable,
|
is_extension_editable,
|
||||||
is_extension_viewable,
|
is_extension_viewable,
|
||||||
is_object_ready,
|
is_object_ready,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,11 +43,17 @@ class StoredObjectVersionNormalizer implements NormalizerInterface, NormalizerAw
|
|||||||
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]),
|
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
|
$normalizationGroups = $context[AbstractNormalizer::GROUPS] ?? [];
|
||||||
|
|
||||||
|
if (is_string($normalizationGroups)) {
|
||||||
|
$normalizationGroups = [$normalizationGroups];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $normalizationGroups, true)) {
|
||||||
$data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context);
|
$data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array(self::WITH_RESTORED_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
|
if (in_array(self::WITH_RESTORED_CONTEXT, $normalizationGroups, true)) {
|
||||||
$data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]);
|
$data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ See the document: Voir le document
|
|||||||
|
|
||||||
document:
|
document:
|
||||||
Any title: Aucun titre
|
Any title: Aucun titre
|
||||||
replace: Remplacer
|
|
||||||
Add: Ajouter un document
|
|
||||||
|
|
||||||
generic_doc:
|
generic_doc:
|
||||||
filter:
|
filter:
|
||||||
|
|||||||
@@ -118,7 +118,7 @@
|
|||||||
|
|
||||||
{{ entity.notes|chill_print_or_message("Aucune note", 'blockquote') }}
|
{{ entity.notes|chill_print_or_message("Aucune note", 'blockquote') }}
|
||||||
{% endblock crud_content_view_details %}
|
{% endblock crud_content_view_details %}
|
||||||
{% block content_view_actions_duplicate_link %}{% endblock %}
|
|
||||||
{% block content_view_actions_back %}
|
{% block content_view_actions_back %}
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
<a class="btn btn-cancel" href="{{ chill_return_path_or('chill_job_report_index', { 'person': entity.person.id }) }}">
|
<a class="btn btn-cancel" href="{{ chill_return_path_or('chill_job_report_index', { 'person': entity.person.id }) }}">
|
||||||
|
|||||||
@@ -46,7 +46,6 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{% endblock crud_content_view_details %}
|
{% endblock crud_content_view_details %}
|
||||||
{% block content_view_actions_duplicate_link %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content_view_actions_back %}
|
{% block content_view_actions_back %}
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
|
|||||||
@@ -206,8 +206,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_view_actions_duplicate_link %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content_view_actions_after %}
|
{% block content_view_actions_after %}
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-misc" href="{{ chill_return_path_or('chill_crud_immersion_bilan', { 'id': entity.id, 'person_id': entity.person.id }) }}">
|
<a class="btn btn-misc" href="{{ chill_return_path_or('chill_crud_immersion_bilan', { 'id': entity.id, 'person_id': entity.person.id }) }}">
|
||||||
|
|||||||
@@ -94,7 +94,6 @@
|
|||||||
|
|
||||||
|
|
||||||
{% endblock crud_content_view_details %}
|
{% endblock crud_content_view_details %}
|
||||||
{% block content_view_actions_duplicate_link %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content_view_actions_back %}
|
{% block content_view_actions_back %}
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\MainBundle\Action\User\UpdateProfile;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\User;
|
|
||||||
use Chill\MainBundle\Notification\NotificationFlagManager;
|
|
||||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
|
||||||
use libphonenumber\PhoneNumber;
|
|
||||||
|
|
||||||
final class UpdateProfileCommand
|
|
||||||
{
|
|
||||||
public array $notificationFlags = [];
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
#[PhonenumberConstraint]
|
|
||||||
public ?PhoneNumber $phonenumber,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public static function create(User $user, NotificationFlagManager $flagManager): self
|
|
||||||
{
|
|
||||||
$updateProfileCommand = new self($user->getPhonenumber());
|
|
||||||
|
|
||||||
foreach ($flagManager->getAllNotificationFlagProviders() as $provider) {
|
|
||||||
$updateProfileCommand->setNotificationFlag(
|
|
||||||
$provider->getFlag(),
|
|
||||||
User::NOTIF_FLAG_IMMEDIATE_EMAIL,
|
|
||||||
$user->isNotificationSendImmediately($provider->getFlag())
|
|
||||||
);
|
|
||||||
$updateProfileCommand->setNotificationFlag(
|
|
||||||
$provider->getFlag(),
|
|
||||||
User::NOTIF_FLAG_DAILY_DIGEST,
|
|
||||||
$user->isNotificationDailyDigest($provider->getFlag())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $updateProfileCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param User::NOTIF_FLAG_IMMEDIATE_EMAIL|User::NOTIF_FLAG_DAILY_DIGEST $kind
|
|
||||||
*/
|
|
||||||
private function setNotificationFlag(string $type, string $kind, bool $value): void
|
|
||||||
{
|
|
||||||
if (!array_key_exists($type, $this->notificationFlags)) {
|
|
||||||
$this->notificationFlags[$type] = ['immediate_email' => true, 'daily_digest' => false];
|
|
||||||
}
|
|
||||||
|
|
||||||
$k = match ($kind) {
|
|
||||||
User::NOTIF_FLAG_IMMEDIATE_EMAIL => 'immediate_email',
|
|
||||||
User::NOTIF_FLAG_DAILY_DIGEST => 'daily_digest',
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->notificationFlags[$type][$k] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\MainBundle\Action\User\UpdateProfile;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\User;
|
|
||||||
|
|
||||||
final readonly class UpdateProfileCommandHandler
|
|
||||||
{
|
|
||||||
public function updateProfile(User $user, UpdateProfileCommand $command): void
|
|
||||||
{
|
|
||||||
$user->setPhonenumber($command->phonenumber);
|
|
||||||
|
|
||||||
foreach ($command->notificationFlags as $flag => $values) {
|
|
||||||
$user->setNotificationImmediately($flag, $values['immediate_email']);
|
|
||||||
$user->setNotificationDailyDigest($flag, $values['daily_digest']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Component\Translation\MessageCatalogue;
|
||||||
|
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
|
||||||
|
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class OverrideTranslationCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly TranslationReaderInterface $reader,
|
||||||
|
private readonly TranslationWriterInterface $writer,
|
||||||
|
) {
|
||||||
|
$this->setName('chill:main:override_translation');
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setDescription('Generate a translation catalogue with translation remplacements based on replacements provided in a YAML file.')
|
||||||
|
->addArgument('locale', InputArgument::REQUIRED, 'The locale to process (e.g. fr, en).')
|
||||||
|
->addArgument('overrides', InputArgument::REQUIRED, 'Path to the overrides YAML file (list of {from, to}).');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$locale = (string) $input->getArgument('locale');
|
||||||
|
$overridesPath = (string) $input->getArgument('overrides');
|
||||||
|
|
||||||
|
$catalogue = $this->loadCatalogue($locale);
|
||||||
|
$overrides = $this->loadOverrides($overridesPath);
|
||||||
|
|
||||||
|
|
||||||
|
$toOverrideCatalogue = new MessageCatalogue($locale);
|
||||||
|
foreach ($catalogue->getDomains() as $domain) {
|
||||||
|
// hack: we have to replace the suffix ".intl-icu" by "+intl-ic"
|
||||||
|
$domain = str_replace('.intl-icu', '+intl-icu', $domain);
|
||||||
|
foreach ($catalogue->all($domain) as $key => $translation) {
|
||||||
|
foreach ($overrides as $changes) {
|
||||||
|
$from = $changes['from'];
|
||||||
|
$to = $changes['to'];
|
||||||
|
|
||||||
|
if (is_string($translation) && str_contains($translation, $from)) {
|
||||||
|
$newTranslation = strtr($translation, [$from => $to]);
|
||||||
|
$toOverrideCatalogue->set($key, $newTranslation, $domain);
|
||||||
|
$translation = $newTranslation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var KernelInterface $kernel */
|
||||||
|
/* @phpstan-ignore-next-line */
|
||||||
|
$kernel = $this->getApplication()->getKernel();
|
||||||
|
$outputDir = rtrim($kernel->getProjectDir(), '/').'/translations';
|
||||||
|
if (!is_dir($outputDir)) {
|
||||||
|
@mkdir($outputDir, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer expects the 'path' option to be a directory; it will create the proper file name
|
||||||
|
$this->writer->write($toOverrideCatalogue, 'yaml', ['path' => $outputDir]);
|
||||||
|
|
||||||
|
$output->writeln(sprintf('Override catalogue written to %s (domain: messages, locale: %s).', $outputDir, $locale));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{from: string, to: string}>
|
||||||
|
*/
|
||||||
|
private function loadOverrides(string $path): array
|
||||||
|
{
|
||||||
|
return Yaml::parseFile($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadCatalogue(string $locale): MessageCatalogue
|
||||||
|
{
|
||||||
|
/** @var KernelInterface $kernel */
|
||||||
|
/* @phpstan-ignore-next-line */
|
||||||
|
$kernel = $this->getApplication()->getKernel();
|
||||||
|
|
||||||
|
// collect path for translations
|
||||||
|
$transPaths = [];
|
||||||
|
foreach ($kernel->getBundles() as $bundle) {
|
||||||
|
$bundleDir = $bundle->getPath();
|
||||||
|
$transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations';
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentCatalogue = new MessageCatalogue($locale);
|
||||||
|
foreach ($transPaths as $path) {
|
||||||
|
if (is_dir($path)) {
|
||||||
|
$this->reader->read($path, $currentCatalogue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $currentCatalogue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,6 @@ class AbsenceController extends AbstractController
|
|||||||
$user = $this->security->getUser();
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
$user->setAbsenceStart(null);
|
$user->setAbsenceStart(null);
|
||||||
$user->setAbsenceEnd(null);
|
|
||||||
$em = $this->managerRegistry->getManager();
|
$em = $this->managerRegistry->getManager();
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Form\UserProfileType;
|
||||||
|
use Chill\MainBundle\Security\ChillSecurity;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
final class UserProfileController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly TranslatorInterface $translator,
|
||||||
|
private readonly ChillSecurity $security,
|
||||||
|
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User profile that allows editing of phonenumber and visualization of certain data.
|
||||||
|
*/
|
||||||
|
#[Route(path: '/{_locale}/main/user/my-profile', name: 'chill_main_user_profile')]
|
||||||
|
public function __invoke(Request $request)
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('ROLE_USER')) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
$editForm = $this->createForm(UserProfileType::class, $user);
|
||||||
|
|
||||||
|
$editForm->get('notificationFlags')->setData($user->getNotificationFlags());
|
||||||
|
|
||||||
|
$editForm->handleRequest($request);
|
||||||
|
|
||||||
|
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||||
|
$notificationFlagsData = $editForm->get('notificationFlags')->getData();
|
||||||
|
$user->setNotificationFlags($notificationFlagsData);
|
||||||
|
|
||||||
|
$em = $this->managerRegistry->getManager();
|
||||||
|
$em->flush();
|
||||||
|
$this->addFlash('success', $this->translator->trans('user.profile.Profile successfully updated!'));
|
||||||
|
|
||||||
|
return $this->redirectToRoute('chill_main_user_profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@ChillMain/User/profile.html.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'form' => $editForm->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\MainBundle\Controller;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand;
|
|
||||||
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommandHandler;
|
|
||||||
use Chill\MainBundle\Entity\User;
|
|
||||||
use Chill\MainBundle\Form\UpdateProfileType;
|
|
||||||
use Chill\MainBundle\Notification\NotificationFlagManager;
|
|
||||||
use Chill\MainBundle\Security\ChillSecurity;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Component\Form\FormFactoryInterface;
|
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
|
||||||
use Twig\Environment;
|
|
||||||
|
|
||||||
final readonly class UserUpdateProfileController
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private TranslatorInterface $translator,
|
|
||||||
private ChillSecurity $security,
|
|
||||||
private EntityManagerInterface $entityManager,
|
|
||||||
private NotificationFlagManager $notificationFlagManager,
|
|
||||||
private FormFactoryInterface $formFactory,
|
|
||||||
private UrlGeneratorInterface $urlGenerator,
|
|
||||||
private Environment $twig,
|
|
||||||
private UpdateProfileCommandHandler $updateProfileCommandHandler,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User profile that allows editing of phonenumber and visualization of certain data.
|
|
||||||
*/
|
|
||||||
#[Route(path: '/{_locale}/main/user/my-profile', name: 'chill_main_user_profile')]
|
|
||||||
public function __invoke(Request $request, Session $session)
|
|
||||||
{
|
|
||||||
if (!$this->security->isGranted('ROLE_USER')) {
|
|
||||||
throw new AccessDeniedHttpException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->security->getUser();
|
|
||||||
|
|
||||||
$command = UpdateProfileCommand::create($user, $this->notificationFlagManager);
|
|
||||||
$editForm = $this->formFactory->create(UpdateProfileType::class, $command);
|
|
||||||
|
|
||||||
$editForm->handleRequest($request);
|
|
||||||
|
|
||||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
|
||||||
$this->updateProfileCommandHandler->updateProfile($user, $command);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
$session->getFlashBag()->add('success', $this->translator->trans('user.profile.Profile successfully updated!'));
|
|
||||||
|
|
||||||
return new RedirectResponse($this->urlGenerator->generate('chill_main_user_profile'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response($this->twig->render('@ChillMain/User/profile.html.twig', [
|
|
||||||
'user' => $user,
|
|
||||||
'form' => $editForm->createView(),
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
|||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User.
|
* User.
|
||||||
@@ -46,8 +45,6 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||||
private ?\DateTimeImmutable $absenceStart = null;
|
private ?\DateTimeImmutable $absenceStart = null;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
|
|
||||||
private ?\DateTimeImmutable $absenceEnd = null;
|
|
||||||
/**
|
/**
|
||||||
* Array where SAML attributes's data are stored.
|
* Array where SAML attributes's data are stored.
|
||||||
*/
|
*/
|
||||||
@@ -160,11 +157,6 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
return $this->absenceStart;
|
return $this->absenceStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAbsenceEnd(): ?\DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->absenceEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get attributes.
|
* Get attributes.
|
||||||
*
|
*
|
||||||
@@ -344,13 +336,7 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
|
|
||||||
public function isAbsent(): bool
|
public function isAbsent(): bool
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable('now');
|
return null !== $this->getAbsenceStart() && $this->getAbsenceStart() <= new \DateTimeImmutable('now');
|
||||||
$absenceStart = $this->getAbsenceStart();
|
|
||||||
$absenceEnd = $this->getAbsenceEnd();
|
|
||||||
|
|
||||||
return null !== $absenceStart
|
|
||||||
&& $absenceStart <= $now
|
|
||||||
&& (null === $absenceEnd || $now <= $absenceEnd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -424,11 +410,6 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
$this->absenceStart = $absenceStart;
|
$this->absenceStart = $absenceStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAbsenceEnd(?\DateTimeImmutable $absenceEnd): void
|
|
||||||
{
|
|
||||||
$this->absenceEnd = $absenceEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAttributeByDomain(string $domain, string $key, $value): self
|
public function setAttributeByDomain(string $domain, string $key, $value): self
|
||||||
{
|
{
|
||||||
$this->attributes[$domain][$key] = $value;
|
$this->attributes[$domain][$key] = $value;
|
||||||
@@ -652,82 +633,46 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getNotificationFlagData(string $flag): array
|
public function getNotificationFlags(): array
|
||||||
{
|
{
|
||||||
return $this->notificationFlags[$flag] ?? [self::NOTIF_FLAG_IMMEDIATE_EMAIL];
|
return $this->notificationFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNotificationFlags(array $notificationFlags)
|
||||||
|
{
|
||||||
|
$this->notificationFlags = $notificationFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNotificationFlagData(string $flag): array
|
||||||
|
{
|
||||||
|
return $this->notificationFlags[$flag] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNotificationFlagData(string $flag, array $data): void
|
||||||
|
{
|
||||||
|
$this->notificationFlags[$flag] = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isNotificationSendImmediately(string $type): bool
|
public function isNotificationSendImmediately(string $type): bool
|
||||||
{
|
{
|
||||||
return $this->isNotificationForElement($type, self::NOTIF_FLAG_IMMEDIATE_EMAIL);
|
if ([] === $this->getNotificationFlagData($type) || in_array(User::NOTIF_FLAG_IMMEDIATE_EMAIL, $this->getNotificationFlagData($type), true)) {
|
||||||
}
|
return true;
|
||||||
|
|
||||||
public function setNotificationImmediately(string $type, bool $active): void
|
|
||||||
{
|
|
||||||
$this->setNotificationFlagElement($type, $active, self::NOTIF_FLAG_IMMEDIATE_EMAIL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNotificationDailyDigest(string $type, bool $active): void
|
|
||||||
{
|
|
||||||
$this->setNotificationFlagElement($type, $active, self::NOTIF_FLAG_DAILY_DIGEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param self::NOTIF_FLAG_IMMEDIATE_EMAIL|self::NOTIF_FLAG_DAILY_DIGEST $kind
|
|
||||||
*/
|
|
||||||
private function setNotificationFlagElement(string $type, bool $active, string $kind): void
|
|
||||||
{
|
|
||||||
$notificationFlags = [...$this->notificationFlags];
|
|
||||||
$changed = false;
|
|
||||||
|
|
||||||
if (!isset($notificationFlags[$type])) {
|
|
||||||
$notificationFlags[$type] = [self::NOTIF_FLAG_IMMEDIATE_EMAIL];
|
|
||||||
$changed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($active) {
|
return false;
|
||||||
if (!in_array($kind, $notificationFlags[$type], true)) {
|
|
||||||
$notificationFlags[$type] = [...$notificationFlags[$type], $kind];
|
|
||||||
$changed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (in_array($kind, $notificationFlags[$type], true)) {
|
|
||||||
$notificationFlags[$type] = array_values(
|
|
||||||
array_filter($notificationFlags[$type], static fn ($k) => $k !== $kind)
|
|
||||||
);
|
|
||||||
$changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($changed) {
|
|
||||||
$this->notificationFlags = [...$notificationFlags];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isNotificationForElement(string $type, string $kind): bool
|
|
||||||
{
|
|
||||||
return in_array($kind, $this->getNotificationFlagData($type), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isNotificationDailyDigest(string $type): bool
|
public function isNotificationDailyDigest(string $type): bool
|
||||||
{
|
{
|
||||||
return $this->isNotificationForElement($type, self::NOTIF_FLAG_DAILY_DIGEST);
|
if (in_array(User::NOTIF_FLAG_DAILY_DIGEST, $this->getNotificationFlagData($type), true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLocale(): string
|
public function getLocale(): string
|
||||||
{
|
{
|
||||||
return 'fr';
|
return 'fr';
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Assert\Callback]
|
|
||||||
public function validateAbsenceDates(ExecutionContextInterface $context): void
|
|
||||||
{
|
|
||||||
if (null !== $this->getAbsenceEnd() && null === $this->getAbsenceStart()) {
|
|
||||||
$context->buildViolation(
|
|
||||||
'user.absence_end_requires_start'
|
|
||||||
)
|
|
||||||
->atPath('absenceEnd')
|
|
||||||
->addViolation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a CSV List for the export.
|
* Create a CSV List for the export.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,14 +23,9 @@ class AbsenceType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('absenceStart', ChillDateType::class, [
|
->add('absenceStart', ChillDateType::class, [
|
||||||
'required' => false,
|
'required' => true,
|
||||||
'input' => 'datetime_immutable',
|
'input' => 'datetime_immutable',
|
||||||
'label' => 'absence.Absence start',
|
'label' => 'absence.Absence start',
|
||||||
])
|
|
||||||
->add('absenceEnd', ChillDateType::class, [
|
|
||||||
'required' => false,
|
|
||||||
'input' => 'datetime_immutable',
|
|
||||||
'label' => 'absence.Absence end',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Form\DataMapper;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Symfony\Component\Form\DataMapperInterface;
|
||||||
|
|
||||||
|
final readonly class NotificationFlagDataMapper implements DataMapperInterface
|
||||||
|
{
|
||||||
|
public function __construct(private array $notificationFlagProviders) {}
|
||||||
|
|
||||||
|
public function mapDataToForms($viewData, $forms): void
|
||||||
|
{
|
||||||
|
if (null === $viewData) {
|
||||||
|
$viewData = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$formsArray = iterator_to_array($forms);
|
||||||
|
|
||||||
|
foreach ($this->notificationFlagProviders as $flagProvider) {
|
||||||
|
$flag = $flagProvider->getFlag();
|
||||||
|
|
||||||
|
if (isset($formsArray[$flag])) {
|
||||||
|
$flagForm = $formsArray[$flag];
|
||||||
|
|
||||||
|
$immediateEmailChecked = in_array(User::NOTIF_FLAG_IMMEDIATE_EMAIL, $viewData[$flag] ?? [], true)
|
||||||
|
|| !array_key_exists($flag, $viewData);
|
||||||
|
$dailyEmailChecked = in_array(User::NOTIF_FLAG_DAILY_DIGEST, $viewData[$flag] ?? [], true);
|
||||||
|
|
||||||
|
if ($flagForm->has('immediate_email')) {
|
||||||
|
$flagForm->get('immediate_email')->setData($immediateEmailChecked);
|
||||||
|
}
|
||||||
|
if ($flagForm->has('daily_email')) {
|
||||||
|
$flagForm->get('daily_email')->setData($dailyEmailChecked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mapFormsToData($forms, &$viewData): void
|
||||||
|
{
|
||||||
|
$formsArray = iterator_to_array($forms);
|
||||||
|
$viewData = [];
|
||||||
|
|
||||||
|
foreach ($this->notificationFlagProviders as $flagProvider) {
|
||||||
|
$flag = $flagProvider->getFlag();
|
||||||
|
|
||||||
|
if (isset($formsArray[$flag])) {
|
||||||
|
$flagForm = $formsArray[$flag];
|
||||||
|
$viewData[$flag] = [];
|
||||||
|
|
||||||
|
if (true === $flagForm['immediate_email']->getData()) {
|
||||||
|
$viewData[$flag][] = User::NOTIF_FLAG_IMMEDIATE_EMAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true === $flagForm['daily_email']->getData()) {
|
||||||
|
$viewData[$flag][] = User::NOTIF_FLAG_DAILY_DIGEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([] === $viewData[$flag]) {
|
||||||
|
$viewData[$flag][] = User::NOTIF_FLAG_IMMEDIATE_EMAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Form\Type;
|
namespace Chill\MainBundle\Form\Type;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Form\DataMapper\NotificationFlagDataMapper;
|
||||||
use Chill\MainBundle\Notification\NotificationFlagManager;
|
use Chill\MainBundle\Notification\NotificationFlagManager;
|
||||||
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\FormType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
@@ -28,24 +30,27 @@ class NotificationFlagsType extends AbstractType
|
|||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
|
$builder->setDataMapper(new NotificationFlagDataMapper($this->notificationFlagProviders));
|
||||||
|
|
||||||
foreach ($this->notificationFlagProviders as $flagProvider) {
|
foreach ($this->notificationFlagProviders as $flagProvider) {
|
||||||
$flag = $flagProvider->getFlag();
|
$flag = $flagProvider->getFlag();
|
||||||
$flagBuilder = $builder->create($flag, options: [
|
$builder->add($flag, FormType::class, [
|
||||||
'label' => $flagProvider->getLabel(),
|
'label' => $flagProvider->getLabel(),
|
||||||
'compound' => true,
|
'required' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$flagBuilder
|
$builder->get($flag)
|
||||||
->add('immediate_email', CheckboxType::class, [
|
->add('immediate_email', CheckboxType::class, [
|
||||||
'label' => false,
|
'label' => false,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
'mapped' => false,
|
||||||
])
|
])
|
||||||
->add('daily_digest', CheckboxType::class, [
|
->add('daily_email', CheckboxType::class, [
|
||||||
'label' => false,
|
'label' => false,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
'mapped' => false,
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
$builder->add($flagBuilder);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +58,6 @@ class NotificationFlagsType extends AbstractType
|
|||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'data_class' => null,
|
'data_class' => null,
|
||||||
'compound' => true,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user