mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-03-26 15:48:07 +00:00
Compare commits
365 Commits
v4.10.1
...
ticket-app
| Author | SHA1 | Date | |
|---|---|---|---|
|
6cc394b006
|
|||
|
d4a625f6b5
|
|||
|
1f7a98a89b
|
|||
| 300547ad14 | |||
| dcccbb36f4 | |||
| d1fe1be0f8 | |||
| 6654bea48f | |||
|
bd4c5adfa6
|
|||
|
3c4c4ee542
|
|||
|
8c80df77f6
|
|||
| a791fea794 | |||
| eff0f6bcda | |||
| 63fc600be6 | |||
| fff9a5b95f | |||
|
59d8bf75b2
|
|||
|
eb2dfc8591
|
|||
| b5a22508ff | |||
| f12bc2f35f | |||
|
9ba8ec8f41
|
|||
| 6a66f05451 | |||
| 1524ed8ce9 | |||
| 0aa0824831 | |||
| dd429ca02a | |||
| 33e60377b5 | |||
|
|
3d008b6f71 | ||
| 81193376a4 | |||
| a921009eff | |||
|
e2dec28577
|
|||
| 30385da409 | |||
| 562fecb4aa | |||
| 8e8f459f90 | |||
| 5de3862ec2 | |||
|
|
6a39811fe8 | ||
|
|
f98af5ab20 | ||
|
|
1e3918319e | ||
| 26838648c8 | |||
| 030553a4de | |||
|
966f9f7e33
|
|||
| 7a5300b713 | |||
| dc3a585e5b | |||
| 7712d76889 | |||
|
|
69bb7026c9 | ||
| acd7240903 | |||
| 22049558da | |||
| c0f2f3f3e0 | |||
| bf56b3cc65 | |||
| f85973f7ae | |||
| f1446d7abe | |||
| 76d675ac02 | |||
| cf0a2b7393 | |||
|
|
80b05a8133 | ||
| 69aba8d9c9 | |||
| a87d936828 | |||
| 290fa7a77c | |||
| 0e1d233d79 | |||
| 590f4121d0 | |||
|
|
1be2806f37 | ||
|
|
ad2e0692a3 | ||
|
|
f1d194d523 | ||
|
3402e4863f
|
|||
| 1f0974ea68 | |||
| 9997fb287a | |||
| f9a9de1148 | |||
|
|
c34f720f94 | ||
| e1b1f592fa | |||
| 8546f4dadc | |||
|
4028c020ee
|
|||
| 0d4eef6a0c | |||
| b6152d5356 | |||
| 8b708f8c73 | |||
| 8d5b200107 | |||
| a9e9207d5a | |||
| 3915574ed4 | |||
| f3217d22ef | |||
|
06c5affbe7
|
|||
| bf461a1211 | |||
| 3f0ad51114 | |||
| a4de8eaab3 | |||
| 2feb137ac2 | |||
| 5ea74d118b | |||
| 8eb7a55ef5 | |||
| 281887355f | |||
| 47b285b584 | |||
|
d79a1f5ed8
|
|||
|
dbe4bed183
|
|||
| 7c9b4d02f6 | |||
| 3ff9bba4de | |||
| c0f9e953fb | |||
| a49ea2b6b9 | |||
|
2b1f337a28
|
|||
|
b1f7b83fdc
|
|||
|
e1cda00a98
|
|||
|
46b1399c3c
|
|||
|
022c0aaebf
|
|||
| a30232d3ce | |||
| aae55e6f8c | |||
| c9513f2f6c | |||
| 11d7425883 | |||
| 08897e0981 | |||
| 98cbfed054 | |||
| 9af4d19744 | |||
| c1cf5a8bb2 | |||
|
766a9292e0
|
|||
| cb677b8212 | |||
|
|
b630c940d4 | ||
|
dae040ebfb
|
|||
|
|
b7df78bc55 | ||
|
|
24927069a4 | ||
|
|
10052cc5e1 | ||
|
|
723beb3c02 | ||
|
74d133e739
|
|||
|
|
c92467b284 | ||
|
|
d3d2c06348 | ||
|
|
fc66d0e070 | ||
| 985761ff00 | |||
| 6d93b2b1b6 | |||
| 9261bbadc3 | |||
|
|
df15c2fee7 | ||
|
e82cee2ce1
|
|||
|
d16de04d27
|
|||
| d21d03d407 | |||
| 2f930ac138 | |||
| 65bbdc283d | |||
|
|
f0b3e63d66 | ||
| 41a31944b3 | |||
| 51607572de | |||
| 1fc5530707 | |||
|
|
b31778c068 | ||
|
305c6deb24
|
|||
|
d691098a21
|
|||
| a22cbe0239 | |||
| 98902bdeb8 | |||
| 4765d4fe28 | |||
|
|
30bcb85549 | ||
|
6d2e78ce55
|
|||
| 61ca700bbe | |||
|
|
b43aeebc3c | ||
| 056e2dcc5f | |||
| e57d1ac696 | |||
| 0eff1d2e79 | |||
| 3928b2cc7a | |||
|
4f51ef81ad
|
|||
|
4637dc692c
|
|||
| 38935edb93 | |||
|
|
e1ef65d4ca | ||
| ec9d0be70b | |||
|
|
0ba2cbc1e8 | ||
| e87429933a | |||
|
8e2e676e3d
|
|||
| e12ad563a3 | |||
|
|
711aa8db9b | ||
| e78d44953f | |||
| 18f67801c7 | |||
|
c815e6bc69
|
|||
| 807f2711fe | |||
|
|
cd594cd580 | ||
|
fb6b26bfb5
|
|||
|
c5cedb8bd6
|
|||
|
2665e43a61
|
|||
|
25561cdf63
|
|||
| 10b73e06e1 | |||
|
|
e7c04e34a9 | ||
| 164beee7c6 | |||
|
|
4d96eb9457 | ||
| fe2eba3b29 | |||
|
|
61d1232e31 | ||
| 6594d4f6a6 | |||
| 1a66a9e864 | |||
| 1b74c119dc | |||
| 14d88810f3 | |||
| 445a2c9358 | |||
| c8baf0a8aa | |||
| faed443a96 | |||
| bbf387d96f | |||
|
|
b7c9b60744 | ||
|
2bd303bbbe
|
|||
|
c5e6122d2c
|
|||
| 088b876e20 | |||
| 3400656d7c | |||
|
568c8be7fd
|
|||
|
538ecc42ea
|
|||
|
15d26d4b06
|
|||
|
d8bd9bd7cd
|
|||
|
dcdfba5ccd
|
|||
|
0204bdd38d
|
|||
|
392fd01b56
|
|||
| 35844f3b73 | |||
| 7506b918d7 | |||
| cfba291f2c | |||
|
|
04438c09d3 | ||
| 2a54d1b909 | |||
|
|
628eeac5e0 | ||
| a2263b3fa1 | |||
| 74796d0fb0 | |||
|
c19481e40a
|
|||
|
|
6eeb717b1a | ||
|
beb7c462da
|
|||
|
|
dbf363a9e8 | ||
|
64a2f7c9ed
|
|||
| f26d9739c8 | |||
|
afa5edc1d8
|
|||
|
42d6c9e672
|
|||
|
2b22d4cb7c
|
|||
|
c8e5d0eb37
|
|||
| 2bf8ad5d6c | |||
| 11698a52e3 | |||
| 70955573e8 | |||
|
|
3df4043eb9 | ||
|
06e8264dde
|
|||
| b451d2c4a3 | |||
| 4f93150874 | |||
| 0566ab0910 | |||
|
|
f4eeee1598 | ||
| 33cf16fc13 | |||
| 0a331aab37 | |||
| d43b739654 | |||
| c72432efae | |||
|
95975fae55
|
|||
|
95a7efa138
|
|||
|
45e193ff6d
|
|||
|
dfc146ff3f
|
|||
| b41fcf66a9 | |||
|
|
a8dd1b3548 | ||
|
2b99a480ac
|
|||
|
7633e587bb
|
|||
|
fc61dfdf3a
|
|||
|
f1a5b5c49e
|
|||
|
ec685dcd47
|
|||
|
631ae3eedd
|
|||
|
440a7837ac
|
|||
|
e0abf34784
|
|||
|
377ae9a9dc
|
|||
|
034dc30e30
|
|||
|
d615111a0f
|
|||
|
ffb756c712
|
|||
|
69daccb860
|
|||
|
16435423cf
|
|||
|
697b4ab436
|
|||
|
67d804e28e
|
|||
|
cf41fa9574
|
|||
|
b8b325f7d7
|
|||
|
e97bd8c4ef
|
|||
|
e28d7df533
|
|||
|
4b20b1bc01
|
|||
|
b15733076c
|
|||
|
25be5c9ea3
|
|||
|
b035020c6f
|
|||
|
128101dc46
|
|||
|
5f2711023e
|
|||
|
bdf2ed4bbd
|
|||
|
1df542603e
|
|||
|
80bcc68ce5
|
|||
|
154fc3e2f6
|
|||
|
e45af94c78
|
|||
|
166a6fde20
|
|||
|
631f047338
|
|||
|
a777588bb8
|
|||
|
ca78d112c2
|
|||
|
bcfd317d83
|
|||
|
348740f073
|
|||
|
0d74f0980f
|
|||
|
be19dc00db
|
|||
|
643028ffd6
|
|||
|
ac4e2e5bf2
|
|||
|
498572b96e
|
|||
|
d2a61ce69b
|
|||
|
a9c0567ee1
|
|||
|
76cec5b5a8
|
|||
|
efe8a67697
|
|||
|
26dfa9b028
|
|||
|
50025044d3
|
|||
|
e6202a2e34
|
|||
|
b863bd967d
|
|||
|
e65bcf7275
|
|||
|
e00ece4200
|
|||
|
640fd71402
|
|||
|
aae50ca290
|
|||
|
1fa483598b
|
|||
|
e4b6a468f8
|
|||
|
|
66c7758023 | ||
|
|
4750d2c24e | ||
|
|
ca05e3d979 | ||
|
|
a20f9b4f86 | ||
|
|
c73c1eb8d5 | ||
|
|
8778bb0731 | ||
|
|
c7d20eebc5 | ||
|
|
b9e130c159 | ||
|
|
3e8bc94af3 | ||
|
|
0c914c9f9f | ||
|
|
580a60c939 | ||
|
|
4996ac3b7c | ||
|
|
2a23bf19cb | ||
|
|
650d2596d9 | ||
|
|
2bdd5a329e | ||
|
78d1776733
|
|||
|
66dc603c85
|
|||
|
3a8154ecce
|
|||
|
c81828e04f
|
|||
|
|
ec17dd7de2 | ||
| 76c076a5f3 | |||
|
|
f0045edd6c | ||
|
|
d00b76ffcd | ||
|
|
8991f0ef3f | ||
|
|
d6f5eae0c9 | ||
|
|
821fce3dd8 | ||
|
|
1d33ae1e39 | ||
|
|
19af0feb57 | ||
|
|
1c09e9a692 | ||
|
|
d72e748388 | ||
|
|
ab850b7b70 | ||
|
|
3f9745d8cf | ||
|
|
473765366a | ||
|
|
6500c24a7f | ||
|
|
1d00457141 | ||
|
|
eb0bf56cff | ||
|
|
7b8cd90cf1 | ||
|
|
a27d92aba0 | ||
|
|
85bdfb9e21 | ||
|
|
4cffcf4de1 | ||
|
|
b2587a688f | ||
|
|
c9f0e9843b | ||
|
|
b40ad9e445 | ||
|
|
3e10e47e29 | ||
|
|
2a1963e993 | ||
| 34c171659b | |||
|
2d8b960d9e
|
|||
| 831ae03431 | |||
|
45828174d1
|
|||
|
ed45f14a45
|
|||
|
fa67835690
|
|||
|
b434d38091
|
|||
|
|
800a952532 | ||
|
9f355032a8
|
|||
|
0bc6e62d4d
|
|||
|
46fb1c04b5
|
|||
| 3b2c3d1464 | |||
|
|
0bd6038160 | ||
|
|
baab8e94ce | ||
|
e2deb55fdb
|
|||
|
|
2cdfb50058 | ||
|
39d701feb2
|
|||
|
613ee8b186
|
|||
|
56a1a488de
|
|||
| 3f789ad0f4 | |||
|
467bea7cde
|
|||
|
670b8eb82b
|
|||
|
a9760b323f
|
|||
|
71a3a1924a
|
|||
|
ecdc1e25bf
|
|||
|
dd37427be1
|
|||
|
c8467df1b1
|
|||
|
4c89a954fa
|
|||
|
7c1f3b114d
|
|||
|
36bc4dab24
|
|||
|
4b30d92282
|
|||
|
75fbec5489
|
|||
|
912fdd6349
|
|||
|
5832542978
|
|||
|
5c3585a1ed
|
|||
|
a2f1e20ddf
|
|||
|
4d67702a76
|
|||
| 18e442db29 | |||
|
|
deb3d92189 | ||
|
a59ea7db31
|
|||
|
a738b0cac9
|
8
.changes/unreleased/DX-20260324-153805.yaml
Normal file
8
.changes/unreleased/DX-20260324-153805.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
kind: DX
|
||||
body: 'Changie: add a field for adding a release note tag when creating an entry in changie.'
|
||||
time: 2026-03-24T15:38:05.320350835+01:00
|
||||
custom:
|
||||
IRN: "No"
|
||||
Issue: ""
|
||||
MR: ""
|
||||
SchemaChange: No schema change
|
||||
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
6
.changes/unreleased/Feature-20240530-160003.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
|
||||
time: 2024-05-30T16:00:03.440767606+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
6
.changes/unreleased/Feature-20240531-190242.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: |
|
||||
Upgrade CKEditor and refactor configuration with use of typescript
|
||||
time: 2024-05-31T19:02:42.776662753+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
6
.changes/unreleased/Feature-20250808-120802.yaml
Normal file
6
.changes/unreleased/Feature-20250808-120802.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Create invitation list in user menu
|
||||
time: 2025-08-08T12:08:02.446361367+02:00
|
||||
custom:
|
||||
Issue: "385"
|
||||
SchemaChange: No schema change
|
||||
6
.changes/unreleased/Feature-20251007-155945.yaml
Normal file
6
.changes/unreleased/Feature-20251007-155945.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Admin interface for Motive entity
|
||||
time: 2025-10-07T15:59:45.597029709+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
||||
6
.changes/unreleased/Feature-20251022-111552.yaml
Normal file
6
.changes/unreleased/Feature-20251022-111552.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Add an admin interface for Motive entity
|
||||
time: 2025-10-22T11:15:52.13937955+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: Add columns or tables
|
||||
7
.changes/unreleased/Feature-20260324-164018.yaml
Normal file
7
.changes/unreleased/Feature-20260324-164018.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
kind: Feature
|
||||
body: Add a field "externalId" on center, to ease the synchronisation of centers with external tools
|
||||
time: 2026-03-24T16:40:18.159561269+01:00
|
||||
custom:
|
||||
Issue: "507"
|
||||
MR: "977"
|
||||
SchemaChange: Add columns or tables
|
||||
6
.changes/unreleased/Fixed-20251106-161605.yaml
Normal file
6
.changes/unreleased/Fixed-20251106-161605.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Fixed
|
||||
body: Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
|
||||
time: 2025-11-06T16:16:05.861813041+01:00
|
||||
custom:
|
||||
Issue: "428"
|
||||
SchemaChange: No schema change
|
||||
6
.changes/unreleased/Major-20260326-162018.yaml
Normal file
6
.changes/unreleased/Major-20260326-162018.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Major
|
||||
body: Add a bundle to deal with tickets
|
||||
time: 2026-03-26T16:20:18.302331043+01:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: Add columns or tables
|
||||
9
.changes/v4.11.0.md
Normal file
9
.changes/v4.11.0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## v4.11.0 - 2025-12-17
|
||||
### Feature
|
||||
* ([#478](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/478)) Add filtering to admin lists: social actions, social issues, goals, results, and evaluations
|
||||
### Fixed
|
||||
* ([#466](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/466)) Fix migration query after previous fix
|
||||
|
||||
* Fix translation key/value
|
||||
|
||||
Cannot start with % and should be wrapped in "".
|
||||
16
.changes/v4.12.0.md
Normal file
16
.changes/v4.12.0.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## v4.12.0 - 2026-01-15
|
||||
### Feature
|
||||
* ([#473](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/473)) Display version of chill bundles in application footer
|
||||
* Increase the delay before removing stale workflow from 90 days to 180 days.
|
||||
### Fixed
|
||||
* ([#480](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/480)) Fix the condition to display concerned persons in calendar list items.
|
||||
* ([#481](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/481)) Fix ordering of social actions: actions with a closing date in the future should be considered as 'still open'.
|
||||
* ([#477](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/477)) Fix export group by center for persons without a center in CenterAggregator.php
|
||||
* Fix the calculation of budget balance to only take into account resources and charges that are still actual
|
||||
* ([#489](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/489)) Fix desactivation date for Goals and results
|
||||
* ([#490](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/490)) Prevent sending a notification when the user signs the document himself
|
||||
* ([#491](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/491)) Fix: acc periods of which user is the referrer should not be included if when the list is filtered by center and none of the participations are part of the center
|
||||
* ([#492](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/492)) fix CommentInput: replace deprecated value binding with model-value
|
||||
* ([#493](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/493)) fix issue with stored object permissions associated with workflows (as attachment, or through a related entity)
|
||||
|
||||
BC: the constructor's signature of `\Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter` has changed.
|
||||
4
.changes/v4.12.1.md
Normal file
4
.changes/v4.12.1.md
Normal file
@@ -0,0 +1,4 @@
|
||||
## v4.12.1 - 2026-02-01
|
||||
### Fixed
|
||||
* ([#496](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/496)) Add the option to deal with duplicate address in BAN adress importer
|
||||
|
||||
15
.changes/v4.13.0.md
Normal file
15
.changes/v4.13.0.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## v4.13.0 - 2026-02-23
|
||||
### Feature
|
||||
* ([#500](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/500)) ([!964](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/964)) Limit the number of public download of stored object to 30 downloads
|
||||
* ([#495](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/495)) ([!967](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/967)) Send email related to notification in both html and txt format, and render quote correctly
|
||||
|
||||
### Fixed
|
||||
* ([#438](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/438)) Change wrong color of submit button "Désigner comme adresse du parcours"
|
||||
* ([#498](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/498)) For giving edit permissions on documents, take into account the workflow creator
|
||||
* Fixed mispelling of address in translations: addresse -> adresse
|
||||
* ([#499](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/499)) ([!963](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/963)) Fix: some postal code appears in the UI, although they are marked as deleted
|
||||
* ([#501](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/501)) ([!966](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/966)) Fix deprecation in the markdown rendering
|
||||
* ([#494](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/494)) ([!965](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/965)) Remove unused all-day slot display
|
||||
|
||||
### DX
|
||||
* ([!960](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/960)) Configure changie to ask for merge request number for a better tracking of changes
|
||||
6
.changes/v4.14.0.md
Normal file
6
.changes/v4.14.0.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## v4.14.0 - 2026-03-09
|
||||
### Feature
|
||||
* ([#486](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/486)) ([!<no value>](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/<no value>)) Add filter and aggregator based on referrer's main center for exports of accompanying period
|
||||
### Fixed
|
||||
* ([#502](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/502)) ([!968](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/968)) Fix import of postal code: mark postal code as deleted if they are not present in the import any more
|
||||
* ([#503](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/503)) ([!969](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/969)) Add a flash message when reassigning accompanying course (reassign list)
|
||||
5
.changes/v4.14.1.md
Normal file
5
.changes/v4.14.1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## v4.14.1 - 2026-03-16
|
||||
### Security
|
||||
* ([#506](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/506)) ([!972](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/972)) Fix permission in list of activities in person context
|
||||
### DX
|
||||
* ([#504](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/504)) ([!970](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/970)) Add seeds in DataFixtures and in some tests to avoid random test failures
|
||||
3
.changes/v4.14.2.md
Normal file
3
.changes/v4.14.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v4.14.2 - 2026-03-18
|
||||
### Fixed
|
||||
* Fix link inside notification email
|
||||
@@ -7,7 +7,7 @@ versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
|
||||
kindFormat: '### {{.Kind}}'
|
||||
# Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description.
|
||||
changeFormat: >-
|
||||
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{ .Body }} {{ if and .Custom.SchemaChange (ne .Custom.SchemaChange "No schema change") }}
|
||||
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{ if not (eq .Custom.MR "") }}([!{{ .Custom.MR }}](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/{{ .Custom.MR }})) {{ end }}{{ .Body }} {{ if (eq .Custom.IRN "Yes") }}(RN){{ end }} {{ if and .Custom.SchemaChange (ne .Custom.SchemaChange "No schema change") }}
|
||||
|
||||
**Schema Change**: {{ .Custom.SchemaChange }}
|
||||
{{- end -}}
|
||||
@@ -30,6 +30,20 @@ custom:
|
||||
type: int
|
||||
minInt: 1
|
||||
|
||||
- key: MR
|
||||
label: Merge request number (on chill-bundles repository) (optional)
|
||||
optional: true
|
||||
type: int
|
||||
minInt: 1
|
||||
|
||||
- key: IRN
|
||||
label: Is this interesting for release notes ?
|
||||
optional: false
|
||||
type: enum
|
||||
enumOptions:
|
||||
- "No"
|
||||
- "Yes"
|
||||
|
||||
body:
|
||||
# allow multiline messages
|
||||
block: true
|
||||
@@ -46,6 +60,8 @@ kinds:
|
||||
auto: patch
|
||||
- label: UX
|
||||
auto: patch
|
||||
- label: Major
|
||||
auto: major
|
||||
newlines:
|
||||
afterChangelogHeader: 1
|
||||
beforeChangelogVersion: 1
|
||||
|
||||
@@ -19,11 +19,11 @@ max_line_length = 80
|
||||
[COMMIT_EDITMSG]
|
||||
max_line_length = 0
|
||||
|
||||
[*.{js, vue, ts}]
|
||||
[*.{js,vue,ts}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[.rst]
|
||||
ident_size = 3
|
||||
ident_style = space
|
||||
[*.rst]
|
||||
indent_size = 3
|
||||
indent_style = space
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
---
|
||||
|
||||
# Select what we should cache between builds
|
||||
cache:
|
||||
paths:
|
||||
@@ -58,18 +57,17 @@ mirror_chill_zimbra_bundle:
|
||||
|
||||
rules:
|
||||
# 1) Allow manual run from GitLab UI, whatever the branch
|
||||
- if: '$CI_PIPELINE_SOURCE == "web"'
|
||||
- if: '$CI_PIPELINE_SOURCE == "web"'
|
||||
|
||||
# 2) Auto-run on commits to master or 472-zimbra-connector
|
||||
# but only if relevant files changed
|
||||
- if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "472-zimbra-connector"'
|
||||
changes:
|
||||
- packages/ChillZimbraBundle/**/*
|
||||
- .gitlab-ci.yml
|
||||
- if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "472-zimbra-connector"'
|
||||
changes:
|
||||
- packages/ChillZimbraBundle/**/*
|
||||
- .gitlab-ci.yml
|
||||
|
||||
# 3) Otherwise: never run
|
||||
- when: never
|
||||
|
||||
- when: never
|
||||
|
||||
before_script:
|
||||
- apk add --no-cache git git-subtree openssh
|
||||
@@ -99,10 +97,12 @@ build:
|
||||
stage: Composer install
|
||||
image: chill/base-image:8.3-edge
|
||||
variables:
|
||||
COMPOSER_MEMORY_LIMIT: 3G
|
||||
before_script:
|
||||
- composer config -g cache-dir "$(pwd)/.cache"
|
||||
script:
|
||||
- composer install --optimize-autoloader --no-ansi --no-interaction --no-progress
|
||||
- php bin/console cache:clear
|
||||
cache:
|
||||
paths:
|
||||
- .cache/
|
||||
@@ -110,12 +110,15 @@ build:
|
||||
expire_in: 1 day
|
||||
paths:
|
||||
- vendor/
|
||||
- var/
|
||||
|
||||
code_style:
|
||||
stage: Tests
|
||||
image: chill/base-image:8.3-edge
|
||||
script:
|
||||
- php-cs-fixer fix --dry-run -v --show-progress=none
|
||||
dependencies:
|
||||
- build
|
||||
cache:
|
||||
paths:
|
||||
- .cache/
|
||||
@@ -133,6 +136,8 @@ phpstan_tests:
|
||||
- bin/console cache:clear --env=dev
|
||||
script:
|
||||
- composer exec phpstan -- analyze --memory-limit=3G
|
||||
dependencies:
|
||||
- build
|
||||
cache:
|
||||
paths:
|
||||
- .cache/
|
||||
@@ -148,6 +153,8 @@ rector_tests:
|
||||
- bin/console cache:clear --env=dev
|
||||
script:
|
||||
- composer exec rector -- process --dry-run
|
||||
dependencies:
|
||||
- build
|
||||
cache:
|
||||
paths:
|
||||
- .cache/
|
||||
@@ -166,10 +173,37 @@ lint:
|
||||
script:
|
||||
- yarn install --ignore-optional
|
||||
- npx eslint-baseline "src/**/*.{js,ts,vue}"
|
||||
dependencies:
|
||||
- build
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
|
||||
artifacts:
|
||||
expire_in: 1 day
|
||||
paths:
|
||||
- vendor/
|
||||
vue_tsc:
|
||||
stage: Tests
|
||||
image: node:20-alpine
|
||||
before_script:
|
||||
- apk add --no-cache python3 make g++ py3-setuptools
|
||||
- export PYTHON="$(which python3)"
|
||||
- export PATH="./node_modules/.bin:$PATH"
|
||||
script:
|
||||
- yarn install --ignore-optional
|
||||
- yarn vue-tsc --noEmit > vue-tsc-report.txt 2>&1 || true
|
||||
- cat vue-tsc-report.txt
|
||||
- grep -q "error" vue-tsc-report.txt && exit 2 || exit 0
|
||||
dependencies:
|
||||
- build
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
artifacts:
|
||||
expire_in: 1 day
|
||||
paths:
|
||||
- vue-tsc-report.txt
|
||||
---
|
||||
# psalm_tests:
|
||||
# stage: Tests
|
||||
# image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82
|
||||
@@ -195,6 +229,8 @@ unit_tests:
|
||||
- php bin/console doctrine:fixtures:load -n --env=test
|
||||
script:
|
||||
- composer exec phpunit -- --colors=never --exclude-group dbIntensive,openstack-integration
|
||||
dependencies:
|
||||
- build
|
||||
artifacts:
|
||||
expire_in: 1 day
|
||||
paths:
|
||||
@@ -208,5 +244,5 @@ release:
|
||||
script:
|
||||
- echo "running release_job"
|
||||
release:
|
||||
tag_name: '$CI_COMMIT_TAG'
|
||||
tag_name: "$CI_COMMIT_TAG"
|
||||
description: "./.changes/$CI_COMMIT_TAG.md"
|
||||
|
||||
@@ -238,13 +238,13 @@ The tests are run from the project's root (not from the bundle's root).
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
vendor/bin/phpunit
|
||||
symfony composer exec phpunit
|
||||
|
||||
# Run a specific test file
|
||||
vendor/bin/phpunit path/to/TestFile.php
|
||||
symfony composer exec phpunit -- path/to/TestFile.php
|
||||
|
||||
# Run a specific test method
|
||||
vendor/bin/phpunit --filter methodName path/to/TestFile.php
|
||||
symfony composer exec phpunit --filter methodName path/to/TestFile.php
|
||||
```
|
||||
|
||||
When writing tests, only test specific files. Do not run all tests or the full
|
||||
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -6,6 +6,71 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v4.14.2 - 2026-03-18
|
||||
### Fixed
|
||||
* Fix link inside notification email
|
||||
|
||||
## v4.14.1 - 2026-03-16
|
||||
### Security
|
||||
* ([#506](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/506)) ([!972](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/972)) Fix permission in list of activities in person context
|
||||
### DX
|
||||
* ([#504](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/504)) ([!970](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/970)) Add seeds in DataFixtures and in some tests to avoid random test failures
|
||||
|
||||
## v4.14.0 - 2026-03-09
|
||||
### Feature
|
||||
* ([#486](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/486)) ([!<no value>](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/<no value>)) Add filter and aggregator based on referrer's main center for exports of accompanying period
|
||||
### Fixed
|
||||
* ([#502](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/502)) ([!968](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/968)) Fix import of postal code: mark postal code as deleted if they are not present in the import any more
|
||||
* ([#503](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/503)) ([!969](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/969)) Add a flash message when reassigning accompanying course (reassign list)
|
||||
|
||||
## v4.13.0 - 2026-02-23
|
||||
### Feature
|
||||
* ([#500](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/500)) ([!964](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/964)) Limit the number of public download of stored object to 30 downloads
|
||||
* ([#495](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/495)) ([!967](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/967)) Send email related to notification in both html and txt format, and render quote correctly
|
||||
|
||||
### Fixed
|
||||
* ([#438](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/438)) Change wrong color of submit button "Désigner comme adresse du parcours"
|
||||
* ([#498](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/498)) For giving edit permissions on documents, take into account the workflow creator
|
||||
* Fixed mispelling of address in translations: addresse -> adresse
|
||||
* ([#499](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/499)) ([!963](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/963)) Fix: some postal code appears in the UI, although they are marked as deleted
|
||||
* ([#501](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/501)) ([!966](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/966)) Fix deprecation in the markdown rendering
|
||||
* ([#494](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/494)) ([!965](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/965)) Remove unused all-day slot display
|
||||
|
||||
### DX
|
||||
* ([!960](https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/960)) Configure changie to ask for merge request number for a better tracking of changes
|
||||
|
||||
## v4.12.1 - 2026-02-01
|
||||
### Fixed
|
||||
* ([#496](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/496)) Add the option to deal with duplicate address in BAN adress importer
|
||||
|
||||
|
||||
## v4.12.0 - 2026-01-15
|
||||
### Feature
|
||||
* ([#473](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/473)) Display version of chill bundles in application footer
|
||||
* Increase the delay before removing stale workflow from 90 days to 180 days.
|
||||
### Fixed
|
||||
* ([#480](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/480)) Fix the condition to display concerned persons in calendar list items.
|
||||
* ([#481](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/481)) Fix ordering of social actions: actions with a closing date in the future should be considered as 'still open'.
|
||||
* ([#477](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/477)) Fix export group by center for persons without a center in CenterAggregator.php
|
||||
* Fix the calculation of budget balance to only take into account resources and charges that are still actual
|
||||
* ([#489](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/489)) Fix desactivation date for Goals and results
|
||||
* ([#490](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/490)) Prevent sending a notification when the user signs the document himself
|
||||
* ([#491](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/491)) Fix: acc periods of which user is the referrer should not be included if when the list is filtered by center and none of the participations are part of the center
|
||||
* ([#492](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/492)) fix CommentInput: replace deprecated value binding with model-value
|
||||
* ([#493](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/493)) fix issue with stored object permissions associated with workflows (as attachment, or through a related entity)
|
||||
|
||||
BC: the constructor's signature of `\Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter` has changed.
|
||||
|
||||
## v4.11.0 - 2025-12-17
|
||||
### Feature
|
||||
* ([#478](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/478)) Add filtering to admin lists: social actions, social issues, goals, results, and evaluations
|
||||
### Fixed
|
||||
* ([#466](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/466)) Fix migration query after previous fix
|
||||
|
||||
* Fix translation key/value
|
||||
|
||||
Cannot start with % and should be wrapped in "".
|
||||
|
||||
## v4.10.1 - 2025-12-11
|
||||
### Fixed
|
||||
* Fix missing translation variable in NewLocation component
|
||||
|
||||
@@ -54,7 +54,7 @@ Arborescence:
|
||||
- person
|
||||
- personvendee
|
||||
- household_edit_metadata
|
||||
- index.js
|
||||
- index.ts
|
||||
```
|
||||
|
||||
## Organisation des feuilles de styles
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { trans, setLocale, setLocaleFallbacks } from "./ux-translator";
|
||||
import {
|
||||
trans,
|
||||
setLocale,
|
||||
getLocale,
|
||||
setLocaleFallbacks,
|
||||
} from "./ux-translator";
|
||||
|
||||
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
|
||||
setLocale('fr');
|
||||
setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" });
|
||||
setLocale("fr");
|
||||
|
||||
export { trans };
|
||||
export * from '../var/translations';
|
||||
export { trans, getLocale };
|
||||
export * from "../var/translations";
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-redis": "*",
|
||||
"ext-zlib": "*",
|
||||
"composer-runtime-api": "*",
|
||||
"champs-libres/wopi-bundle": "dev-symfony-v5@dev",
|
||||
"champs-libres/wopi-lib": "dev-master@dev",
|
||||
"doctrine/data-fixtures": "^1.8",
|
||||
@@ -82,7 +83,7 @@
|
||||
"symfony/templating": "^5.4",
|
||||
"symfony/translation": "^5.4",
|
||||
"symfony/twig-bundle": "^5.4",
|
||||
"symfony/ux-translator": "^2.22",
|
||||
"symfony/ux-translator": "2.31.0",
|
||||
"symfony/validator": "^5.4",
|
||||
"symfony/webpack-encore-bundle": "^1.11",
|
||||
"symfony/workflow": "^5.4",
|
||||
@@ -97,7 +98,7 @@
|
||||
"require-dev": {
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.3",
|
||||
"fakerphp/faker": "^1.13",
|
||||
"friendsofphp/php-cs-fixer": "3.65.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.94",
|
||||
"jangregor/phpstan-prophecy": "^1.0",
|
||||
"nelmio/alice": "^3.8",
|
||||
"nikic/php-parser": "^4.15",
|
||||
@@ -112,13 +113,13 @@
|
||||
"symfony/debug-bundle": "^5.4",
|
||||
"symfony/dotenv": "^5.4",
|
||||
"symfony/flex": "^2.4",
|
||||
"symfony/loco-translation-provider": "^6.0",
|
||||
"symfony/maker-bundle": "^1.20",
|
||||
"symfony/phpunit-bridge": "^7.1",
|
||||
"symfony/runtime": "^5.4",
|
||||
"symfony/stopwatch": "^5.4",
|
||||
"symfony/var-dumper": "^5.4",
|
||||
"symfony/web-profiler-bundle": "^5.4",
|
||||
"symfony/loco-translation-provider": "^6.0"
|
||||
"symfony/web-profiler-bundle": "^5.4"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
@@ -141,6 +142,7 @@
|
||||
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
|
||||
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
|
||||
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
|
||||
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
|
||||
"Chill\\Utils\\Rector\\": "utils/rector/src"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -34,6 +34,7 @@ return [
|
||||
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
|
||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||
Chill\TicketBundle\ChillTicketBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
|
||||
|
||||
@@ -34,6 +34,9 @@ chill_main:
|
||||
x: '%env(float:ADD_ADDRESS_MAP_CENTER_X)%'
|
||||
y: '%env(float:ADD_ADDRESS_MAP_CENTER_Y)%'
|
||||
z: '%env(float:ADD_ADDRESS_MAP_CENTER_Z)%'
|
||||
homepage:
|
||||
default_tab: 'MyCustoms'
|
||||
display_tabs: ['MyCustoms', 'MyNotifications', 'MyAccompanyingCourses', 'MyEvaluations', 'MyTasks', 'MyWorkflows', 'MyTickets']
|
||||
|
||||
when@test:
|
||||
chill_main:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
chill_doc_store:
|
||||
use_driver: openstack
|
||||
use_driver: local_storage
|
||||
local_storage:
|
||||
storage_path: '%kernel.project_dir%/var/storage'
|
||||
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\Budget': '@ChillBudgetBundle/migrations'
|
||||
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
|
||||
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
|
||||
all_or_nothing:
|
||||
true
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ framework:
|
||||
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
|
||||
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
|
||||
'Chill\TicketBundle\Messenger\PostTicketUpdateMessage': async
|
||||
# end of routes added by chill-bundles recipes
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
||||
@@ -8,5 +8,6 @@ when@dev: &dev
|
||||
- 'file'
|
||||
- 'md5'
|
||||
- 'sha1'
|
||||
seed: 1234567890
|
||||
|
||||
when@test: *dev
|
||||
|
||||
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'
|
||||
@@ -1,23 +1,90 @@
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
A copy of the license is included in the section entitled "GNU
|
||||
Free Documentation License".
|
||||
# Create a new bundle {#create-new-bundle}
|
||||
|
||||
###### Create a new bundle
|
||||
:::: warning
|
||||
::: title
|
||||
Warning
|
||||
:::
|
||||
|
||||
Create your own bundle is not a trivial task.
|
||||
This part of the doc is not yet tested
|
||||
::::
|
||||
|
||||
The easiest way to achieve this is seems to be :
|
||||
## Create a new directory with Bundle class
|
||||
|
||||
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 [installation-for-development` section, and add your new repository to the composer.json file
|
||||
6. Work as :ref:`usual ](editing-code-and-commiting.md)
|
||||
``` bash
|
||||
mkdir -p src/Bundle/ChillSomeBundle/src/config
|
||||
mkdir -p src/Bundle/ChillSomeBundle/src/Controller
|
||||
```
|
||||
|
||||
This part of the doc is not yet tested
|
||||
Add a bundle file
|
||||
|
||||
TODO
|
||||
``` 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:
|
||||
|
||||
``` yaml
|
||||
chill_ticket_controller:
|
||||
resource: '@ChillTicketBundle/Controller/'
|
||||
type: annotation
|
||||
```
|
||||
|
||||
## Register the new psr-4 namespace
|
||||
|
||||
In composer.json, add the new psr4 namespace
|
||||
|
||||
``` diff
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
+ "Chill\\SomeBundle\\": "src/Bundle/ChillSomeBundle/src",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Register the bundle
|
||||
|
||||
Register in the file `config/bundles.php`:
|
||||
|
||||
``` php
|
||||
Vendor\Bundle\YourBundle\YourBundle::class => ['all' => true],
|
||||
```
|
||||
|
||||
And import routes in `config/routes/chill_some_bundle.yaml`:
|
||||
|
||||
``` yaml
|
||||
chill_ticket_bundle:
|
||||
resource: '@ChillSomeBundle/config/routes.yaml'
|
||||
```
|
||||
|
||||
## Add the doctrine_migration namespace
|
||||
|
||||
Add the namespace to `config/packages/doctrine_migrations_chill.yaml`
|
||||
|
||||
``` diff
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
+ 'Chill\Some\Ticket': '@ChillSomeBundle/migrations'
|
||||
```
|
||||
|
||||
## Dump autoloading
|
||||
|
||||
``` bash
|
||||
symfony composer dump-autoload
|
||||
```
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"vue-loader": "^17.0.0",
|
||||
"vue-tsc": "^3.1.3",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
@@ -80,12 +81,12 @@
|
||||
"dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build": "encore production --progress",
|
||||
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> 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-create-dir": "mkdir -p templates/api",
|
||||
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||
"version": "node --version",
|
||||
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
kind: Added
|
||||
body: Use admin delegated account for handling authentication
|
||||
time: 2026-01-22T15:32:23.932994899+01:00
|
||||
@@ -80,12 +80,19 @@ final readonly class CreateZimbraComponent
|
||||
$location = $calendar->getCalendar()->getLocation();
|
||||
$hasLocation = $calendar->getCalendar()->hasLocation();
|
||||
$isPrivate = $calendar->getCalendar()->getAccompanyingPeriod()?->isConfidential() ?? false;
|
||||
} else {
|
||||
} elseif ($calendar instanceof Calendar) {
|
||||
$startDate = $calendar->getStartDate();
|
||||
$endDate = $calendar->getEndDate();
|
||||
$location = $calendar->getLocation();
|
||||
$hasLocation = $calendar->hasLocation();
|
||||
$isPrivate = $calendar->getAccompanyingPeriod()?->isConfidential() ?? false;
|
||||
} else {
|
||||
// Calendar range case
|
||||
$startDate = $calendar->getStartDate();
|
||||
$endDate = $calendar->getEndDate();
|
||||
$location = $calendar->getLocation();
|
||||
$hasLocation = $calendar->hasLocation();
|
||||
$isPrivate = false;
|
||||
}
|
||||
|
||||
$comp = new InviteComponent();
|
||||
|
||||
@@ -11,48 +11,84 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\ZimbraBundle\Calendar\Connector\ZimbraConnector;
|
||||
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpClient\Psr18Client;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Zimbra\Admin\AdminApi;
|
||||
use Zimbra\Common\Enum\AccountBy;
|
||||
use Zimbra\Common\Soap\ClientFactory;
|
||||
use Zimbra\Common\Struct\AccountSelector;
|
||||
use Zimbra\Common\Struct\Header\AccountInfo;
|
||||
use Zimbra\Mail\MailApi;
|
||||
|
||||
final readonly class SoapClientBuilder
|
||||
final class SoapClientBuilder
|
||||
{
|
||||
private string $username;
|
||||
private readonly string $username;
|
||||
|
||||
private string $password;
|
||||
private readonly string $password;
|
||||
|
||||
private string $url;
|
||||
private readonly string $url;
|
||||
|
||||
public function __construct(private ParameterBagInterface $parameterBag, private HttpClientInterface $client)
|
||||
{
|
||||
private readonly string $adminUrl;
|
||||
|
||||
private readonly bool $verifyHost;
|
||||
|
||||
private readonly bool $verifyPeer;
|
||||
|
||||
private readonly bool $adminVerifyHost;
|
||||
|
||||
private readonly bool $adminVerifyPeer;
|
||||
|
||||
/**
|
||||
* Keep the cache of the tokens.
|
||||
*
|
||||
* @var array<string, array{token: string, expirationTime: \DateTimeImmutable}>
|
||||
*/
|
||||
private array $tokenCache = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly ParameterBagInterface $parameterBag,
|
||||
private readonly HttpClientInterface $client,
|
||||
private readonly ClockInterface $clock,
|
||||
) {
|
||||
$dsn = $this->parameterBag->get('chill_calendar.remote_calendar_dsn');
|
||||
$url = parse_url($dsn);
|
||||
|
||||
$this->username = urldecode($url['user']);
|
||||
$this->password = urldecode($url['pass']);
|
||||
if ('zimbra+http' === $url['scheme']) {
|
||||
$scheme = 'http://';
|
||||
$scheme = 'http';
|
||||
$port = $url['port'] ?? 80;
|
||||
} elseif ('zimbra+https' === $url['scheme']) {
|
||||
$scheme = 'https://';
|
||||
$scheme = 'https';
|
||||
$port = $url['port'] ?? 443;
|
||||
} else {
|
||||
throw new \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException('Unsupported remote calendar scheme: '.$url['scheme']);
|
||||
}
|
||||
|
||||
$this->url = $scheme.$url['host'].':'.$port;
|
||||
// get attributes for adminUrl
|
||||
$query = [];
|
||||
parse_str($url['query'] ?? '', $query);
|
||||
$adminPort = $query['adminPort'] ?? '7071';
|
||||
$adminHost = $query['adminHost'] ?? $url['host'];
|
||||
$adminScheme = $query['adminScheme'] ?? $scheme;
|
||||
|
||||
$this->verifyPeer = (bool) ($query['verifyPeer'] ?? true);
|
||||
$this->verifyHost = (bool) ($query['verifyHost'] ?? true);
|
||||
$this->adminVerifyHost = (bool) ($query['adminVerifyHost'] ?? $this->verifyPeer);
|
||||
$this->adminVerifyPeer = (bool) ($query['adminVerifyPeer'] ?? $this->verifyHost);
|
||||
|
||||
$this->url = $scheme.'://'.$url['host'].':'.$port;
|
||||
$this->adminUrl = $adminScheme.'://'.$adminHost.':'.$adminPort;
|
||||
}
|
||||
|
||||
private function buildApi(): MailApi
|
||||
{
|
||||
$baseClient = $this->client->withOptions([
|
||||
'base_uri' => $location = $this->url.'/service/soap',
|
||||
'verify_host' => false,
|
||||
'verify_peer' => false,
|
||||
'verify_host' => $this->verifyHost,
|
||||
'verify_peer' => $this->verifyPeer,
|
||||
]);
|
||||
$psr18Client = new Psr18Client($baseClient);
|
||||
$api = new MailApi();
|
||||
@@ -62,12 +98,36 @@ final readonly class SoapClientBuilder
|
||||
return $api;
|
||||
}
|
||||
|
||||
private function buildAdminApi(): AdminApi
|
||||
{
|
||||
$baseClient = $this->client->withOptions([
|
||||
'base_uri' => $location = $this->adminUrl.'/service/admin/soap',
|
||||
'verify_host' => $this->adminVerifyHost,
|
||||
'verify_peer' => $this->adminVerifyPeer,
|
||||
]);
|
||||
$psr18Client = new Psr18Client($baseClient);
|
||||
$api = new AdminApi();
|
||||
$client = ClientFactory::create($location, $psr18Client);
|
||||
$api->setClient($client);
|
||||
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function getApiForAccount(string $accountName): MailApi
|
||||
{
|
||||
$api = $this->buildApi();
|
||||
$response = $api->authByAccountName($this->username, $this->password);
|
||||
['token' => $token, 'expirationTime' => $expirationTime] = $this->tokenCache[$accountName]
|
||||
?? ['token' => null, 'expirationTime' => null];
|
||||
|
||||
$token = $response->getAuthToken();
|
||||
if (null === $token || null === $expirationTime || $expirationTime <= $this->clock->now()) {
|
||||
$adminApi = $this->buildAdminApi();
|
||||
$adminApi->auth($this->username, $this->password);
|
||||
|
||||
$delegateResponse = $adminApi->delegateAuth(new AccountSelector(AccountBy::NAME, $accountName));
|
||||
$token = $delegateResponse->getAuthToken();
|
||||
$expiration = $delegateResponse->getLifetime();
|
||||
$expirationTime = $this->clock->now()->add(new \DateInterval('PT'.$expiration.'S'));
|
||||
$this->tokenCache[$accountName] = ['token' => $token, 'expirationTime' => $expirationTime];
|
||||
}
|
||||
|
||||
$apiBy = $this->buildApi();
|
||||
$apiBy->setAuthToken($token);
|
||||
|
||||
@@ -58,6 +58,10 @@
|
||||
<!-- temporarily removed, the time to find a fix -->
|
||||
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="TicketBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
|
||||
</testsuite>
|
||||
<!--
|
||||
<testsuite name="ReportBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
||||
|
||||
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"}
|
||||
@@ -33,6 +33,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
|
||||
|
||||
public function __construct(private readonly EntityManagerInterface $em)
|
||||
{
|
||||
mt_srand(123456789);
|
||||
$this->faker = FakerFactory::create('fr_FR');
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
|
||||
->findAll();
|
||||
|
||||
foreach ($persons as $person) {
|
||||
$activityNbr = random_int(0, 3);
|
||||
$activityNbr = mt_rand(0, 3);
|
||||
|
||||
for ($i = 0; $i < $activityNbr; ++$i) {
|
||||
$activity = $this->newRandomActivity($person);
|
||||
@@ -73,7 +74,7 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
|
||||
|
||||
// ->setAttendee($this->faker->boolean())
|
||||
|
||||
for ($i = 0; random_int(0, 4) > $i; ++$i) {
|
||||
for ($i = 0; mt_rand(0, 4) > $i; ++$i) {
|
||||
$reason = $this->getRandomActivityReason();
|
||||
|
||||
if (null !== $reason) {
|
||||
|
||||
@@ -69,7 +69,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
|
||||
}
|
||||
|
||||
/** (non-PHPdoc).
|
||||
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
|
||||
* @see PrependExtensionInterface::prepend()
|
||||
*/
|
||||
public function prependRoutes(ContainerBuilder $container)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInt
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -340,7 +341,7 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos
|
||||
}
|
||||
|
||||
foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
|
||||
if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) {
|
||||
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $participation->getAccompanyingPeriod())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<concerned-groups v-if="hasPerson" />
|
||||
<social-issues-acc v-if="hasSocialIssues" />
|
||||
<location v-if="hasLocation" />
|
||||
<concerned-groups v-if="hasPerson" />
|
||||
<social-issues-acc v-if="hasSocialIssues" />
|
||||
<location v-if="hasLocation" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -10,12 +10,12 @@ import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
|
||||
import Location from "./components/Location.vue";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
SocialIssuesAcc,
|
||||
Location,
|
||||
},
|
||||
name: "App",
|
||||
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
SocialIssuesAcc,
|
||||
Location,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
<template>
|
||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||
<persons-bloc
|
||||
v-for="bloc in contextPersonsBlocs"
|
||||
:key="bloc.key"
|
||||
:bloc="bloc"
|
||||
:bloc-width="getBlocWidth"
|
||||
:set-persons-in-bloc="setPersonsInBloc"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
getContext === 'accompanyingCourse' &&
|
||||
suggestedEntities.length > 0
|
||||
"
|
||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||
<persons-bloc
|
||||
v-for="bloc in contextPersonsBlocs"
|
||||
:key="bloc.key"
|
||||
:bloc="bloc"
|
||||
:bloc-width="getBlocWidth"
|
||||
:set-persons-in-bloc="setPersonsInBloc"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="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">
|
||||
<li
|
||||
v-for="(p, i) in suggestedEntities"
|
||||
@click="addSuggestedEntity(p)"
|
||||
:key="`suggestedEntities-${i}`"
|
||||
>
|
||||
<person-text v-if="p.type === 'person'" :person="p" />
|
||||
<span v-else>{{ p.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<person-text v-if="p.type === 'person'" :person="p" />
|
||||
<span v-else>{{ p.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="add-persons">
|
||||
<add-persons
|
||||
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
v-bind:key="addPersons.key"
|
||||
v-bind:options="addPersonsOptions"
|
||||
@addNewPersons="addNewPersons"
|
||||
ref="addPersons"
|
||||
>
|
||||
</add-persons>
|
||||
</li>
|
||||
</ul>
|
||||
</teleport>
|
||||
<ul class="record_actions">
|
||||
<li class="add-persons">
|
||||
<add-persons
|
||||
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||
v-bind:key="addPersons.key"
|
||||
v-bind:options="addPersonsOptions"
|
||||
@addNewPersons="addNewPersons"
|
||||
ref="addPersons"
|
||||
>
|
||||
</add-persons>
|
||||
</li>
|
||||
</ul>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -49,208 +46,208 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
||||
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||
import {
|
||||
ACTIVITY_BLOC_PERSONS,
|
||||
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||
ACTIVITY_BLOC_THIRDPARTY,
|
||||
ACTIVITY_BLOC_USERS,
|
||||
ACTIVITY_ADD_PERSONS,
|
||||
trans,
|
||||
ACTIVITY_BLOC_PERSONS,
|
||||
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||
ACTIVITY_BLOC_THIRDPARTY,
|
||||
ACTIVITY_BLOC_USERS,
|
||||
ACTIVITY_ADD_PERSONS,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "ConcernedGroups",
|
||||
components: {
|
||||
AddPersons,
|
||||
PersonsBloc,
|
||||
PersonText,
|
||||
name: "ConcernedGroups",
|
||||
components: {
|
||||
AddPersons,
|
||||
PersonsBloc,
|
||||
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() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_ADD_PERSONS,
|
||||
};
|
||||
...mapState({
|
||||
persons: (state) => state.activity.persons,
|
||||
thirdParties: (state) => state.activity.thirdParties,
|
||||
users: (state) => state.activity.users,
|
||||
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
||||
}),
|
||||
...mapGetters(["suggestedEntities"]),
|
||||
getContext() {
|
||||
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
||||
},
|
||||
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",
|
||||
},
|
||||
};
|
||||
contextPersonsBlocs() {
|
||||
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
||||
},
|
||||
computed: {
|
||||
isComponentVisible() {
|
||||
return window.activity
|
||||
? window.activity.activityType.personsVisible !== 0 ||
|
||||
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
||||
window.activity.activityType.usersVisible !== 0
|
||||
: true;
|
||||
},
|
||||
...mapState({
|
||||
persons: (state) => state.activity.persons,
|
||||
thirdParties: (state) => state.activity.thirdParties,
|
||||
users: (state) => state.activity.users,
|
||||
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
||||
}),
|
||||
...mapGetters(["suggestedEntities"]),
|
||||
getContext() {
|
||||
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
||||
},
|
||||
contextPersonsBlocs() {
|
||||
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
||||
},
|
||||
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) + "%";
|
||||
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",
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.setPersonsInBloc();
|
||||
getBlocWidth() {
|
||||
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
||||
},
|
||||
methods: {
|
||||
setPersonsInBloc() {
|
||||
let groups;
|
||||
if (this.accompanyingCourse) {
|
||||
groups = this.splitPersonsInGroups();
|
||||
}
|
||||
this.personsBlocs.forEach((bloc) => {
|
||||
if (this.accompanyingCourse) {
|
||||
switch (bloc.key) {
|
||||
case "personsAssociated":
|
||||
bloc.persons = groups.personsAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
case "personsNotAssociated":
|
||||
bloc.persons = groups.personsNotAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (bloc.key) {
|
||||
case "persons":
|
||||
bloc.persons = this.persons;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (bloc.key) {
|
||||
case "thirdparty":
|
||||
bloc.persons = this.thirdParties;
|
||||
break;
|
||||
case "users":
|
||||
bloc.persons = this.users;
|
||||
break;
|
||||
}
|
||||
}, groups);
|
||||
},
|
||||
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();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
methods: {
|
||||
setPersonsInBloc() {
|
||||
let groups;
|
||||
if (this.accompanyingCourse) {
|
||||
groups = this.splitPersonsInGroups();
|
||||
}
|
||||
this.personsBlocs.forEach((bloc) => {
|
||||
if (this.accompanyingCourse) {
|
||||
switch (bloc.key) {
|
||||
case "personsAssociated":
|
||||
bloc.persons = groups.personsAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
case "personsNotAssociated":
|
||||
bloc.persons = groups.personsNotAssociated;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (bloc.key) {
|
||||
case "persons":
|
||||
bloc.persons = this.persons;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (bloc.key) {
|
||||
case "thirdparty":
|
||||
bloc.persons = this.thirdParties;
|
||||
break;
|
||||
case "users":
|
||||
bloc.persons = this.users;
|
||||
break;
|
||||
}
|
||||
}, groups);
|
||||
},
|
||||
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>
|
||||
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<template>
|
||||
<li>
|
||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||
<span class="chill_denomination">
|
||||
<person-text :person="person" :is-cut="true" />
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||
<span class="chill_denomination">
|
||||
<person-text :person="person" :is-cut="true" />
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||
|
||||
export default {
|
||||
name: "PersonBadge",
|
||||
props: ["person"],
|
||||
components: {
|
||||
PersonText,
|
||||
},
|
||||
// computed: {
|
||||
// textCutted() {
|
||||
// let more = (this.person.text.length > 15) ?'…' : '';
|
||||
// return this.person.text.slice(0,15) + more;
|
||||
// }
|
||||
// },
|
||||
emits: ["remove"],
|
||||
name: "PersonBadge",
|
||||
props: ["person"],
|
||||
components: {
|
||||
PersonText,
|
||||
},
|
||||
// computed: {
|
||||
// textCutted() {
|
||||
// let more = (this.person.text.length > 15) ?'…' : '';
|
||||
// return this.person.text.slice(0,15) + more;
|
||||
// }
|
||||
// },
|
||||
emits: ["remove"],
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h4>{{ $t(bloc.title) }}</h4>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-suggest remove-items">
|
||||
<person-badge
|
||||
v-for="person in bloc.persons"
|
||||
:key="person.id"
|
||||
:person="person"
|
||||
@remove="removePerson"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<h4>{{ $t(bloc.title) }}</h4>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-suggest remove-items">
|
||||
<person-badge
|
||||
v-for="person in bloc.persons"
|
||||
:key="person.id"
|
||||
:person="person"
|
||||
@remove="removePerson"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PersonBadge from "./PersonBadge.vue";
|
||||
export default {
|
||||
name: "PersonsBloc",
|
||||
components: {
|
||||
PersonBadge,
|
||||
},
|
||||
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
||||
methods: {
|
||||
removePerson(item) {
|
||||
console.log("@@ CLICK remove person: item", item);
|
||||
this.$store.dispatch("removePersonInvolved", item);
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
name: "PersonsBloc",
|
||||
components: {
|
||||
PersonBadge,
|
||||
},
|
||||
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
||||
methods: {
|
||||
removePerson(item) {
|
||||
console.log("@@ CLICK remove person: item", item);
|
||||
this.$store.dispatch("removePersonInvolved", item);
|
||||
this.setPersonsInBloc();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<template>
|
||||
<teleport to="#location">
|
||||
<div class="mb-3 row">
|
||||
<label :class="locationClassList">
|
||||
{{ trans(ACTIVITY_LOCATION) }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<VueMultiselect
|
||||
name="selectLocation"
|
||||
id="selectLocation"
|
||||
label="name"
|
||||
track-by="id"
|
||||
open-direction="top"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||
:custom-label="customLabel"
|
||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||
:options="availableLocations"
|
||||
group-values="locations"
|
||||
group-label="locationGroup"
|
||||
v-model="location"
|
||||
/>
|
||||
<new-location v-bind:available-locations="availableLocations" />
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
<teleport to="#location">
|
||||
<div class="mb-3 row">
|
||||
<label :class="locationClassList">
|
||||
{{ trans(ACTIVITY_LOCATION) }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<VueMultiselect
|
||||
name="selectLocation"
|
||||
id="selectLocation"
|
||||
label="name"
|
||||
track-by="id"
|
||||
open-direction="top"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||
:custom-label="customLabel"
|
||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||
:options="availableLocations"
|
||||
group-values="locations"
|
||||
group-label="locationGroup"
|
||||
v-model="location"
|
||||
/>
|
||||
<new-location v-bind:available-locations="availableLocations" />
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -35,60 +35,60 @@ import VueMultiselect from "vue-multiselect";
|
||||
import NewLocation from "./Location/NewLocation.vue";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
import {
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "Location",
|
||||
components: {
|
||||
NewLocation,
|
||||
VueMultiselect,
|
||||
name: "Location",
|
||||
components: {
|
||||
NewLocation,
|
||||
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 {
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
labelAccompanyingCourseLocation(value) {
|
||||
return `${value.address.text} (${localizeString(value.locationType.title)})`;
|
||||
},
|
||||
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);
|
||||
},
|
||||
},
|
||||
},
|
||||
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)
|
||||
: "";
|
||||
},
|
||||
customLabel(value) {
|
||||
return value.locationType
|
||||
? value.name
|
||||
? value.name === "__AccompanyingCourseLocation__"
|
||||
? this.labelAccompanyingCourseLocation(value)
|
||||
: `${value.name} (${localizeString(value.locationType.title)})`
|
||||
: localizeString(value.locationType.title)
|
||||
: "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,123 +1,114 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-sm btn-create" @click="openModal">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-sm btn-create" @click="openModal">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<teleport to="body">
|
||||
<modal
|
||||
v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<form>
|
||||
<div class="alert alert-warning" v-if="errors.length">
|
||||
<ul>
|
||||
<li v-for="(e, i) in errors" :key="i">
|
||||
{{ e }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<modal
|
||||
v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<form>
|
||||
<div class="alert alert-warning" v-if="errors.length">
|
||||
<ul>
|
||||
<li v-for="(e, i) in errors" :key="i">
|
||||
{{ e }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<select
|
||||
class="form-select form-select-lg"
|
||||
id="type"
|
||||
required
|
||||
v-model="selectType"
|
||||
>
|
||||
<option selected disabled value="">
|
||||
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||
</option>
|
||||
<option
|
||||
v-for="t in locationTypes"
|
||||
:value="t"
|
||||
:key="t.id"
|
||||
>
|
||||
{{ localizeString(t.title) }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<select
|
||||
class="form-select form-select-lg"
|
||||
id="type"
|
||||
required
|
||||
v-model="selectType"
|
||||
>
|
||||
<option selected disabled value="">
|
||||
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||
</option>
|
||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
||||
{{ localizeString(t.title) }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="name"
|
||||
v-model="inputName"
|
||||
placeholder
|
||||
/>
|
||||
<label for="name">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="name"
|
||||
v-model="inputName"
|
||||
placeholder
|
||||
/>
|
||||
<label for="name">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
||||
}}</label>
|
||||
</div>
|
||||
|
||||
<add-address
|
||||
:context="addAddress.context"
|
||||
:options="addAddress.options"
|
||||
:addressChangedCallback="submitNewAddress"
|
||||
v-if="showAddAddress"
|
||||
ref="addAddress"
|
||||
/>
|
||||
<add-address
|
||||
:context="addAddress.context"
|
||||
:options="addAddress.options"
|
||||
:addressChangedCallback="submitNewAddress"
|
||||
v-if="showAddAddress"
|
||||
ref="addAddress"
|
||||
/>
|
||||
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber1"
|
||||
v-model="inputPhonenumber1"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber1">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber2"
|
||||
v-model="inputPhonenumber2"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber2">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="email"
|
||||
v-model="inputEmail"
|
||||
placeholder
|
||||
/>
|
||||
<label for="email">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
||||
}}</label>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button
|
||||
class="btn btn-save"
|
||||
@click.prevent="saveNewLocation"
|
||||
>
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber1"
|
||||
v-model="inputPhonenumber1"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber1">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="phonenumber2"
|
||||
v-model="inputPhonenumber2"
|
||||
placeholder
|
||||
/>
|
||||
<label for="phonenumber2">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
id="email"
|
||||
v-model="inputEmail"
|
||||
placeholder
|
||||
/>
|
||||
<label for="email">{{
|
||||
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
||||
}}</label>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button class="btn btn-save" @click.prevent="saveNewLocation">
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -128,241 +119,240 @@ import { getLocationTypes } from "../../api";
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
import {
|
||||
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,
|
||||
ACTIVITY_EDIT_ADDRESS,
|
||||
ACTIVITY_CREATE_ADDRESS,
|
||||
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,
|
||||
ACTIVITY_EDIT_ADDRESS,
|
||||
ACTIVITY_CREATE_ADDRESS,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "NewLocation",
|
||||
components: {
|
||||
Modal,
|
||||
AddAddress,
|
||||
name: "NewLocation",
|
||||
components: {
|
||||
Modal,
|
||||
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,
|
||||
ACTIVITY_EDIT_ADDRESS,
|
||||
ACTIVITY_CREATE_ADDRESS,
|
||||
};
|
||||
},
|
||||
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() {
|
||||
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,
|
||||
ACTIVITY_EDIT_ADDRESS,
|
||||
ACTIVITY_CREATE_ADDRESS,
|
||||
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,
|
||||
};
|
||||
},
|
||||
props: ["availableLocations"],
|
||||
data() {
|
||||
return {
|
||||
errors: [],
|
||||
selected: {
|
||||
type: null,
|
||||
name: null,
|
||||
addressId: null,
|
||||
phonenumber1: null,
|
||||
phonenumber2: null,
|
||||
email: null,
|
||||
if (this.selected.addressId) {
|
||||
body = Object.assign(body, {
|
||||
address: {
|
||||
id: this.selected.addressId,
|
||||
},
|
||||
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)
|
||||
.then((response) => {
|
||||
this.$store.dispatch("addAvailableLocationGroup", {
|
||||
locationGroup: "Localisations nouvellement créées",
|
||||
locations: [response],
|
||||
});
|
||||
this.$store.dispatch("updateLocation", response);
|
||||
this.modal.showModal = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.name === "ValidationException") {
|
||||
for (let v of error.violations) {
|
||||
this.errors.push(v);
|
||||
}
|
||||
} else {
|
||||
this.errors.push("An error occurred");
|
||||
}
|
||||
});
|
||||
makeFetch("POST", "/api/1.0/main/location.json", body)
|
||||
.then((response) => {
|
||||
this.$store.dispatch("addAvailableLocationGroup", {
|
||||
locationGroup: "Localisations nouvellement créées",
|
||||
locations: [response],
|
||||
});
|
||||
this.$store.dispatch("updateLocation", response);
|
||||
this.modal.showModal = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.name === "ValidationException") {
|
||||
for (let v of error.violations) {
|
||||
this.errors.push(v);
|
||||
}
|
||||
} else {
|
||||
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>
|
||||
|
||||
@@ -1,103 +1,98 @@
|
||||
<template>
|
||||
<teleport to="#social-issues-acc">
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialIssuesClassList">{{
|
||||
trans(ACTIVITY_SOCIAL_ISSUES)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<check-social-issue
|
||||
v-for="issue in socialIssuesList"
|
||||
:key="issue.id"
|
||||
:issue="issue"
|
||||
:selection="socialIssuesSelected"
|
||||
@updateSelected="updateIssuesSelected"
|
||||
>
|
||||
</check-social-issue>
|
||||
<teleport to="#social-issues-acc">
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialIssuesClassList">{{
|
||||
trans(ACTIVITY_SOCIAL_ISSUES)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<check-social-issue
|
||||
v-for="issue in socialIssuesList"
|
||||
:key="issue.id"
|
||||
:issue="issue"
|
||||
:selection="socialIssuesSelected"
|
||||
@updateSelected="updateIssuesSelected"
|
||||
>
|
||||
</check-social-issue>
|
||||
|
||||
<div class="my-3">
|
||||
<VueMultiselect
|
||||
name="otherIssues"
|
||||
label="text"
|
||||
track-by="id"
|
||||
open-direction="bottom"
|
||||
:close-on-select="true"
|
||||
:preserve-search="false"
|
||||
:reset-after="true"
|
||||
:hide-selected="true"
|
||||
:taggable="false"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:allow-empty="true"
|
||||
:show-labels="false"
|
||||
:loading="issueIsLoading"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||
:options="socialIssuesOther"
|
||||
@select="addIssueInList"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<VueMultiselect
|
||||
name="otherIssues"
|
||||
label="text"
|
||||
track-by="id"
|
||||
open-direction="bottom"
|
||||
:close-on-select="true"
|
||||
:preserve-search="false"
|
||||
:reset-after="true"
|
||||
:hide-selected="true"
|
||||
:taggable="false"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:allow-empty="true"
|
||||
:show-labels="false"
|
||||
:loading="issueIsLoading"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||
:options="socialIssuesOther"
|
||||
@select="addIssueInList"
|
||||
>
|
||||
</VueMultiselect>
|
||||
</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 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>
|
||||
<span
|
||||
v-else-if="socialIssuesSelected.length === 0"
|
||||
class="inline-choice chill-no-data-statement mt-3"
|
||||
>
|
||||
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-else-if="socialIssuesSelected.length === 0"
|
||||
class="inline-choice chill-no-data-statement mt-3"
|
||||
>
|
||||
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||
</span>
|
||||
<template
|
||||
v-else-if="
|
||||
socialActionsList.length > 0 &&
|
||||
(socialIssuesSelected.length || socialActionsSelected.length)
|
||||
"
|
||||
>
|
||||
<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
|
||||
v-else-if="
|
||||
socialActionsList.length > 0 &&
|
||||
(socialIssuesSelected.length ||
|
||||
socialActionsSelected.length)
|
||||
"
|
||||
>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
@@ -106,175 +101,174 @@ import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
|
||||
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
||||
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
||||
import {
|
||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||
ACTIVITY_SOCIAL_ACTIONS,
|
||||
ACTIVITY_SOCIAL_ISSUES,
|
||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||
trans,
|
||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||
ACTIVITY_SOCIAL_ACTIONS,
|
||||
ACTIVITY_SOCIAL_ISSUES,
|
||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "SocialIssuesAcc",
|
||||
components: {
|
||||
CheckSocialIssue,
|
||||
CheckSocialAction,
|
||||
VueMultiselect,
|
||||
name: "SocialIssuesAcc",
|
||||
components: {
|
||||
CheckSocialIssue,
|
||||
CheckSocialAction,
|
||||
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": true,
|
||||
required: false,
|
||||
},
|
||||
socialActionsClassList: {
|
||||
"col-form-label": true,
|
||||
required: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
socialIssuesList() {
|
||||
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
||||
},
|
||||
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,
|
||||
};
|
||||
socialIssuesSelected() {
|
||||
return this.$store.state.activity.socialIssues;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
issueIsLoading: false,
|
||||
actionIsLoading: false,
|
||||
actionAreLoaded: false,
|
||||
socialIssuesClassList: {
|
||||
"col-form-label": true,
|
||||
required: false,
|
||||
},
|
||||
socialActionsClassList: {
|
||||
"col-form-label": true,
|
||||
required: false,
|
||||
},
|
||||
};
|
||||
socialIssuesOther() {
|
||||
return this.$store.state.socialIssuesOther;
|
||||
},
|
||||
computed: {
|
||||
socialIssuesList() {
|
||||
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;
|
||||
},
|
||||
socialActionsList() {
|
||||
return this.$store.getters.socialActionsListSorted;
|
||||
},
|
||||
mounted() {
|
||||
/* Load classNames after element is present */
|
||||
const socialActionsEl = document.querySelector(
|
||||
"input#chill_activitybundle_activity_socialActions",
|
||||
);
|
||||
if (socialActionsEl && socialActionsEl.hasAttribute("required")) {
|
||||
this.socialActionsClassList.required = true;
|
||||
socialActionsSelected() {
|
||||
return this.$store.state.activity.socialActions;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
/* Load classNames after element is present */
|
||||
const socialActionsEl = document.querySelector(
|
||||
"input#chill_activitybundle_activity_socialActions",
|
||||
);
|
||||
if (socialActionsEl && socialActionsEl.hasAttribute("required")) {
|
||||
this.socialActionsClassList.required = true;
|
||||
}
|
||||
|
||||
const socialIssuesEl = document.querySelector(
|
||||
"input#chill_activitybundle_activity_socialIssues",
|
||||
);
|
||||
if (socialIssuesEl && socialIssuesEl.hasAttribute("required")) {
|
||||
this.socialIssuesClassList.required = true;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
});
|
||||
|
||||
const socialIssuesEl = document.querySelector(
|
||||
"input#chill_activitybundle_activity_socialIssues",
|
||||
);
|
||||
if (socialIssuesEl && socialIssuesEl.hasAttribute("required")) {
|
||||
this.socialIssuesClassList.required = true;
|
||||
}
|
||||
/* Remove from multiselect the issues that are not yet in the checkbox list */
|
||||
this.socialIssuesList.forEach((issue) => {
|
||||
this.$store.commit("removeIssueInOther", issue);
|
||||
});
|
||||
|
||||
/* Load other issues in multiselect */
|
||||
this.issueIsLoading = true;
|
||||
this.actionAreLoaded = false;
|
||||
/* Filter issues */
|
||||
this.$store.commit("filterList", "issues");
|
||||
|
||||
getSocialIssues().then((response) => {
|
||||
/* Add issues to the store */
|
||||
this.$store.commit("updateIssuesOther", response);
|
||||
/* Add in list the actions already associated (if not yet listed) */
|
||||
this.socialActionsSelected.forEach((action) => {
|
||||
this.$store.commit("addActionInList", action);
|
||||
});
|
||||
|
||||
/* 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);
|
||||
}
|
||||
});
|
||||
/* Filter actions */
|
||||
this.$store.commit("filterList", "actions");
|
||||
|
||||
/* 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),
|
||||
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
|
||||
*/
|
||||
addIssueInList(value) {
|
||||
//console.log('addIssueInList', value);
|
||||
this.$store.commit("addIssueInList", value);
|
||||
this.$store.commit("removeIssueInOther", value);
|
||||
this.$store.dispatch("addIssueSelected", value);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected issues checkboxes
|
||||
*/
|
||||
updateIssuesSelected(issues) {
|
||||
//console.log('updateIssuesSelected', issues);
|
||||
this.$store.dispatch("updateIssuesSelected", issues);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected actions checkboxes
|
||||
*/
|
||||
updateActionsSelected(actions) {
|
||||
//console.log('updateActionsSelected', actions);
|
||||
this.$store.dispatch("updateActionsSelected", actions);
|
||||
},
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
addIssueInList(value) {
|
||||
//console.log('addIssueInList', value);
|
||||
this.$store.commit("addIssueInList", value);
|
||||
this.$store.commit("removeIssueInOther", value);
|
||||
this.$store.dispatch("addIssueSelected", value);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected issues checkboxes
|
||||
*/
|
||||
updateIssuesSelected(issues) {
|
||||
//console.log('updateIssuesSelected', issues);
|
||||
this.$store.dispatch("updateIssuesSelected", issues);
|
||||
this.updateActionsList();
|
||||
},
|
||||
/* Update value for selected actions checkboxes
|
||||
*/
|
||||
updateActionsSelected(actions) {
|
||||
//console.log('updateActionsSelected', actions);
|
||||
this.$store.dispatch("updateActionsSelected", actions);
|
||||
},
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
to get social actions concerned
|
||||
*/
|
||||
updateActionsList() {
|
||||
this.resetActionsList();
|
||||
this.socialIssuesSelected.forEach((item) => {
|
||||
this.actionIsLoading = true;
|
||||
getSocialActionByIssue(item.id).then(
|
||||
(actions) =>
|
||||
new Promise((resolve) => {
|
||||
actions.results.forEach((action) => {
|
||||
this.$store.commit("addActionInList", action);
|
||||
}, this);
|
||||
updateActionsList() {
|
||||
this.resetActionsList();
|
||||
this.socialIssuesSelected.forEach((item) => {
|
||||
this.actionIsLoading = true;
|
||||
getSocialActionByIssue(item.id).then(
|
||||
(actions) =>
|
||||
new Promise((resolve) => {
|
||||
actions.results.forEach((action) => {
|
||||
this.$store.commit("addActionInList", action);
|
||||
}, this);
|
||||
|
||||
this.$store.commit("filterList", "actions");
|
||||
this.$store.commit("filterList", "actions");
|
||||
|
||||
this.actionIsLoading = false;
|
||||
this.actionAreLoaded = true;
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
}, 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);
|
||||
},
|
||||
this.actionIsLoading = false;
|
||||
this.actionAreLoaded = true;
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
}, 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>
|
||||
|
||||
@@ -284,18 +278,18 @@ export default {
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
|
||||
span.multiselect__single {
|
||||
display: none !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#actionsList {
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem;
|
||||
background-color: whitesmoke;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
span.badge {
|
||||
margin-bottom: 0.5rem;
|
||||
@include badge_social($social-issue-color);
|
||||
margin-bottom: 0.5rem;
|
||||
@include badge_social($social-issue-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="action"
|
||||
:id="action.id"
|
||||
:value="action"
|
||||
/>
|
||||
<label class="form-check-label" :for="action.id">
|
||||
<span class="badge bg-light text-dark" :title="action.text">{{
|
||||
action.text
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="action"
|
||||
:id="action.id"
|
||||
:value="action"
|
||||
/>
|
||||
<label class="form-check-label" :for="action.id">
|
||||
<span class="badge bg-light text-dark" :title="action.text">{{
|
||||
action.text
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "CheckSocialAction",
|
||||
props: ["action", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
name: "CheckSocialAction",
|
||||
props: ["action", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -41,25 +41,24 @@ export default {
|
||||
@import "ChillPersonAssets/chill/scss/mixins";
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
span.badge {
|
||||
@include badge_social($social-action-color);
|
||||
font-size: 95%;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
text-align: left;
|
||||
line-height: 1.2em;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 0;
|
||||
margin: 0 0.3em 0 -0.75em;
|
||||
}
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
@include badge_social($social-action-color);
|
||||
font-size: 95%;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
text-align: left;
|
||||
line-height: 1.2em;
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 0;
|
||||
margin: 0 0.3em 0 -0.75em;
|
||||
}
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
<template>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="issue"
|
||||
:id="issue.id"
|
||||
:value="issue"
|
||||
/>
|
||||
<label class="form-check-label" :for="issue.id">
|
||||
<span class="badge bg-chill-l-gray text-dark">{{
|
||||
issue.text
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="issue"
|
||||
:id="issue.id"
|
||||
:value="issue"
|
||||
/>
|
||||
<label class="form-check-label" :for="issue.id">
|
||||
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "CheckSocialIssue",
|
||||
props: ["issue", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
name: "CheckSocialIssue",
|
||||
props: ["issue", "selection"],
|
||||
emits: ["updateSelected"],
|
||||
computed: {
|
||||
selected: {
|
||||
set(value) {
|
||||
this.$emit("updateSelected", value);
|
||||
},
|
||||
get() {
|
||||
return this.selection;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -41,24 +39,24 @@ export default {
|
||||
@import "ChillPersonAssets/chill/scss/mixins";
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
span.badge {
|
||||
@include badge_social($social-issue-color);
|
||||
font-size: 95%;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
text-align: left;
|
||||
@include badge_social($social-issue-color);
|
||||
font-size: 95%;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
text-align: left;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 0;
|
||||
margin: 0 0.3em 0 -0.75em;
|
||||
}
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 0;
|
||||
margin: 0 0.3em 0 -0.75em;
|
||||
}
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,8 @@ use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
|
||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
|
||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter\AbstractStoredObjectVoter;
|
||||
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
|
||||
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelperInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
class ActivityStoredObjectVoter extends AbstractStoredObjectVoter
|
||||
@@ -24,9 +25,10 @@ class ActivityStoredObjectVoter extends AbstractStoredObjectVoter
|
||||
public function __construct(
|
||||
private readonly ActivityRepository $repository,
|
||||
Security $security,
|
||||
WorkflowRelatedEntityPermissionHelper $workflowDocumentService,
|
||||
WorkflowRelatedEntityPermissionHelperInterface $workflowDocumentService,
|
||||
EntityWorkflowAttachmentRepository $attachmentRepository,
|
||||
) {
|
||||
parent::__construct($security, $workflowDocumentService);
|
||||
parent::__construct($security, $attachmentRepository, $workflowDocumentService);
|
||||
}
|
||||
|
||||
protected function getRepository(): AssociatedEntityToStoredObjectInterface
|
||||
|
||||
@@ -39,7 +39,7 @@ final class Version20251118124241 extends AbstractMigration
|
||||
$this->addSql("COMMENT ON COLUMN activity_user.by_migration IS 'For backup purpose - can be safely deleted after a while. See migration \\Chill\\Migrations\\Activity\\Version20251118124241'");
|
||||
|
||||
$this->addSql('INSERT INTO activity_user (activity_id, user_id, by_migration)
|
||||
SELECT id, user_id, true FROM activity
|
||||
SELECT id, user_id, true FROM activity WHERE user_id is not null
|
||||
ON CONFLICT DO NOTHING');
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class LoadAsideActivity extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
public function __construct(private readonly UserRepository $userRepository) {}
|
||||
public function __construct(private readonly UserRepository $userRepository)
|
||||
{
|
||||
mt_srand(123456789);
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
@@ -47,7 +50,7 @@ class LoadAsideActivity extends Fixture implements DependentFixtureInterface
|
||||
$this->getReference('aside_activity_category_0', AsideActivityCategory::class)
|
||||
)
|
||||
->setDate((new \DateTimeImmutable('today'))
|
||||
->sub(new \DateInterval('P'.\random_int(1, 100).'D')));
|
||||
->sub(new \DateInterval('P'.\mt_rand(1, 100).'D')));
|
||||
|
||||
$manager->persist($activity);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac
|
||||
}
|
||||
|
||||
/** (non-PHPdoc).
|
||||
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
|
||||
* @see PrependExtensionInterface::prepend()
|
||||
*/
|
||||
public function prependRoutes(ContainerBuilder $container)
|
||||
{
|
||||
|
||||
@@ -72,14 +72,20 @@
|
||||
|
||||
{% macro table_results(actualCharges, actualResources, results) %}
|
||||
|
||||
{% set now = date() %}
|
||||
|
||||
{% set totalCharges = 0 %}
|
||||
{% for c in actualCharges %}
|
||||
{% set totalCharges = totalCharges + c.amount %}
|
||||
{% if c.startDate <= now and (c.endDate is null or c.endDate >= now) %}
|
||||
{% set totalCharges = totalCharges + c.amount %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set totalResources = 0 %}
|
||||
{% for r in actualResources %}
|
||||
{% set totalResources = totalResources + r.amount %}
|
||||
{% if r.startDate <= now and (r.endDate is null or r.endDate >= now) %}
|
||||
{% set totalResources = totalResources + r.amount %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set result = (totalResources - totalCharges) %}
|
||||
|
||||
@@ -1,76 +1,81 @@
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import {
|
||||
DateTime,
|
||||
Location,
|
||||
User,
|
||||
UserAssociatedInterface,
|
||||
DateTime,
|
||||
Location,
|
||||
User,
|
||||
UserAssociatedInterface,
|
||||
} from "../../../ChillMainBundle/Resources/public/types";
|
||||
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
|
||||
|
||||
export interface CalendarRange {
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
user: User;
|
||||
location: Location;
|
||||
createdAt: DateTime;
|
||||
createdBy: User;
|
||||
updatedAt: DateTime;
|
||||
updatedBy: User;
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
user: User;
|
||||
location: Location;
|
||||
createdAt: DateTime;
|
||||
createdBy: User;
|
||||
updatedAt: DateTime;
|
||||
updatedBy: User;
|
||||
}
|
||||
|
||||
export interface CalendarRangeCreate {
|
||||
user: UserAssociatedInterface;
|
||||
startDate: DateTime;
|
||||
endDate: DateTime;
|
||||
location: Location;
|
||||
user: UserAssociatedInterface;
|
||||
startDate: DateTime;
|
||||
endDate: DateTime;
|
||||
location: Location;
|
||||
}
|
||||
|
||||
export interface CalendarRangeEdit {
|
||||
startDate?: DateTime;
|
||||
endDate?: DateTime;
|
||||
location?: Location;
|
||||
startDate?: DateTime;
|
||||
endDate?: DateTime;
|
||||
location?: Location;
|
||||
}
|
||||
|
||||
export interface Calendar {
|
||||
id: number;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface CalendarLight {
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
mainUser: User;
|
||||
persons: Person[];
|
||||
status: "valid" | "moved" | "canceled";
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
mainUser: User;
|
||||
persons: Person[];
|
||||
status: "valid" | "moved" | "canceled";
|
||||
}
|
||||
|
||||
export interface CalendarRemote {
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
title: string;
|
||||
isAllDay: boolean;
|
||||
id: number;
|
||||
endDate: DateTime;
|
||||
startDate: DateTime;
|
||||
title: string;
|
||||
isAllDay: boolean;
|
||||
}
|
||||
|
||||
export type EventInputCalendarRange = EventInput & {
|
||||
id: string;
|
||||
userId: number;
|
||||
userLabel: string;
|
||||
calendarRangeId: number;
|
||||
locationId: number;
|
||||
locationName: string;
|
||||
start: string;
|
||||
end: string;
|
||||
is: "range";
|
||||
id: string;
|
||||
userId: number;
|
||||
userLabel: string;
|
||||
calendarRangeId: number;
|
||||
locationId: number;
|
||||
locationName: string;
|
||||
start: string;
|
||||
end: string;
|
||||
is: "range";
|
||||
};
|
||||
|
||||
export function isEventInputCalendarRange(
|
||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||
): toBeDetermined is EventInputCalendarRange {
|
||||
return (
|
||||
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
|
||||
);
|
||||
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
|
||||
}
|
||||
|
||||
export enum AnswerStatus {
|
||||
ACCEPTED = "accepted",
|
||||
DECLINED = "declined",
|
||||
PENDING = "pending",
|
||||
TENTATIVE = "tentative",
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
@@ -1,166 +1,148 @@
|
||||
<template>
|
||||
<teleport to="#mainUser">
|
||||
<h2 class="chill-red">Utilisateur principal</h2>
|
||||
<div>
|
||||
<div>
|
||||
<div v-if="null !== 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>
|
||||
<teleport to="#mainUser">
|
||||
<h2 class="chill-red">Utilisateur principal</h2>
|
||||
<div>
|
||||
<div>
|
||||
<div v-if="null !== this.$store.getters.getMainUser">
|
||||
<calendar-active :user="this.$store.getters.getMainUser" />
|
||||
</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">
|
||||
<div class="row mb-3" v-if="activity.startDate !== null">
|
||||
<label class="col-form-label col-sm-4">Date</label>
|
||||
<div class="col-sm-8">
|
||||
{{ $d(activity.startDate, "long") }} -
|
||||
{{ $d(activity.endDate, "hoursOnly") }}
|
||||
<span v-if="activity.calendarRange === null"
|
||||
>(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"
|
||||
<teleport to="#schedule">
|
||||
<div class="row mb-3" v-if="activity.startDate !== null">
|
||||
<label class="col-form-label col-sm-4">Date</label>
|
||||
<div class="col-sm-8">
|
||||
{{ $d(activity.startDate, "long") }} -
|
||||
{{ $d(activity.endDate, "hoursOnly") }}
|
||||
<span v-if="activity.calendarRange === null"
|
||||
>(Pas de plage de disponibilité sélectionnée)</span
|
||||
>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="slotDuration"
|
||||
>Durée des créneaux</label
|
||||
>
|
||||
<select
|
||||
v-model="slotDuration"
|
||||
id="slotDuration"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:05:00">5 minutes</option>
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
<option value="00:45:00">45 minutes</option>
|
||||
<option value="00:60:00">60 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select
|
||||
v-model="slotMinTime"
|
||||
id="slotMinTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="00:00:00">0h</option>
|
||||
<option value="01:00:00">1h</option>
|
||||
<option value="02:00:00">2h</option>
|
||||
<option value="03:00:00">3h</option>
|
||||
<option value="04:00:00">4h</option>
|
||||
<option value="05:00:00">5h</option>
|
||||
<option value="06:00:00">6h</option>
|
||||
<option value="07:00:00">7h</option>
|
||||
<option value="08:00:00">8h</option>
|
||||
<option value="09:00:00">9h</option>
|
||||
<option value="10:00:00">10h</option>
|
||||
<option value="11:00:00">11h</option>
|
||||
<option value="12:00:00">12h</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||
<select
|
||||
v-model="slotMaxTime"
|
||||
id="slotMaxTime"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="12:00:00">12h</option>
|
||||
<option value="13:00:00">13h</option>
|
||||
<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-sm-3 col-xs-12">
|
||||
<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="hideWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label
|
||||
for="showHideWE"
|
||||
class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="slotDuration"
|
||||
>Durée des créneaux</label
|
||||
>
|
||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
||||
<option value="00:05:00">5 minutes</option>
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
<option value="00:45:00">45 minutes</option>
|
||||
<option value="00:60:00">60 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
||||
<option value="00:00:00">0h</option>
|
||||
<option value="01:00:00">1h</option>
|
||||
<option value="02:00:00">2h</option>
|
||||
<option value="03:00:00">3h</option>
|
||||
<option value="04:00:00">4h</option>
|
||||
<option value="05:00:00">5h</option>
|
||||
<option value="06:00:00">6h</option>
|
||||
<option value="07:00:00">7h</option>
|
||||
<option value="08:00:00">8h</option>
|
||||
<option value="09:00:00">9h</option>
|
||||
<option value="10:00:00">10h</option>
|
||||
<option value="11:00:00">11h</option>
|
||||
<option value="12:00:00">12h</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
||||
<option value="12:00:00">12h</option>
|
||||
<option value="13:00:00">13h</option>
|
||||
<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>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||
<template #eventContent="arg">
|
||||
<span>
|
||||
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<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="hideWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label for="showHideWE" class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||
<template #eventContent="arg">
|
||||
<span>
|
||||
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
@@ -177,219 +159,210 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
Location,
|
||||
FullCalendar,
|
||||
CalendarActive,
|
||||
PickEntity,
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
Location,
|
||||
FullCalendar,
|
||||
CalendarActive,
|
||||
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() {
|
||||
return {
|
||||
errorMsg: [],
|
||||
showMyCalendar: false,
|
||||
slotDuration: "00:05:00",
|
||||
slotMinTime: "09:00:00",
|
||||
slotMaxTime: "18:00:00",
|
||||
hideWeekEnds: true,
|
||||
previousUser: [],
|
||||
};
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getMainUser"]),
|
||||
...mapState(["activity"]),
|
||||
events() {
|
||||
return this.$store.getters.getEventSources;
|
||||
},
|
||||
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;
|
||||
},
|
||||
getActiveUsers() {
|
||||
const users = [];
|
||||
for (const id of this.$store.state.currentView.users.keys()) {
|
||||
users.push(this.$store.getters.getUserDataById(id).user);
|
||||
}
|
||||
return users;
|
||||
},
|
||||
methods: {
|
||||
setMainUser({ entity }) {
|
||||
const user = entity;
|
||||
console.log("setMainUser APP", entity);
|
||||
suggestedUsers() {
|
||||
const suggested = [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
this.$data.previousUser.forEach((u) => {
|
||||
if (u.id !== this.$store.getters.getMainUser.id) {
|
||||
suggested.push(u);
|
||||
}
|
||||
});
|
||||
|
||||
// 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,
|
||||
});
|
||||
},
|
||||
return suggested;
|
||||
},
|
||||
},
|
||||
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>
|
||||
|
||||
<style>
|
||||
.calendar-actives {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.display-options {
|
||||
margin-top: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* for events which are range */
|
||||
.fc-event.isrange {
|
||||
border-width: 3px;
|
||||
border-width: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,119 +1,103 @@
|
||||
<template>
|
||||
<div :style="style" class="calendar-active">
|
||||
<span class="badge-user">
|
||||
{{ user.text }}
|
||||
<template v-if="invite !== null">
|
||||
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||
<i
|
||||
v-else-if="invite.status === 'declined'"
|
||||
class="fa fa-times"
|
||||
/>
|
||||
<i
|
||||
v-else-if="invite.status === 'pending'"
|
||||
class="fa fa-question-o"
|
||||
/>
|
||||
<i
|
||||
v-else-if="invite.status === 'tentative'"
|
||||
class="fa fa-question"
|
||||
/>
|
||||
<span v-else="">{{ invite.status }}</span>
|
||||
</template>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="rangeShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Disponibilités"
|
||||
><i class="fa fa-calendar-check-o"
|
||||
/></label>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="remoteShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Agenda"
|
||||
><i class="fa fa-calendar"
|
||||
/></label>
|
||||
</span>
|
||||
</div>
|
||||
<div :style="style" class="calendar-active">
|
||||
<span class="badge-user">
|
||||
{{ user.text }}
|
||||
<template v-if="invite !== null">
|
||||
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||
<i v-else-if="invite.status === 'declined'" class="fa fa-times" />
|
||||
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
|
||||
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
|
||||
<span v-else>{{ invite.status }}</span>
|
||||
</template>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="rangeShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Disponibilités"
|
||||
><i class="fa fa-calendar-check-o"
|
||||
/></label>
|
||||
</span>
|
||||
<span class="form-check-inline form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="flexSwitchCheckDefault"
|
||||
v-model="remoteShow"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
for="flexSwitchCheckDefault"
|
||||
title="Agenda"
|
||||
><i class="fa fa-calendar"
|
||||
/></label>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "CalendarActive",
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
invite: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
name: "CalendarActive",
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
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,
|
||||
);
|
||||
},
|
||||
},
|
||||
invite: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.calendar-active {
|
||||
margin: 0 0.25rem 0.25rem 0;
|
||||
padding: 0.5rem;
|
||||
margin: 0 0.25rem 0.25rem 0;
|
||||
padding: 0.5rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
color: var(--bs-blue);
|
||||
color: var(--bs-blue);
|
||||
|
||||
& > .badge-user {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
& > .badge-user {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
|
||||
* @return Promise
|
||||
*/
|
||||
export const fetchCalendarRangeForUser = (
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
): Promise<CalendarRange[]> => {
|
||||
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
|
||||
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
export const fetchCalendarRemoteForUser = (
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
): Promise<CalendarRemote[]> => {
|
||||
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
|
||||
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
export const fetchCalendarLocalForUser = (
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
user: User,
|
||||
start: Date,
|
||||
end: Date,
|
||||
): Promise<CalendarLight[]> => {
|
||||
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
||||
const dateFrom = datetimeToISO(start);
|
||||
const dateTo = datetimeToISO(end);
|
||||
|
||||
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
const COLORS = [
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
];
|
||||
|
||||
export { COLORS };
|
||||
|
||||
@@ -24,6 +24,14 @@ const appMessages = {
|
||||
list_three_days: "Liste 3 jours",
|
||||
current_selected: "Rendez-vous fixé",
|
||||
main_user: "Utilisateur principal",
|
||||
Give_an_answer: "Répondre",
|
||||
Accepted: "Accepté",
|
||||
Declined: "Refusé",
|
||||
Tentative: "Accepté provisoirement",
|
||||
Accept: "Accepter",
|
||||
Decline: "Refuser",
|
||||
Tentatively_accept: "Accepter provisoirement",
|
||||
Set_pending: "Ne pas répondre",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
import { COLORS } from "../const";
|
||||
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import {
|
||||
DateTime,
|
||||
User,
|
||||
DateTime,
|
||||
User,
|
||||
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
|
||||
import type { EventInputCalendarRange } from "../../../types";
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
|
||||
export interface UserData {
|
||||
user: User;
|
||||
calendarRanges: CalendarRange[];
|
||||
calendarRangesLoaded: {}[];
|
||||
remotes: CalendarRemote[];
|
||||
remotesLoaded: {}[];
|
||||
locals: CalendarRemote[];
|
||||
localsLoaded: {}[];
|
||||
mainColor: string;
|
||||
user: User;
|
||||
calendarRanges: CalendarRange[];
|
||||
calendarRangesLoaded: {}[];
|
||||
remotes: CalendarRemote[];
|
||||
remotesLoaded: {}[];
|
||||
locals: CalendarRemote[];
|
||||
localsLoaded: {}[];
|
||||
mainColor: string;
|
||||
}
|
||||
|
||||
export const addIdToValue = (string: string, id: number): string => {
|
||||
const array = string ? string.split(",") : [];
|
||||
array.push(id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
const array = string ? string.split(",") : [];
|
||||
array.push(id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
export const removeIdFromValue = (string: string, id: number) => {
|
||||
let array = string.split(",");
|
||||
array = array.filter((el) => el !== id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
let array = string.split(",");
|
||||
array = array.filter((el) => el !== id.toString());
|
||||
const str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
/*
|
||||
* Assign missing keys for the ConcernedGroups component
|
||||
*/
|
||||
export const mapEntity = (entity: EventInput): EventInput => {
|
||||
const calendar = { ...entity };
|
||||
Object.assign(calendar, { thirdParties: entity.professionals });
|
||||
const calendar = { ...entity };
|
||||
Object.assign(calendar, { thirdParties: entity.professionals });
|
||||
|
||||
if (entity.startDate !== null) {
|
||||
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
||||
}
|
||||
if (entity.endDate !== null) {
|
||||
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
||||
}
|
||||
if (entity.startDate !== null) {
|
||||
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
||||
}
|
||||
if (entity.endDate !== null) {
|
||||
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
||||
}
|
||||
|
||||
if (entity.calendarRange !== null) {
|
||||
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
||||
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
||||
}
|
||||
if (entity.calendarRange !== null) {
|
||||
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
||||
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
||||
}
|
||||
|
||||
return calendar;
|
||||
return calendar;
|
||||
};
|
||||
|
||||
export const createUserData = (user: User, colorIndex: number): UserData => {
|
||||
const colorId = colorIndex % COLORS.length;
|
||||
const colorId = colorIndex % COLORS.length;
|
||||
|
||||
return {
|
||||
user: user,
|
||||
calendarRanges: [],
|
||||
calendarRangesLoaded: [],
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
mainColor: COLORS[colorId],
|
||||
};
|
||||
return {
|
||||
user: user,
|
||||
calendarRanges: [],
|
||||
calendarRangesLoaded: [],
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
mainColor: COLORS[colorId],
|
||||
};
|
||||
};
|
||||
|
||||
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
||||
export const calendarRangeToFullCalendarEvent = (
|
||||
entity: CalendarRange,
|
||||
entity: CalendarRange,
|
||||
): EventInputCalendarRange => {
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: "(" + entity.user.text + ")",
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
userId: entity.user.id,
|
||||
userLabel: entity.user.label,
|
||||
calendarRangeId: entity.id,
|
||||
locationId: entity.location.id,
|
||||
locationName: entity.location.name,
|
||||
is: "range",
|
||||
};
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: "(" + entity.user.text + ")",
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
userId: entity.user.id,
|
||||
userLabel: entity.user.label,
|
||||
calendarRangeId: entity.id,
|
||||
locationId: entity.location.id,
|
||||
locationName: entity.location.name,
|
||||
is: "range",
|
||||
};
|
||||
};
|
||||
|
||||
export const remoteToFullCalendarEvent = (
|
||||
entity: CalendarRemote,
|
||||
entity: CalendarRemote,
|
||||
): EventInput & { id: string } => {
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: entity.title,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: entity.isAllDay,
|
||||
is: "remote",
|
||||
};
|
||||
return {
|
||||
id: `range_${entity.id}`,
|
||||
title: entity.title,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: entity.isAllDay,
|
||||
is: "remote",
|
||||
};
|
||||
};
|
||||
|
||||
export const localsToFullCalendarEvent = (
|
||||
entity: CalendarLight,
|
||||
entity: CalendarLight,
|
||||
): EventInput & { id: string; originId: number } => {
|
||||
return {
|
||||
id: `local_${entity.id}`,
|
||||
title: entity.persons.map((p) => p.text).join(", "),
|
||||
originId: entity.id,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
is: "local",
|
||||
};
|
||||
return {
|
||||
id: `local_${entity.id}`,
|
||||
title: entity.persons.map((p) => p.text).join(", "),
|
||||
originId: entity.id,
|
||||
start: entity.startDate.datetime8601,
|
||||
end: entity.endDate.datetime8601,
|
||||
allDay: false,
|
||||
is: "local",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,133 +1,84 @@
|
||||
<template>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
id="btnGroupDrop1"
|
||||
type="button"
|
||||
class="btn btn-misc dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
id="btnGroupDrop1"
|
||||
type="button"
|
||||
class="btn btn-misc dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
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">
|
||||
<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
|
||||
>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
<script lang="ts" setup>
|
||||
import { AnswerStatus } from "../../types";
|
||||
|
||||
const ACCEPTED = "accepted";
|
||||
const DECLINED = "declined";
|
||||
const PENDING = "pending";
|
||||
const TENTATIVELY_ACCEPTED = "tentative";
|
||||
const props = defineProps<{
|
||||
calendarId: number;
|
||||
status: AnswerStatus;
|
||||
}>();
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
Give_an_answer: "Répondre",
|
||||
Accepted: "Accepté",
|
||||
Declined: "Refusé",
|
||||
Tentative: "Accepté provisoirement",
|
||||
Accept: "Accepter",
|
||||
Decline: "Refuser",
|
||||
Tentatively_accept: "Accepter provisoirement",
|
||||
Set_pending: "Ne pas répondre",
|
||||
},
|
||||
},
|
||||
const emit =
|
||||
defineEmits<(e: "statusChanged", newStatus: AnswerStatus) => void>();
|
||||
|
||||
const Statuses = {
|
||||
ACCEPTED: AnswerStatus.ACCEPTED,
|
||||
DECLINED: AnswerStatus.DECLINED,
|
||||
PENDING: AnswerStatus.PENDING,
|
||||
TENTATIVELY_ACCEPTED: AnswerStatus.TENTATIVE,
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "Answer",
|
||||
i18n,
|
||||
props: {
|
||||
calendarId: { type: Number, required: true },
|
||||
status: {
|
||||
type: String as PropType<
|
||||
"accepted" | "declined" | "pending" | "tentative"
|
||||
>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
statusChanged(
|
||||
payload: "accepted" | "declined" | "pending" | "tentative",
|
||||
) {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Statuses: {
|
||||
ACCEPTED,
|
||||
DECLINED,
|
||||
PENDING,
|
||||
TENTATIVELY_ACCEPTED,
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeStatus: function (
|
||||
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
||||
) {
|
||||
console.log("changeStatus", newStatus);
|
||||
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
|
||||
window
|
||||
.fetch(url, {
|
||||
method: "POST",
|
||||
})
|
||||
.then((r: Response) => {
|
||||
if (!r.ok) {
|
||||
console.error("could not confirm answer", newStatus);
|
||||
return;
|
||||
}
|
||||
console.log("answer sent", newStatus);
|
||||
this.$emit("statusChanged", newStatus);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
function changeStatus(newStatus: AnswerStatus) {
|
||||
const url = `/api/1.0/calendar/calendar/${props.calendarId}/answer/${newStatus}.json`;
|
||||
window
|
||||
.fetch(url, {
|
||||
method: "POST",
|
||||
})
|
||||
.then((r: Response) => {
|
||||
if (!r.ok) {
|
||||
console.error("could not confirm answer", newStatus);
|
||||
return;
|
||||
}
|
||||
emit("statusChanged", newStatus);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -346,6 +346,7 @@ const baseOptions = ref<CalendarOptions>({
|
||||
center: "title",
|
||||
right: "timeGridWeek,timeGridDay",
|
||||
},
|
||||
allDaySlot: false,
|
||||
});
|
||||
|
||||
const ranges = computed<EventInput[]>(() => {
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<component :is="Teleport" to="body">
|
||||
<modal v-if="showModal" @close="closeModal">
|
||||
<template v-slot:header>
|
||||
<h3>{{ "Modifier le lieu" }}</h3>
|
||||
</template>
|
||||
<component :is="Teleport" to="body">
|
||||
<modal v-if="showModal" @close="closeModal">
|
||||
<template v-slot:header>
|
||||
<h3>{{ "Modifier le lieu" }}</h3>
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<div></div>
|
||||
<label>Localisation</label>
|
||||
<vue-multiselect
|
||||
v-model="location"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
></vue-multiselect>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<div></div>
|
||||
<label>Localisation</label>
|
||||
<vue-multiselect
|
||||
v-model="location"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
></vue-multiselect>
|
||||
</template>
|
||||
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-save" @click="saveAndClose">
|
||||
{{ "Enregistrer" }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</component>
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-save" @click="saveAndClose">
|
||||
{{ "Enregistrer" }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
|
||||
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
||||
|
||||
const Teleport = teleport_ as new () => {
|
||||
$props: VNodeProps & TeleportProps;
|
||||
$props: VNodeProps & TeleportProps;
|
||||
};
|
||||
|
||||
const store = useStore(key);
|
||||
@@ -50,37 +50,37 @@ const showModal = ref(false);
|
||||
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
|
||||
|
||||
const locations = computed<Location[]>(() => {
|
||||
return store.state.locations.locations;
|
||||
return store.state.locations.locations;
|
||||
});
|
||||
|
||||
const startEdit = function (event: EventApi): void {
|
||||
console.log("startEditing", event);
|
||||
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
||||
location.value =
|
||||
store.getters["locations/getLocationById"](
|
||||
event.extendedProps.locationId,
|
||||
) || null;
|
||||
console.log("startEditing", event);
|
||||
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
||||
location.value =
|
||||
store.getters["locations/getLocationById"](
|
||||
event.extendedProps.locationId,
|
||||
) || null;
|
||||
|
||||
console.log("new location value", location.value);
|
||||
console.log("calendar range id", calendarRangeId.value);
|
||||
showModal.value = true;
|
||||
console.log("new location value", location.value);
|
||||
console.log("calendar range id", calendarRangeId.value);
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
const saveAndClose = function (e: Event): void {
|
||||
console.log("saveEditAndClose", e);
|
||||
console.log("saveEditAndClose", e);
|
||||
|
||||
store
|
||||
.dispatch("calendarRanges/patchRangeLocation", {
|
||||
location: location.value,
|
||||
calendarRangeId: calendarRangeId.value,
|
||||
})
|
||||
.then((_) => {
|
||||
showModal.value = false;
|
||||
});
|
||||
store
|
||||
.dispatch("calendarRanges/patchRangeLocation", {
|
||||
location: location.value,
|
||||
calendarRangeId: calendarRangeId.value,
|
||||
})
|
||||
.then(() => {
|
||||
showModal.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = function (_: any): void {
|
||||
showModal.value = false;
|
||||
const closeModal = function (): void {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
defineExpose({ startEdit });
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
const appMessages = {
|
||||
fr: {
|
||||
created_availabilities: "Lieu des plages de disponibilités créées",
|
||||
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
||||
show_my_calendar: "Afficher mon calendrier",
|
||||
show_weekends: "Afficher les week-ends",
|
||||
copy_range: "Copier",
|
||||
copy_range_from_to: "Copier les plages",
|
||||
from_day_to_day: "d'un jour à l'autre",
|
||||
from_week_to_week: "d'une semaine à l'autre",
|
||||
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.",
|
||||
new_range_to_save: "Nouvelles plages à enregistrer",
|
||||
update_range_to_save: "Plages à modifier",
|
||||
delete_range_to_save: "Plages à supprimer",
|
||||
by: "Par",
|
||||
main_user_concerned: "Utilisateur concerné",
|
||||
dateFrom: "De",
|
||||
dateTo: "à",
|
||||
day: "Jour",
|
||||
week: "Semaine",
|
||||
month: "Mois",
|
||||
today: "Aujourd'hui",
|
||||
},
|
||||
fr: {
|
||||
created_availabilities: "Lieu des plages de disponibilités créées",
|
||||
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
||||
show_my_calendar: "Afficher mon calendrier",
|
||||
show_weekends: "Afficher les week-ends",
|
||||
copy_range: "Copier",
|
||||
copy_range_from_to: "Copier les plages",
|
||||
from_day_to_day: "d'un jour à l'autre",
|
||||
from_week_to_week: "d'une semaine à l'autre",
|
||||
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.",
|
||||
new_range_to_save: "Nouvelles plages à enregistrer",
|
||||
update_range_to_save: "Plages à modifier",
|
||||
delete_range_to_save: "Plages à supprimer",
|
||||
by: "Par",
|
||||
main_user_concerned: "Utilisateur concerné",
|
||||
dateFrom: "De",
|
||||
dateTo: "à",
|
||||
day: "Jour",
|
||||
week: "Semaine",
|
||||
month: "Mois",
|
||||
today: "Aujourd'hui",
|
||||
},
|
||||
};
|
||||
|
||||
export { appMessages };
|
||||
|
||||
@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
futureStore().then((store) => {
|
||||
const i18n = _createI18n(appMessages, false);
|
||||
const i18n = _createI18n(appMessages, false);
|
||||
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
})
|
||||
.use(store, key)
|
||||
.use(i18n)
|
||||
.component("app", App2)
|
||||
.mount("#myCalendar");
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
})
|
||||
.use(store, key)
|
||||
.use(i18n)
|
||||
.component("app", App2)
|
||||
.mount("#myCalendar");
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
|
||||
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
||||
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
||||
import calendarRemotes, {
|
||||
CalendarRemotesState,
|
||||
CalendarRemotesState,
|
||||
} from "./modules/calendarRemotes";
|
||||
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
@@ -15,42 +15,40 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
|
||||
const debug = process.env.NODE_ENV !== "production";
|
||||
|
||||
export interface State {
|
||||
calendarRanges: CalendarRangesState;
|
||||
calendarRemotes: CalendarRemotesState;
|
||||
calendarLocals: CalendarLocalsState;
|
||||
fullCalendar: FullCalendarState;
|
||||
me: MeState;
|
||||
locations: LocationState;
|
||||
calendarRanges: CalendarRangesState;
|
||||
calendarRemotes: CalendarRemotesState;
|
||||
calendarLocals: CalendarLocalsState;
|
||||
fullCalendar: FullCalendarState;
|
||||
me: MeState;
|
||||
locations: LocationState;
|
||||
}
|
||||
|
||||
export const key: InjectionKey<Store<State>> = Symbol();
|
||||
|
||||
const futureStore = function (): Promise<Store<State>> {
|
||||
return whoami().then((user: User) => {
|
||||
const store = createStore<State>({
|
||||
strict: debug,
|
||||
modules: {
|
||||
me,
|
||||
fullCalendar,
|
||||
calendarRanges,
|
||||
calendarRemotes,
|
||||
calendarLocals,
|
||||
locations,
|
||||
},
|
||||
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);
|
||||
return whoami().then((user: User) => {
|
||||
const store = createStore<State>({
|
||||
strict: debug,
|
||||
modules: {
|
||||
me,
|
||||
fullCalendar,
|
||||
calendarRanges,
|
||||
calendarRemotes,
|
||||
calendarLocals,
|
||||
locations,
|
||||
},
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
export default futureStore;
|
||||
|
||||
@@ -8,109 +8,99 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
||||
import { COLORS } from "../../../Calendar/const";
|
||||
|
||||
export interface CalendarLocalsState {
|
||||
locals: EventInput[];
|
||||
localsLoaded: { start: number; end: number }[];
|
||||
localsIndex: Set<string>;
|
||||
key: number;
|
||||
locals: EventInput[];
|
||||
localsLoaded: { start: number; end: number }[];
|
||||
localsIndex: Set<string>;
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<CalendarLocalsState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): CalendarLocalsState => ({
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
localsIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isLocalsLoaded:
|
||||
(state: CalendarLocalsState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.localsLoaded) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
namespaced: true,
|
||||
state: (): CalendarLocalsState => ({
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
localsIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isLocalsLoaded:
|
||||
(state: CalendarLocalsState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.localsLoaded) {
|
||||
if (start.getTime() === range.start && 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: {
|
||||
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;
|
||||
},
|
||||
addLoaded(
|
||||
state: CalendarLocalsState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.localsLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
|
||||
state.localsLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
actions: {
|
||||
fetchLocals(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
},
|
||||
actions: {
|
||||
fetchLocals(
|
||||
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(null);
|
||||
}
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarLocalForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
)
|
||||
.then((remotes: CalendarLight[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => localsToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||
.then((remotes: CalendarLight[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => localsToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
} as Module<CalendarLocalsState, State>;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { State } from "./../index";
|
||||
import { ActionContext, Module } from "vuex";
|
||||
import {
|
||||
CalendarRange,
|
||||
CalendarRangeCreate,
|
||||
CalendarRangeEdit,
|
||||
isEventInputCalendarRange,
|
||||
CalendarRange,
|
||||
CalendarRangeCreate,
|
||||
CalendarRangeEdit,
|
||||
isEventInputCalendarRange,
|
||||
} from "../../../../types";
|
||||
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
|
||||
@@ -12,369 +12,332 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import {
|
||||
datetimeToISO,
|
||||
dateToISO,
|
||||
ISOToDatetime,
|
||||
datetimeToISO,
|
||||
dateToISO,
|
||||
ISOToDatetime,
|
||||
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import type { EventInputCalendarRange } from "../../../../types";
|
||||
|
||||
export interface CalendarRangesState {
|
||||
ranges: (EventInput | EventInputCalendarRange)[];
|
||||
rangesLoaded: { start: number; end: number }[];
|
||||
rangesIndex: Set<string>;
|
||||
key: number;
|
||||
ranges: (EventInput | EventInputCalendarRange)[];
|
||||
rangesLoaded: { start: number; end: number }[];
|
||||
rangesIndex: Set<string>;
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<CalendarRangesState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): CalendarRangesState => ({
|
||||
ranges: [],
|
||||
rangesLoaded: [],
|
||||
rangesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRangeLoaded:
|
||||
(state: CalendarRangesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.rangesLoaded) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
namespaced: true,
|
||||
state: (): CalendarRangesState => ({
|
||||
ranges: [],
|
||||
rangesLoaded: [],
|
||||
rangesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRangeLoaded:
|
||||
(state: CalendarRangesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.rangesLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getRangesOnDate:
|
||||
(state: CalendarRangesState) =>
|
||||
(date: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
const dateStr = dateToISO(date) as string;
|
||||
return false;
|
||||
},
|
||||
getRangesOnDate:
|
||||
(state: CalendarRangesState) =>
|
||||
(date: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
const dateStr = dateToISO(date) as string;
|
||||
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
|
||||
return founds;
|
||||
},
|
||||
getRangesOnWeek:
|
||||
(state: CalendarRangesState) =>
|
||||
(mondayDate: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
for (const d of Array.from(Array(7).keys())) {
|
||||
const dateOfWeek = new Date(mondayDate);
|
||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||
const dateStr = dateToISO(dateOfWeek) as string;
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
return founds;
|
||||
},
|
||||
getRangesOnWeek:
|
||||
(state: CalendarRangesState) =>
|
||||
(mondayDate: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
for (const d of Array.from(Array(7).keys())) {
|
||||
const dateOfWeek = new Date(mondayDate);
|
||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||
const dateStr = dateToISO(dateOfWeek) as string;
|
||||
for (const range of state.ranges) {
|
||||
if (
|
||||
isEventInputCalendarRange(range) &&
|
||||
range.start.startsWith(dateStr)
|
||||
) {
|
||||
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: {
|
||||
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));
|
||||
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;
|
||||
},
|
||||
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;
|
||||
},
|
||||
toAdd.forEach((r) => {
|
||||
state.rangesIndex.add(r.id);
|
||||
state.ranges.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
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));
|
||||
},
|
||||
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: {
|
||||
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>;
|
||||
|
||||
@@ -8,109 +8,102 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
||||
import { COLORS } from "../../../Calendar/const";
|
||||
|
||||
export interface CalendarRemotesState {
|
||||
remotes: EventInput[];
|
||||
remotesLoaded: { start: number; end: number }[];
|
||||
remotesIndex: Set<string>;
|
||||
key: number;
|
||||
remotes: EventInput[];
|
||||
remotesLoaded: { start: number; end: number }[];
|
||||
remotesIndex: Set<string>;
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<CalendarRemotesState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): CalendarRemotesState => ({
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
remotesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRemotesLoaded:
|
||||
(state: CalendarRemotesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.remotesLoaded) {
|
||||
if (
|
||||
start.getTime() === range.start &&
|
||||
end.getTime() === range.end
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
namespaced: true,
|
||||
state: (): CalendarRemotesState => ({
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
remotesIndex: new Set<string>(),
|
||||
key: 0,
|
||||
}),
|
||||
getters: {
|
||||
isRemotesLoaded:
|
||||
(state: CalendarRemotesState) =>
|
||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||
for (const range of state.remotesLoaded) {
|
||||
if (start.getTime() === range.start && 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: {
|
||||
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;
|
||||
},
|
||||
addLoaded(
|
||||
state: CalendarRemotesState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.remotesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
addLoaded(
|
||||
state: CalendarRemotesState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.remotesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
actions: {
|
||||
fetchRemotes(
|
||||
ctx: Context,
|
||||
payload: { start: Date; end: Date },
|
||||
): Promise<null> {
|
||||
const start = payload.start;
|
||||
const end = payload.end;
|
||||
},
|
||||
actions: {
|
||||
fetchRemotes(
|
||||
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(null);
|
||||
}
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
ctx.commit("addLoaded", {
|
||||
start: start,
|
||||
end: end,
|
||||
});
|
||||
|
||||
return fetchCalendarRemoteForUser(
|
||||
ctx.rootGetters["me/getMe"],
|
||||
start,
|
||||
end,
|
||||
)
|
||||
.then((remotes: CalendarRemote[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end)
|
||||
.then((remotes: CalendarRemote[]) => {
|
||||
// to be add when reactivity problem will be solve ?
|
||||
//ctx.commit('addRemotes', remotes);
|
||||
const inputs = remotes
|
||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||
.map((cr) => ({
|
||||
...cr,
|
||||
backgroundColor: COLORS[0],
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
}));
|
||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||
root: true,
|
||||
});
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((e: TransportExceptionInterface) => {
|
||||
console.error(e);
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
} as Module<CalendarRemotesState, State>;
|
||||
|
||||
@@ -2,77 +2,77 @@ import { State } from "./../index";
|
||||
import { ActionContext } from "vuex";
|
||||
|
||||
export interface FullCalendarState {
|
||||
currentView: {
|
||||
start: Date | null;
|
||||
end: Date | null;
|
||||
};
|
||||
key: number;
|
||||
currentView: {
|
||||
start: Date | null;
|
||||
end: Date | null;
|
||||
};
|
||||
key: number;
|
||||
}
|
||||
|
||||
type Context = ActionContext<FullCalendarState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): FullCalendarState => ({
|
||||
currentView: {
|
||||
start: 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;
|
||||
},
|
||||
namespaced: true,
|
||||
state: (): FullCalendarState => ({
|
||||
currentView: {
|
||||
start: null,
|
||||
end: null,
|
||||
},
|
||||
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);
|
||||
}
|
||||
},
|
||||
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: {
|
||||
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";
|
||||
|
||||
export interface LocationState {
|
||||
locations: Location[];
|
||||
locationPicked: Location | null;
|
||||
currentLocation: Location | null;
|
||||
locations: Location[];
|
||||
locationPicked: Location | null;
|
||||
currentLocation: Location | null;
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): LocationState => {
|
||||
return {
|
||||
locations: [],
|
||||
locationPicked: null,
|
||||
currentLocation: null,
|
||||
};
|
||||
namespaced: true,
|
||||
state: (): LocationState => {
|
||||
return {
|
||||
locations: [],
|
||||
locationPicked: 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: {
|
||||
getLocationById:
|
||||
(state) =>
|
||||
(id: number): Location | undefined => {
|
||||
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;
|
||||
}
|
||||
setLocationPicked(state, location: Location | null): void {
|
||||
if (null === location) {
|
||||
state.locationPicked = null;
|
||||
return;
|
||||
}
|
||||
|
||||
state.locationPicked =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
setCurrentLocation(state, location: Location | null): void {
|
||||
if (null === location) {
|
||||
state.currentLocation = null;
|
||||
return;
|
||||
}
|
||||
state.locationPicked =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
setCurrentLocation(state, location: Location | null): void {
|
||||
if (null === location) {
|
||||
state.currentLocation = null;
|
||||
return;
|
||||
}
|
||||
|
||||
state.currentLocation =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
state.currentLocation =
|
||||
state.locations.find((l) => l.id === location.id) || null;
|
||||
},
|
||||
actions: {
|
||||
getLocations(ctx): Promise<void> {
|
||||
return getLocations().then((locations) => {
|
||||
ctx.commit("setLocations", locations);
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
getCurrentLocation(ctx): Promise<void> {
|
||||
return whereami().then((location) => {
|
||||
ctx.commit("setCurrentLocation", location);
|
||||
});
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getLocations(ctx): Promise<void> {
|
||||
return getLocations().then((locations) => {
|
||||
ctx.commit("setLocations", locations);
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
getCurrentLocation(ctx): Promise<void> {
|
||||
return whereami().then((location) => {
|
||||
ctx.commit("setCurrentLocation", location);
|
||||
});
|
||||
},
|
||||
},
|
||||
} as Module<LocationState, State>;
|
||||
|
||||
@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
|
||||
import { ActionContext } from "vuex";
|
||||
|
||||
export interface MeState {
|
||||
me: User | null;
|
||||
me: User | null;
|
||||
}
|
||||
|
||||
type Context = ActionContext<MeState, State>;
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: (): MeState => ({
|
||||
me: null,
|
||||
}),
|
||||
getters: {
|
||||
getMe: function (state: MeState): User | null {
|
||||
return state.me;
|
||||
},
|
||||
namespaced: true,
|
||||
state: (): MeState => ({
|
||||
me: null,
|
||||
}),
|
||||
getters: {
|
||||
getMe: function (state: MeState): User | null {
|
||||
return state.me;
|
||||
},
|
||||
mutations: {
|
||||
setWhoAmi(state: MeState, me: User) {
|
||||
state.me = me;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setWhoAmi(state: MeState, me: User) {
|
||||
state.me = me;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="chill-red">
|
||||
{{ $t("choose_your_calendar_user") }}
|
||||
</h2>
|
||||
<VueMultiselect
|
||||
name="field"
|
||||
id="calendarUserSelector"
|
||||
v-model="value"
|
||||
track-by="id"
|
||||
label="value"
|
||||
:custom-label="transName"
|
||||
:placeholder="$t('select_user')"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:allow-empty="true"
|
||||
:model-value="value"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@select="selectUsers"
|
||||
@remove="unSelectUsers"
|
||||
@close="coloriseSelectedValues"
|
||||
:options="options"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="myCalendar"
|
||||
class="form-check-input"
|
||||
v-model="showMyCalendarWidget"
|
||||
/>
|
||||
<label class="form-check-label" for="myCalendar">{{
|
||||
$t("show_my_calendar")
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="weekends"
|
||||
class="form-check-input"
|
||||
@click="toggleWeekends"
|
||||
/>
|
||||
<label class="form-check-label" for="weekends">{{
|
||||
$t("show_weekends")
|
||||
}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="chill-red">
|
||||
{{ $t("choose_your_calendar_user") }}
|
||||
</h2>
|
||||
<VueMultiselect
|
||||
name="field"
|
||||
id="calendarUserSelector"
|
||||
v-model="value"
|
||||
track-by="id"
|
||||
label="value"
|
||||
:custom-label="transName"
|
||||
:placeholder="$t('select_user')"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:allow-empty="true"
|
||||
:model-value="value"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@select="selectUsers"
|
||||
@remove="unSelectUsers"
|
||||
@close="coloriseSelectedValues"
|
||||
:options="options"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="myCalendar"
|
||||
class="form-check-input"
|
||||
v-model="showMyCalendarWidget"
|
||||
/>
|
||||
<label class="form-check-label" for="myCalendar">{{
|
||||
$t("show_my_calendar")
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="weekends"
|
||||
class="form-check-input"
|
||||
@click="toggleWeekends"
|
||||
/>
|
||||
<label class="form-check-label" for="weekends">{{
|
||||
$t("show_weekends")
|
||||
}}</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
|
||||
@@ -53,206 +53,183 @@ import VueMultiselect from "vue-multiselect";
|
||||
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
|
||||
|
||||
const COLORS = [
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
];
|
||||
|
||||
export default {
|
||||
name: "CalendarUserSelector",
|
||||
components: { VueMultiselect },
|
||||
props: [
|
||||
"users",
|
||||
"updateEventsSource",
|
||||
"calendarEvents",
|
||||
"showMyCalendar",
|
||||
"toggleMyCalendar",
|
||||
"toggleWeekends",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
errorMsg: [],
|
||||
value: [],
|
||||
options: [],
|
||||
};
|
||||
name: "CalendarUserSelector",
|
||||
components: { VueMultiselect },
|
||||
props: [
|
||||
"users",
|
||||
"updateEventsSource",
|
||||
"calendarEvents",
|
||||
"showMyCalendar",
|
||||
"toggleMyCalendar",
|
||||
"toggleWeekends",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
errorMsg: [],
|
||||
value: [],
|
||||
options: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showMyCalendarWidget: {
|
||||
set(value) {
|
||||
this.toggleMyCalendar(value);
|
||||
this.updateEventsSource();
|
||||
},
|
||||
get() {
|
||||
return this.showMyCalendar;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showMyCalendarWidget: {
|
||||
set(value) {
|
||||
this.toggleMyCalendar(value);
|
||||
this.updateEventsSource();
|
||||
},
|
||||
get() {
|
||||
return this.showMyCalendar;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.fetchData();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.fetchData();
|
||||
},
|
||||
fetchData() {
|
||||
fetchCalendarRanges()
|
||||
.then(
|
||||
(calendarRanges) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let results = calendarRanges.results;
|
||||
fetchData() {
|
||||
fetchCalendarRanges()
|
||||
.then(
|
||||
(calendarRanges) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let results = calendarRanges.results;
|
||||
|
||||
let users = [];
|
||||
let users = [];
|
||||
|
||||
results.forEach((i) => {
|
||||
if (!users.some((j) => i.user.id === j.id)) {
|
||||
let ratio = Math.floor(
|
||||
users.length / COLORS.length,
|
||||
);
|
||||
let colorIndex =
|
||||
users.length - ratio * COLORS.length;
|
||||
users.push({
|
||||
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";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
results.forEach((i) => {
|
||||
if (!users.some((j) => i.user.id === j.id)) {
|
||||
let ratio = Math.floor(users.length / COLORS.length);
|
||||
let colorIndex = users.length - ratio * COLORS.length;
|
||||
users.push({
|
||||
id: i.user.id,
|
||||
username: i.user.username,
|
||||
color: COLORS[colorIndex],
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
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();
|
||||
},
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
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 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>
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
{% if calendar.status == 'canceled' %}
|
||||
<del>
|
||||
{% endif %}
|
||||
{% if calendar.status == 'canceled' %}
|
||||
<del>
|
||||
{% endif %}
|
||||
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||
- {{ calendar.endDate|format_datetime('short', 'short') }}
|
||||
@@ -75,7 +78,7 @@
|
||||
</div>
|
||||
|
||||
{% if calendar.comment.comment is not empty
|
||||
or calendar.users|length > 0
|
||||
or calendar.persons|length > 0
|
||||
or calendar.thirdParties|length > 0
|
||||
or calendar.users|length > 0 %}
|
||||
<div class="item-row details separator">
|
||||
@@ -237,6 +240,10 @@
|
||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_cancel', { 'id': calendar.id } ) }}"
|
||||
class="btn btn-action"><i class="bi bi-x-circle"></i> {{ 'Cancel'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_cancel', { 'id': calendar.id } ) }}"
|
||||
class="btn btn-action"><i class="bi bi-x-circle"></i> {{ 'Cancel'|trans }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
|
||||
|
||||
@@ -41,6 +41,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
mt_srand(123456789);
|
||||
$this->fakerFr = \Faker\Factory::create('fr_FR');
|
||||
$this->fakerEn = \Faker\Factory::create('en_EN');
|
||||
$this->fakerNl = \Faker\Factory::create('nl_NL');
|
||||
@@ -104,7 +105,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface
|
||||
$manager->persist($parent);
|
||||
|
||||
// Load children
|
||||
$expected_nb_children = random_int(10, 50);
|
||||
$expected_nb_children = mt_rand(10, 50);
|
||||
|
||||
for ($i = 0; $i < $expected_nb_children; ++$i) {
|
||||
$companyName = $this->fakerFr->company;
|
||||
@@ -144,7 +145,7 @@ class LoadOption extends AbstractFixture implements OrderedFixtureInterface
|
||||
$manager->persist($parent);
|
||||
|
||||
// Load children
|
||||
$expected_nb_children = random_int(10, 50);
|
||||
$expected_nb_children = mt_rand(10, 50);
|
||||
|
||||
for ($i = 0; $i < $expected_nb_children; ++$i) {
|
||||
$manager->persist($this->createChildOption($parent, [
|
||||
|
||||
@@ -52,7 +52,7 @@ class ChillCustomFieldsExtension extends Extension implements PrependExtensionIn
|
||||
}
|
||||
|
||||
/** (non-PHPdoc).
|
||||
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
|
||||
* @see PrependExtensionInterface::prepend()
|
||||
*/
|
||||
public function prepend(ContainerBuilder $container)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ class ChoiceWithOtherType extends AbstractType
|
||||
private string $otherValueLabel = 'Other value';
|
||||
|
||||
/** (non-PHPdoc).
|
||||
* @see \Symfony\Component\Form\AbstractType::buildForm()
|
||||
* @see AbstractType::buildForm()
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
@@ -42,7 +42,7 @@ class ChoiceWithOtherType extends AbstractType
|
||||
}
|
||||
|
||||
/** (non-PHPdoc).
|
||||
* @see \Symfony\Component\Form\AbstractType::configureOptions()
|
||||
* @see AbstractType::configureOptions()
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ use Symfony\Component\Form\FormEvents;
|
||||
class ChoicesListType extends AbstractType
|
||||
{
|
||||
/** (non-PHPdoc).
|
||||
* @see \Symfony\Component\Form\AbstractType::buildForm()
|
||||
* @see AbstractType::buildForm()
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ class CustomFieldProvider implements ContainerAwareInterface
|
||||
/**
|
||||
* (non-PHPdoc).
|
||||
*
|
||||
* @see \Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer()
|
||||
* @see ContainerAwareInterface::setContainer()
|
||||
*/
|
||||
public function setContainer(?ContainerInterface $container = null)
|
||||
{
|
||||
|
||||
@@ -1,59 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="templates.length > 0">
|
||||
<slot name="title">
|
||||
<h2>{{ $t("generate_document") }}</h2>
|
||||
</slot>
|
||||
<div>
|
||||
<template v-if="templates.length > 0">
|
||||
<slot name="title">
|
||||
<h2>{{ $t("generate_document") }}</h2>
|
||||
</slot>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<slot name="label">
|
||||
<label>{{ $t("select_a_template") }}</label>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group mb-3">
|
||||
<select class="form-select" v-model="template">
|
||||
<option disabled selected value="">
|
||||
{{ $t("choose_a_template") }}
|
||||
</option>
|
||||
<template v-for="t in templates" :key="t.id">
|
||||
<option :value="t.id">
|
||||
{{
|
||||
localizeString(t.name) ||
|
||||
"Aucun nom défini"
|
||||
}}
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
<a
|
||||
v-if="canGenerate"
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
:href="buildUrlGenerate"
|
||||
@click.prevent="
|
||||
clickGenerate($event, buildUrlGenerate)
|
||||
"
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
<a
|
||||
v-else
|
||||
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 class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<slot name="label">
|
||||
<label>{{ $t("select_a_template") }}</label>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group mb-3">
|
||||
<select class="form-select" v-model="template">
|
||||
<option disabled selected value="">
|
||||
{{ $t("choose_a_template") }}
|
||||
</option>
|
||||
<template v-for="t in templates" :key="t.id">
|
||||
<option :value="t.id">
|
||||
{{ localizeString(t.name) || "Aucun nom défini" }}
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
<a
|
||||
v-if="canGenerate"
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
:href="buildUrlGenerate"
|
||||
@click.prevent="clickGenerate($event, buildUrlGenerate)"
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
<a
|
||||
v-else
|
||||
class="btn btn-update btn-sm change-icon"
|
||||
href="#"
|
||||
disabled
|
||||
><i class="fa fa-fw fa-cog"
|
||||
/></a>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
@@ -61,83 +56,83 @@ import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
|
||||
export default {
|
||||
name: "PickTemplate",
|
||||
props: {
|
||||
entityId: [String, Number],
|
||||
entityClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
templates: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
preventDefaultMoveToGenerate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
name: "PickTemplate",
|
||||
props: {
|
||||
entityId: [String, Number],
|
||||
entityClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
emits: ["goToGenerateDocument"],
|
||||
data() {
|
||||
return {
|
||||
template: null,
|
||||
};
|
||||
templates: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
canGenerate() {
|
||||
return this.template != null;
|
||||
},
|
||||
hasDescription() {
|
||||
if (this.template == null) {
|
||||
return false;
|
||||
}
|
||||
preventDefaultMoveToGenerate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["goToGenerateDocument"],
|
||||
data() {
|
||||
return {
|
||||
template: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canGenerate() {
|
||||
return this.template != null;
|
||||
},
|
||||
hasDescription() {
|
||||
if (this.template == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
getDescription() {
|
||||
if (null === this.template) {
|
||||
return "";
|
||||
}
|
||||
let desc = this.templates.find((t) => t.id === this.template);
|
||||
if (null === desc) {
|
||||
return "";
|
||||
}
|
||||
return desc.description || "";
|
||||
},
|
||||
buildUrlGenerate() {
|
||||
if (null === this.template) {
|
||||
return "#";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getDescription() {
|
||||
if (null === this.template) {
|
||||
return "";
|
||||
}
|
||||
let desc = this.templates.find((t) => t.id === this.template);
|
||||
if (null === desc) {
|
||||
return "";
|
||||
}
|
||||
return desc.description || "";
|
||||
},
|
||||
buildUrlGenerate() {
|
||||
if (null === this.template) {
|
||||
return "#";
|
||||
}
|
||||
|
||||
return buildLink(this.template, this.entityId, this.entityClass);
|
||||
},
|
||||
return buildLink(this.template, this.entityId, this.entityClass);
|
||||
},
|
||||
methods: {
|
||||
localizeString(str) {
|
||||
return localizeString(str);
|
||||
},
|
||||
clickGenerate(event, link) {
|
||||
if (!this.preventDefaultMoveToGenerate) {
|
||||
window.location.assign(link);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
localizeString(str) {
|
||||
return localizeString(str);
|
||||
},
|
||||
clickGenerate(event, link) {
|
||||
if (!this.preventDefaultMoveToGenerate) {
|
||||
window.location.assign(link);
|
||||
}
|
||||
|
||||
this.$emit("goToGenerateDocument", {
|
||||
event,
|
||||
link,
|
||||
template: this.template,
|
||||
});
|
||||
},
|
||||
this.$emit("goToGenerateDocument", {
|
||||
event,
|
||||
link,
|
||||
template: this.template,
|
||||
});
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
generate_document: "Générer un document",
|
||||
select_a_template: "Choisir un modèle",
|
||||
choose_a_template: "Choisir",
|
||||
},
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
generate_document: "Générer un document",
|
||||
select_a_template: "Choisir un modèle",
|
||||
choose_a_template: "Choisir",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface
|
||||
/**
|
||||
* @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)]
|
||||
#[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true, fetch: 'EAGER')]
|
||||
private Collection&Selectable $versions;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,20 +6,20 @@ const algo = "AES-CBC";
|
||||
const URL_POST = "/asyncupload/temp_url/generate/post";
|
||||
|
||||
const keyDefinition = {
|
||||
name: algo,
|
||||
length: 256,
|
||||
name: algo,
|
||||
length: 256,
|
||||
};
|
||||
|
||||
const createFilename = (): string => {
|
||||
let text = "";
|
||||
const possible =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let text = "";
|
||||
const possible =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
for (let i = 0; i < 7; i++) {
|
||||
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.
|
||||
*/
|
||||
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 (
|
||||
uploadFile: ArrayBuffer,
|
||||
storedObject: StoredObject,
|
||||
uploadFile: ArrayBuffer,
|
||||
storedObject: StoredObject,
|
||||
): Promise<string> => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("expires_delay", "180");
|
||||
params.append("submit_delay", "180");
|
||||
const asyncData: PostStoreObjectSignature = await makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
||||
"?" +
|
||||
params.toString(),
|
||||
);
|
||||
const suffix = createFilename();
|
||||
const filename = asyncData.prefix + suffix;
|
||||
const formData = new FormData();
|
||||
formData.append("redirect", asyncData.redirect);
|
||||
formData.append("max_file_size", asyncData.max_file_size.toString());
|
||||
formData.append("max_file_count", asyncData.max_file_count.toString());
|
||||
formData.append("expires", asyncData.expires.toString());
|
||||
formData.append("signature", asyncData.signature);
|
||||
formData.append(filename, new Blob([uploadFile]), suffix);
|
||||
const params = new URLSearchParams();
|
||||
params.append("expires_delay", "180");
|
||||
params.append("submit_delay", "180");
|
||||
const asyncData: PostStoreObjectSignature = await makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
||||
"?" +
|
||||
params.toString(),
|
||||
);
|
||||
const suffix = createFilename();
|
||||
const filename = asyncData.prefix + suffix;
|
||||
const formData = new FormData();
|
||||
formData.append("redirect", asyncData.redirect);
|
||||
formData.append("max_file_size", asyncData.max_file_size.toString());
|
||||
formData.append("max_file_count", asyncData.max_file_count.toString());
|
||||
formData.append("expires", asyncData.expires.toString());
|
||||
formData.append("signature", asyncData.signature);
|
||||
formData.append(filename, new Blob([uploadFile]), suffix);
|
||||
|
||||
const response = await window.fetch(asyncData.url, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
const response = await window.fetch(asyncData.url, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Error while sending file to store", response);
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
if (!response.ok) {
|
||||
console.error("Error while sending file to store", response);
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
return Promise.resolve(filename);
|
||||
return Promise.resolve(filename);
|
||||
};
|
||||
|
||||
export const encryptFile = async (
|
||||
originalFile: ArrayBuffer,
|
||||
originalFile: ArrayBuffer,
|
||||
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
]);
|
||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{ name: algo, iv: iv },
|
||||
key,
|
||||
originalFile,
|
||||
);
|
||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
]);
|
||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{ name: algo, iv: iv },
|
||||
key,
|
||||
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";
|
||||
|
||||
export function fetch_generic_docs_by_accompanying_period(
|
||||
periodId: number,
|
||||
periodId: number,
|
||||
): Promise<GenericDocForAccompanyingPeriod[]> {
|
||||
return fetchResults(
|
||||
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
||||
);
|
||||
return fetchResults(
|
||||
`/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 startApp = (
|
||||
divElement: HTMLDivElement,
|
||||
collectionEntry: null | HTMLLIElement,
|
||||
divElement: HTMLDivElement,
|
||||
collectionEntry: null | HTMLLIElement,
|
||||
): 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 =
|
||||
divElement.querySelector("input[data-stored-object]");
|
||||
if (null === input_stored_object) {
|
||||
throw new Error("input to stored object not found");
|
||||
}
|
||||
const input_stored_object: HTMLInputElement | null = divElement.querySelector(
|
||||
"input[data-stored-object]",
|
||||
);
|
||||
if (null === input_stored_object) {
|
||||
throw new Error("input to stored object not found");
|
||||
}
|
||||
|
||||
let existingDoc: StoredObject | null = null;
|
||||
if (input_stored_object.value !== "") {
|
||||
existingDoc = JSON.parse(input_stored_object.value);
|
||||
}
|
||||
const app_container = document.createElement("div");
|
||||
divElement.appendChild(app_container);
|
||||
let existingDoc: StoredObject | null = null;
|
||||
if (input_stored_object.value !== "") {
|
||||
existingDoc = JSON.parse(input_stored_object.value);
|
||||
}
|
||||
const app_container = document.createElement("div");
|
||||
divElement.appendChild(app_container);
|
||||
|
||||
const app = createApp({
|
||||
template:
|
||||
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
||||
data() {
|
||||
return {
|
||||
existingDoc: existingDoc,
|
||||
inputTitle: inputTitle,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
DropFileWidget,
|
||||
},
|
||||
methods: {
|
||||
addDocument: function ({
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
}: {
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
}): void {
|
||||
stored_object.title = file_name;
|
||||
console.log("object added", stored_object);
|
||||
console.log("version added", stored_object_version);
|
||||
this.$data.existingDoc = stored_object;
|
||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||
input_stored_object.value = JSON.stringify(
|
||||
this.$data.existingDoc,
|
||||
);
|
||||
if (this.$data.inputTitle) {
|
||||
if (!this.$data.inputTitle?.value) {
|
||||
this.$data.inputTitle.value = file_name;
|
||||
}
|
||||
}
|
||||
},
|
||||
removeDocument: function (object: StoredObject): void {
|
||||
console.log("catch remove document", object);
|
||||
input_stored_object.value = "";
|
||||
this.$data.existingDoc = undefined;
|
||||
console.log("collectionEntry", collectionEntry);
|
||||
const app = createApp({
|
||||
template:
|
||||
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
||||
data() {
|
||||
return {
|
||||
existingDoc: existingDoc,
|
||||
inputTitle: inputTitle,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
DropFileWidget,
|
||||
},
|
||||
methods: {
|
||||
addDocument: function ({
|
||||
stored_object,
|
||||
stored_object_version,
|
||||
file_name,
|
||||
}: {
|
||||
stored_object: StoredObject;
|
||||
stored_object_version: StoredObjectVersion;
|
||||
file_name: string;
|
||||
}): void {
|
||||
stored_object.title = file_name;
|
||||
console.log("object added", stored_object);
|
||||
console.log("version added", stored_object_version);
|
||||
this.$data.existingDoc = stored_object;
|
||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
|
||||
if (this.$data.inputTitle) {
|
||||
if (!this.$data.inputTitle?.value) {
|
||||
this.$data.inputTitle.value = file_name;
|
||||
}
|
||||
}
|
||||
},
|
||||
removeDocument: function (object: StoredObject): void {
|
||||
console.log("catch remove document", object);
|
||||
input_stored_object.value = "";
|
||||
this.$data.existingDoc = undefined;
|
||||
console.log("collectionEntry", collectionEntry);
|
||||
|
||||
if (null !== collectionEntry) {
|
||||
console.log("will remove collection");
|
||||
collectionEntry.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (null !== collectionEntry) {
|
||||
console.log("will remove collection");
|
||||
collectionEntry.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.use(i18n).mount(app_container);
|
||||
app.use(i18n).mount(app_container);
|
||||
};
|
||||
window.addEventListener("collection-add-entry", ((
|
||||
e: CustomEvent<CollectionEventPayload>,
|
||||
e: CustomEvent<CollectionEventPayload>,
|
||||
) => {
|
||||
const detail = e.detail;
|
||||
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
const detail = e.detail;
|
||||
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
|
||||
if (null === divElement) {
|
||||
throw new Error("div[data-stored-object] not found");
|
||||
}
|
||||
if (null === divElement) {
|
||||
throw new Error("div[data-stored-object] not found");
|
||||
}
|
||||
|
||||
startApp(divElement, detail.entry);
|
||||
startApp(divElement, detail.entry);
|
||||
}) as EventListener);
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
||||
"div[data-stored-object]",
|
||||
);
|
||||
|
||||
upload_inputs.forEach((input: HTMLDivElement): void => {
|
||||
// test for a parent to check if this is a collection entry
|
||||
let collectionEntry: null | HTMLLIElement = null;
|
||||
const parent = input.parentElement;
|
||||
console.log("parent", parent);
|
||||
if (null !== parent) {
|
||||
const grandParent = parent.parentElement;
|
||||
console.log("grandParent", grandParent);
|
||||
if (null !== grandParent) {
|
||||
if (
|
||||
grandParent.tagName.toLowerCase() === "li" &&
|
||||
grandParent.classList.contains("entry")
|
||||
) {
|
||||
collectionEntry = grandParent as HTMLLIElement;
|
||||
}
|
||||
}
|
||||
upload_inputs.forEach((input: HTMLDivElement): void => {
|
||||
// test for a parent to check if this is a collection entry
|
||||
let collectionEntry: null | HTMLLIElement = null;
|
||||
const parent = input.parentElement;
|
||||
console.log("parent", parent);
|
||||
if (null !== parent) {
|
||||
const grandParent = parent.parentElement;
|
||||
console.log("grandParent", grandParent);
|
||||
if (null !== grandParent) {
|
||||
if (
|
||||
grandParent.tagName.toLowerCase() === "li" &&
|
||||
grandParent.classList.contains("entry")
|
||||
) {
|
||||
collectionEntry = grandParent as HTMLLIElement;
|
||||
}
|
||||
startApp(input, collectionEntry);
|
||||
});
|
||||
}
|
||||
}
|
||||
startApp(input, collectionEntry);
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
|
||||
@@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification";
|
||||
const i18n = _createI18n({});
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function (e) {
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
||||
.forEach((el) => {
|
||||
const storedObject = JSON.parse(
|
||||
el.dataset.storedObject as string,
|
||||
) as StoredObject;
|
||||
const title = el.dataset.title as string;
|
||||
const app = createApp({
|
||||
components: { DownloadButton },
|
||||
data() {
|
||||
return {
|
||||
storedObject,
|
||||
title,
|
||||
classes: { btn: true, "btn-outline-primary": true },
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
||||
});
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
||||
.forEach((el) => {
|
||||
const storedObject = JSON.parse(
|
||||
el.dataset.storedObject as string,
|
||||
) as StoredObject;
|
||||
const title = el.dataset.title as string;
|
||||
const app = createApp({
|
||||
components: { DownloadButton },
|
||||
data() {
|
||||
return {
|
||||
storedObject,
|
||||
title,
|
||||
classes: { btn: true, "btn-outline-primary": true },
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,72 +2,71 @@ import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
||||
import { createApp } from "vue";
|
||||
import { StoredObject, StoredObjectStatusChange } from "../../types";
|
||||
import { is_object_ready } from "../../vuejs/StoredObjectButton/helpers";
|
||||
import ToastPlugin from "vue-toast-notification";
|
||||
|
||||
const i18n = _createI18n({});
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function (e) {
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
||||
.forEach((el) => {
|
||||
const app = createApp({
|
||||
components: { DocumentActionButtonsGroup },
|
||||
data() {
|
||||
const datasets = el.dataset as {
|
||||
filename: string;
|
||||
canEdit: string;
|
||||
storedObject: string;
|
||||
buttonSmall: string;
|
||||
davLink: string;
|
||||
davLinkExpiration: string;
|
||||
};
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
document
|
||||
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
||||
.forEach((el) => {
|
||||
const app = createApp({
|
||||
components: { DocumentActionButtonsGroup },
|
||||
data() {
|
||||
const datasets = el.dataset as {
|
||||
filename: string;
|
||||
canEdit: string;
|
||||
storedObject: string;
|
||||
buttonSmall: string;
|
||||
davLink: string;
|
||||
davLinkExpiration: string;
|
||||
};
|
||||
|
||||
const storedObject = JSON.parse(
|
||||
datasets.storedObject,
|
||||
) as StoredObject,
|
||||
filename = datasets.filename,
|
||||
canEdit = datasets.canEdit === "1",
|
||||
small = datasets.buttonSmall === "1",
|
||||
davLink =
|
||||
"davLink" in datasets && datasets.davLink !== ""
|
||||
? datasets.davLink
|
||||
: null,
|
||||
davLinkExpiration =
|
||||
"davLinkExpiration" in datasets
|
||||
? Number.parseInt(datasets.davLinkExpiration)
|
||||
: null;
|
||||
return {
|
||||
storedObject,
|
||||
filename,
|
||||
canEdit,
|
||||
small,
|
||||
davLink,
|
||||
davLinkExpiration,
|
||||
};
|
||||
},
|
||||
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>',
|
||||
methods: {
|
||||
onStoredObjectStatusChange: function (
|
||||
newStatus: StoredObjectStatusChange,
|
||||
): void {
|
||||
this.$data.storedObject.status = newStatus.status;
|
||||
this.$data.storedObject.filename = newStatus.filename;
|
||||
this.$data.storedObject.type = newStatus.type;
|
||||
const storedObject = JSON.parse(
|
||||
datasets.storedObject,
|
||||
) as StoredObject,
|
||||
filename = datasets.filename,
|
||||
canEdit = datasets.canEdit === "1",
|
||||
small = datasets.buttonSmall === "1",
|
||||
davLink =
|
||||
"davLink" in datasets && datasets.davLink !== ""
|
||||
? datasets.davLink
|
||||
: null,
|
||||
davLinkExpiration =
|
||||
"davLinkExpiration" in datasets
|
||||
? Number.parseInt(datasets.davLinkExpiration)
|
||||
: null;
|
||||
return {
|
||||
storedObject,
|
||||
filename,
|
||||
canEdit,
|
||||
small,
|
||||
davLink,
|
||||
davLinkExpiration,
|
||||
};
|
||||
},
|
||||
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>',
|
||||
methods: {
|
||||
onStoredObjectStatusChange: function (
|
||||
newStatus: StoredObjectStatusChange,
|
||||
): void {
|
||||
this.$data.storedObject.status = newStatus.status;
|
||||
this.$data.storedObject.filename = newStatus.filename;
|
||||
this.$data.storedObject.type = newStatus.type;
|
||||
|
||||
// remove eventual div which inform pending status
|
||||
document
|
||||
.querySelectorAll(
|
||||
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
||||
)
|
||||
.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
// remove eventual div which inform pending status
|
||||
document
|
||||
.querySelectorAll(
|
||||
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
||||
)
|
||||
.forEach(function (el) {
|
||||
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";
|
||||
|
||||
export interface GenericDocMetadata {
|
||||
isPresent: boolean;
|
||||
isPresent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,69 +15,69 @@ export interface EmptyMetadata extends GenericDocMetadata {}
|
||||
* Minimal Metadata for a GenericDoc with a normalizer
|
||||
*/
|
||||
export interface BaseMetadata extends GenericDocMetadata {
|
||||
title: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
||||
*/
|
||||
export interface GenericDoc {
|
||||
type: "doc_store_generic_doc";
|
||||
uniqueKey: string;
|
||||
key: string;
|
||||
identifiers: { id: number };
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
metadata: GenericDocMetadata;
|
||||
storedObject: StoredObject | null;
|
||||
type: "doc_store_generic_doc";
|
||||
uniqueKey: string;
|
||||
key: string;
|
||||
identifiers: { id: number };
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
metadata: GenericDocMetadata;
|
||||
storedObject: StoredObject | null;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
||||
context: "accompanying-period";
|
||||
context: "accompanying-period";
|
||||
}
|
||||
|
||||
export function isGenericDocForAccompanyingPeriod(
|
||||
doc: GenericDoc,
|
||||
doc: GenericDoc,
|
||||
): doc is GenericDocForAccompanyingPeriod {
|
||||
return doc.context === "accompanying-period";
|
||||
return doc.context === "accompanying-period";
|
||||
}
|
||||
|
||||
export function isGenericDocWithStoredObject(
|
||||
doc: GenericDoc,
|
||||
doc: GenericDoc,
|
||||
): doc is GenericDoc & { storedObject: StoredObject } {
|
||||
return doc.storedObject !== null;
|
||||
return doc.storedObject !== null;
|
||||
}
|
||||
|
||||
interface BaseMetadataWithHtml extends BaseMetadata {
|
||||
html: string;
|
||||
html: string;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseDocument extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
key: "accompanying_course_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseActivityDocument extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_activity_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
key: "accompanying_course_activity_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseCalendarDocument extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_calendar_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
key: "accompanying_course_calendar_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCoursePersonDocument extends GenericDocForAccompanyingPeriod {
|
||||
key: "person_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
key: "person_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_period_work_evaluation_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
key: "accompanying_period_work_evaluation_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
@@ -4,71 +4,72 @@ import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpe
|
||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
||||
|
||||
export interface StoredObject {
|
||||
id: number;
|
||||
title: string | null;
|
||||
uuid: string;
|
||||
prefix: string;
|
||||
status: StoredObjectStatus;
|
||||
currentVersion:
|
||||
| null
|
||||
| StoredObjectVersionCreated
|
||||
| StoredObjectVersionPersisted;
|
||||
totalVersions: number;
|
||||
datas: object;
|
||||
/** @deprecated */
|
||||
creationDate: DateTime;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
_permissions: {
|
||||
canEdit: boolean;
|
||||
canSee: boolean;
|
||||
};
|
||||
_links?: {
|
||||
dav_link?: {
|
||||
href: string;
|
||||
expiration: number;
|
||||
};
|
||||
downloadLink?: SignedUrlGet;
|
||||
id: number;
|
||||
title: string | null;
|
||||
uuid: string;
|
||||
prefix: string;
|
||||
status: StoredObjectStatus;
|
||||
type: string;
|
||||
currentVersion:
|
||||
| null
|
||||
| StoredObjectVersionCreated
|
||||
| StoredObjectVersionPersisted;
|
||||
totalVersions: number;
|
||||
datas: object;
|
||||
/** @deprecated */
|
||||
creationDate: DateTime;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
_permissions: {
|
||||
canEdit: boolean;
|
||||
canSee: boolean;
|
||||
};
|
||||
_links?: {
|
||||
dav_link?: {
|
||||
href: string;
|
||||
expiration: number;
|
||||
};
|
||||
downloadLink?: SignedUrlGet;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StoredObjectVersion {
|
||||
/**
|
||||
* filename of the object in the object storage
|
||||
*/
|
||||
filename: string;
|
||||
iv: number[];
|
||||
keyInfos: JsonWebKey;
|
||||
type: string;
|
||||
/**
|
||||
* filename of the object in the object storage
|
||||
*/
|
||||
filename: string;
|
||||
iv: number[];
|
||||
keyInfos: JsonWebKey;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface StoredObjectVersionCreated extends StoredObjectVersion {
|
||||
persisted: false;
|
||||
persisted: false;
|
||||
}
|
||||
|
||||
export interface StoredObjectVersionPersisted extends StoredObjectVersionCreated {
|
||||
version: number;
|
||||
id: number;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
version: number;
|
||||
id: number;
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
}
|
||||
|
||||
export interface StoredObjectStatusChange {
|
||||
id: number;
|
||||
filename: string;
|
||||
status: StoredObjectStatus;
|
||||
type: string;
|
||||
id: number;
|
||||
filename: string;
|
||||
status: StoredObjectStatus;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface StoredObjectVersionWithPointInTime extends StoredObjectVersionPersisted {
|
||||
"point-in-times": StoredObjectPointInTime[];
|
||||
"from-restored": StoredObjectVersionPersisted | null;
|
||||
"point-in-times": StoredObjectPointInTime[];
|
||||
"from-restored": StoredObjectVersionPersisted | null;
|
||||
}
|
||||
|
||||
export interface StoredObjectPointInTime {
|
||||
id: number;
|
||||
byUser: User | null;
|
||||
reason: "keep-before-conversion" | "keep-by-user";
|
||||
id: number;
|
||||
byUser: User | null;
|
||||
reason: "keep-before-conversion" | "keep-by-user";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,63 +81,63 @@ export type WopiEditButtonExecutableBeforeLeaveFunction = () => Promise<void>;
|
||||
* Object containing information for performering a POST request to a swift object store
|
||||
*/
|
||||
export interface PostStoreObjectSignature {
|
||||
method: "POST";
|
||||
max_file_size: number;
|
||||
max_file_count: 1;
|
||||
expires: number;
|
||||
submit_delay: 180;
|
||||
redirect: string;
|
||||
prefix: string;
|
||||
url: string;
|
||||
signature: string;
|
||||
method: "POST";
|
||||
max_file_size: number;
|
||||
max_file_count: 1;
|
||||
expires: number;
|
||||
submit_delay: 180;
|
||||
redirect: string;
|
||||
prefix: string;
|
||||
url: string;
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export interface PDFPage {
|
||||
index: number;
|
||||
width: number;
|
||||
height: number;
|
||||
index: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
export interface SignatureZone {
|
||||
index: number | null;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
PDFPage: PDFPage;
|
||||
index: number | null;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
PDFPage: PDFPage;
|
||||
}
|
||||
|
||||
export interface Signature {
|
||||
id: number;
|
||||
storedObject: StoredObject;
|
||||
zones: SignatureZone[];
|
||||
id: number;
|
||||
storedObject: StoredObject;
|
||||
zones: SignatureZone[];
|
||||
}
|
||||
|
||||
export type SignedState =
|
||||
| "pending"
|
||||
| "signed"
|
||||
| "rejected"
|
||||
| "canceled"
|
||||
| "error";
|
||||
| "pending"
|
||||
| "signed"
|
||||
| "rejected"
|
||||
| "canceled"
|
||||
| "error";
|
||||
|
||||
export interface CheckSignature {
|
||||
state: SignedState;
|
||||
storedObject: StoredObject;
|
||||
state: SignedState;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export type CanvasEvent = "select" | "add";
|
||||
|
||||
export interface ZoomLevel {
|
||||
id: number;
|
||||
zoom: number;
|
||||
label: {
|
||||
fr?: string;
|
||||
nl?: string;
|
||||
};
|
||||
id: number;
|
||||
zoom: number;
|
||||
label: {
|
||||
fr?: string;
|
||||
nl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GenericDoc {
|
||||
type: "doc_store_generic_doc";
|
||||
key: string;
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
type: "doc_store_generic_doc";
|
||||
key: string;
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
}
|
||||
|
||||
@@ -1,67 +1,65 @@
|
||||
<template>
|
||||
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
||||
<button
|
||||
:class="
|
||||
Object.assign({
|
||||
btn: true,
|
||||
'btn-outline-primary': true,
|
||||
'dropdown-toggle': true,
|
||||
'btn-sm': props.small,
|
||||
})
|
||||
"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-if="isEditableOnline">
|
||||
<wopi-edit-button
|
||||
:stored-object="props.storedObject"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:execute-before-leave="props.executeBeforeLeave"
|
||||
></wopi-edit-button>
|
||||
</li>
|
||||
<li v-if="isEditableOnDesktop">
|
||||
<desktop-edit-button
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:edit-link="props.davLink"
|
||||
:expiration-link="props.davLinkExpiration"
|
||||
></desktop-edit-button>
|
||||
</li>
|
||||
<li v-if="isConvertibleToPdf">
|
||||
<convert-button
|
||||
:stored-object="props.storedObject"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
></convert-button>
|
||||
</li>
|
||||
<li v-if="isDownloadable">
|
||||
<download-button
|
||||
:stored-object="props.storedObject"
|
||||
:at-version="props.storedObject.currentVersion"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:display-action-string-in-button="true"
|
||||
></download-button>
|
||||
</li>
|
||||
<li v-if="isHistoryViewable">
|
||||
<history-button
|
||||
:stored-object="props.storedObject"
|
||||
:can-edit="
|
||||
canEdit && props.storedObject._permissions.canEdit
|
||||
"
|
||||
></history-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="'pending' === props.storedObject.status">
|
||||
<div class="btn btn-outline-info">Génération en cours</div>
|
||||
</div>
|
||||
<div v-else-if="'failure' === props.storedObject.status">
|
||||
<div class="btn btn-outline-danger">La génération a échoué</div>
|
||||
</div>
|
||||
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
||||
<button
|
||||
:class="
|
||||
Object.assign({
|
||||
btn: true,
|
||||
'btn-outline-primary': true,
|
||||
'dropdown-toggle': true,
|
||||
'btn-sm': props.small,
|
||||
})
|
||||
"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-if="isEditableOnline">
|
||||
<wopi-edit-button
|
||||
:stored-object="props.storedObject"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:execute-before-leave="props.executeBeforeLeave"
|
||||
></wopi-edit-button>
|
||||
</li>
|
||||
<li v-if="isEditableOnDesktop">
|
||||
<desktop-edit-button
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:edit-link="props.davLink ?? ''"
|
||||
:expiration-link="props.davLinkExpiration ?? 0"
|
||||
></desktop-edit-button>
|
||||
</li>
|
||||
<li v-if="isConvertibleToPdf">
|
||||
<convert-button
|
||||
:stored-object="props.storedObject"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
></convert-button>
|
||||
</li>
|
||||
<li v-if="isDownloadable">
|
||||
<download-button
|
||||
:stored-object="props.storedObject"
|
||||
:at-version="props.storedObject.currentVersion"
|
||||
:filename="filename"
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:display-action-string-in-button="true"
|
||||
></download-button>
|
||||
</li>
|
||||
<li v-if="isHistoryViewable">
|
||||
<history-button
|
||||
:stored-object="props.storedObject"
|
||||
:can-edit="canEdit && props.storedObject._permissions.canEdit"
|
||||
></history-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="'pending' === props.storedObject.status">
|
||||
<div class="btn btn-outline-info">Génération en cours</div>
|
||||
</div>
|
||||
<div v-else-if="'failure' === props.storedObject.status">
|
||||
<div class="btn btn-outline-danger">La génération a échoué</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -70,68 +68,65 @@ import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
|
||||
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
||||
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
||||
import {
|
||||
is_extension_editable,
|
||||
is_extension_viewable,
|
||||
is_object_ready,
|
||||
is_extension_editable,
|
||||
is_extension_viewable,
|
||||
is_object_ready,
|
||||
} from "./StoredObjectButton/helpers";
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObjectStatusChange,
|
||||
StoredObjectVersion,
|
||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||
StoredObject,
|
||||
StoredObjectStatusChange,
|
||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||
} from "../types";
|
||||
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
|
||||
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
|
||||
|
||||
interface DocumentActionButtonsGroupConfig {
|
||||
storedObject: StoredObject;
|
||||
small?: boolean;
|
||||
canEdit?: boolean;
|
||||
canDownload?: boolean;
|
||||
canConvertPdf?: boolean;
|
||||
returnPath?: string;
|
||||
storedObject: StoredObject;
|
||||
small?: boolean;
|
||||
canEdit?: boolean;
|
||||
canDownload?: boolean;
|
||||
canConvertPdf?: boolean;
|
||||
returnPath?: string;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*
|
||||
* If not set, 'document' will be used.
|
||||
*/
|
||||
filename?: string;
|
||||
/**
|
||||
* 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)
|
||||
*
|
||||
* If not set, 'document' will be used.
|
||||
*/
|
||||
filename?: string;
|
||||
|
||||
/**
|
||||
* If set, will execute this function before leaving to the editor
|
||||
*/
|
||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||
/**
|
||||
* If set, will execute this function before leaving to the editor
|
||||
*/
|
||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||
|
||||
/**
|
||||
* a link to download and edit file using webdav
|
||||
*/
|
||||
davLink?: string;
|
||||
/**
|
||||
* a link to download and edit file using webdav
|
||||
*/
|
||||
davLink?: string;
|
||||
|
||||
/**
|
||||
* the expiration date of the download, as a unix timestamp
|
||||
*/
|
||||
davLinkExpiration?: number;
|
||||
/**
|
||||
* the expiration date of the download, as a unix timestamp
|
||||
*/
|
||||
davLinkExpiration?: number;
|
||||
}
|
||||
|
||||
const emit =
|
||||
defineEmits<
|
||||
(
|
||||
e: "onStoredObjectStatusChange",
|
||||
newStatus: StoredObjectStatusChange,
|
||||
) => void
|
||||
>();
|
||||
defineEmits<
|
||||
(
|
||||
e: "onStoredObjectStatusChange",
|
||||
newStatus: StoredObjectStatusChange,
|
||||
) => void
|
||||
>();
|
||||
|
||||
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
||||
small: false,
|
||||
canEdit: true,
|
||||
canDownload: true,
|
||||
canConvertPdf: true,
|
||||
returnPath:
|
||||
window.location.pathname +
|
||||
window.location.search +
|
||||
window.location.hash,
|
||||
small: false,
|
||||
canEdit: true,
|
||||
canDownload: true,
|
||||
canConvertPdf: true,
|
||||
returnPath:
|
||||
window.location.pathname + window.location.search + window.location.hash,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -145,93 +140,89 @@ let tryiesForReady = 0;
|
||||
const maxTryiesForReady = 120;
|
||||
|
||||
const isButtonGroupDisplayable = computed<boolean>(() => {
|
||||
return (
|
||||
isDownloadable.value ||
|
||||
isEditableOnline.value ||
|
||||
isEditableOnDesktop.value ||
|
||||
isConvertibleToPdf.value
|
||||
);
|
||||
return (
|
||||
isDownloadable.value ||
|
||||
isEditableOnline.value ||
|
||||
isEditableOnDesktop.value ||
|
||||
isConvertibleToPdf.value
|
||||
);
|
||||
});
|
||||
|
||||
const isDownloadable = computed<boolean>(() => {
|
||||
return (
|
||||
props.storedObject.status === "ready" ||
|
||||
// happens when the stored object version is just added, but not persisted
|
||||
(props.storedObject.currentVersion !== null &&
|
||||
props.storedObject.status === "empty")
|
||||
);
|
||||
return (
|
||||
props.storedObject.status === "ready" ||
|
||||
// happens when the stored object version is just added, but not persisted
|
||||
(props.storedObject.currentVersion !== null &&
|
||||
props.storedObject.status === "empty")
|
||||
);
|
||||
});
|
||||
|
||||
const isEditableOnline = computed<boolean>(() => {
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canEdit &&
|
||||
props.canEdit &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_editable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canEdit &&
|
||||
props.canEdit &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_editable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
});
|
||||
|
||||
const isEditableOnDesktop = computed<boolean>(() => {
|
||||
return isEditableOnline.value;
|
||||
return isEditableOnline.value;
|
||||
});
|
||||
|
||||
const isConvertibleToPdf = computed<boolean>(() => {
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canSee &&
|
||||
props.canConvertPdf &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.type !== "application/pdf" &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
return (
|
||||
props.storedObject.status === "ready" &&
|
||||
props.storedObject._permissions.canSee &&
|
||||
props.canConvertPdf &&
|
||||
props.storedObject.currentVersion !== null &&
|
||||
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
||||
props.storedObject.currentVersion.type !== "application/pdf" &&
|
||||
props.storedObject.currentVersion.persisted !== false
|
||||
);
|
||||
});
|
||||
|
||||
const isHistoryViewable = computed<boolean>(() => {
|
||||
return props.storedObject.status === "ready";
|
||||
return props.storedObject.status === "ready";
|
||||
});
|
||||
|
||||
const checkForReady = function (): void {
|
||||
if (
|
||||
"ready" === props.storedObject.status ||
|
||||
"empty" === props.storedObject.status ||
|
||||
"failure" === props.storedObject.status ||
|
||||
// stop reloading if the page stays opened for a long time
|
||||
tryiesForReady > maxTryiesForReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
"ready" === props.storedObject.status ||
|
||||
"empty" === props.storedObject.status ||
|
||||
"failure" === props.storedObject.status ||
|
||||
// stop reloading if the page stays opened for a long time
|
||||
tryiesForReady > maxTryiesForReady
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
tryiesForReady = tryiesForReady + 1;
|
||||
tryiesForReady = tryiesForReady + 1;
|
||||
|
||||
setTimeout(onObjectNewStatusCallback, 5000);
|
||||
setTimeout(onObjectNewStatusCallback, 5000);
|
||||
};
|
||||
|
||||
const onObjectNewStatusCallback = async function (): Promise<void> {
|
||||
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();
|
||||
}
|
||||
|
||||
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(() => {
|
||||
checkForReady();
|
||||
checkForReady();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user