mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-14 18:54:59 +00:00
Compare commits
161 Commits
v3.5.0
...
362-bug-ma
Author | SHA1 | Date | |
---|---|---|---|
499aa3adea | |||
b597ff89e0 | |||
3c801a6d0e | |||
4093949c2f | |||
7ed10efcd1 | |||
350661a4fa | |||
08207b656a | |||
8de63de6d6 | |||
51804b10c0 | |||
02f555efae | |||
d2fcb6945b | |||
c89e3785ef | |||
9f17ec4841 | |||
b277a7749a | |||
c8b6b6e33a | |||
10eaebf610 | |||
0a34f9086f
|
|||
739e0b1692 | |||
8db8f5fdf5
|
|||
fc32f9eca9 | |||
ab35e8c034
|
|||
2aded2974f | |||
f84c1632b2
|
|||
|
03717a1a87 | ||
|
02c524dd79 | ||
99e4824137 | |||
dacaaea235 | |||
096466e79e
|
|||
7285e5c2b0 | |||
37227a3aeb | |||
7569667189
|
|||
b0993f4062 | |||
7c79b65f48 | |||
b8f25bcd45 | |||
f4efb0e975 | |||
c641baec78 | |||
cc150e32f0 | |||
|
bc7f0907ab | ||
26cf6459b4 | |||
d0fa6dd512 | |||
|
fbdc0d32f0 | ||
|
5f31473c90 | ||
|
98cf167040 | ||
|
6c37d798bf | ||
03748a7e84 | |||
9e3431f397 | |||
912861dbff | |||
35f25daf7c | |||
21274155b5 | |||
3f7c136d6b
|
|||
5d9c573853 | |||
9a5fd67842 | |||
2755bc12c4 | |||
9e191f1b5b | |||
ab684a20ad
|
|||
bc92b52498 | |||
be5655e537
|
|||
ceb0bd982e
|
|||
47c0af3623
|
|||
f6f2efee2c
|
|||
59fd9fc63f | |||
ec2c08681e | |||
fedcbb9a70
|
|||
3f1a4fe353 | |||
fc27c73dab | |||
20bfd5b717
|
|||
5e3a1eb2ab
|
|||
b02820407c
|
|||
594ed4a5b4
|
|||
88fbf7bc1c
|
|||
aa26e67f6f
|
|||
21ac3eaab4 | |||
2ff500b00e | |||
19fa308c06 | |||
1b831bc424 | |||
573118e514 | |||
0cabf5654a | |||
cfb547d55f | |||
a915c35026 | |||
018f8aef5c | |||
de6385ba21 | |||
edb51dd3cd | |||
c379bccad4 | |||
bd9ad8a569 | |||
0cdd9184a3 | |||
cb5fd2b69d | |||
feebcf6662
|
|||
2a61197999 | |||
0a53a9a9d1 | |||
eea1e40663 | |||
1b0771eb07 | |||
3a74c48104 | |||
6de4861b98
|
|||
b4a1e824ac
|
|||
d87cf925e2
|
|||
ce3cce7b95 | |||
6c97654e5e
|
|||
0787e61c22
|
|||
73bcfb82b7
|
|||
812e4047d0
|
|||
999ac3af2b
|
|||
0c628c39db
|
|||
c65f1d495d
|
|||
83f7086bb0
|
|||
c1e449f48e
|
|||
1f6de3cb11
|
|||
3a2548ed89
|
|||
d7652658f2
|
|||
67b5bc6dba
|
|||
e25c1e1816
|
|||
282b7f7fbb | |||
ab311eaecb
|
|||
b37d7fb907 | |||
57b8dacba0 | |||
edcc01149b | |||
27b2d77fdb | |||
96bb98f854
|
|||
68ed2db51e
|
|||
184bb095d8 | |||
78c8e94765 | |||
c8d1a91953 | |||
3e8e2b0fa8 | |||
|
2c9c700ca7 | ||
|
110db30748 | ||
|
1f96f76f87 | ||
|
65902ea231 | ||
|
88c0b1570d | ||
4933d2251c
|
|||
c939ff4a4e
|
|||
82fb98348b
|
|||
d56d00421a | |||
de11fa86c6 | |||
4cc254a3e5 | |||
22ce16ef49 | |||
1e52a93cbe | |||
16fe07cce7 | |||
e8962782ed | |||
64a853fb6d | |||
ebfd48e41f | |||
a6aa2a81c2 | |||
98d29c2134 | |||
60386ae9ac | |||
54e10cacd3 | |||
3ca126804b | |||
40d733c290 | |||
aa0785fc71 | |||
610227815a | |||
9d9f062417 | |||
0454e5d758 | |||
2d6d2a1f58 | |||
c971e34675 | |||
be2a119163 | |||
22ecb11227 | |||
dd854ea339 | |||
f05c25853c | |||
90798b12e5 | |||
f91f5ce27e | |||
96f73b419d | |||
273f91fd00 | |||
27ecae4486 | |||
f90f1c7ef8 |
6
.changes/unreleased/DX-20250131-131801.yaml
Normal file
6
.changes/unreleased/DX-20250131-131801.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: DX
|
||||
body: Create an unique source of trust for translations
|
||||
time: 2025-01-31T13:18:01.239211506+01:00
|
||||
custom:
|
||||
Issue: "333"
|
||||
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250130-120207.yaml
Normal file
6
.changes/unreleased/Feature-20250130-120207.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Suggest all referrers within actions of the accompanying period when creating an activity
|
||||
time: 2025-01-30T12:02:07.053587034+01:00
|
||||
custom:
|
||||
Issue: "349"
|
||||
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250130-120459.yaml
Normal file
6
.changes/unreleased/Feature-20250130-120459.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Add possibility to export a csv with all social issues and social actions
|
||||
time: 2025-01-30T12:04:59.754998541+01:00
|
||||
custom:
|
||||
Issue: "343"
|
||||
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250214-150328.yaml
Normal file
6
.changes/unreleased/Feature-20250214-150328.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Restore document to previous kept version when a workflow is canceled
|
||||
time: 2025-02-14T15:03:28.707250207+01:00
|
||||
custom:
|
||||
Issue: "360"
|
||||
SchemaChange: No schema change
|
6
.changes/unreleased/Feature-20250219-120928.yaml
Normal file
6
.changes/unreleased/Feature-20250219-120928.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Add a list of third parties from within the admin (csv download)
|
||||
time: 2025-02-19T12:09:28.487991703+01:00
|
||||
custom:
|
||||
Issue: "341"
|
||||
SchemaChange: No schema change
|
6
.changes/unreleased/Fixed-20250214-121010.yaml
Normal file
6
.changes/unreleased/Fixed-20250214-121010.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Fixed
|
||||
body: fix generation of document with accompanying period context, and list of activities and works
|
||||
time: 2025-02-14T12:10:10.920355454+01:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
4
.changes/v3.5.1.md
Normal file
4
.changes/v3.5.1.md
Normal file
@@ -0,0 +1,4 @@
|
||||
## v3.5.1 - 2024-12-16
|
||||
### Fixed
|
||||
* Filiation: fix the display of the gender label in the graph
|
||||
* Wrap handling of PdfSignedMessage into transactions
|
3
.changes/v3.5.2.md
Normal file
3
.changes/v3.5.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v3.5.2 - 2024-12-19
|
||||
### Fixed
|
||||
* ([#345](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/345)) Export: activity filtering of users that were associated to an activity between certain dates. Results contained activities that were not within the specified date range"
|
3
.changes/v3.5.3.md
Normal file
3
.changes/v3.5.3.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v3.5.3 - 2025-01-07
|
||||
### Fixed
|
||||
* Fix the EntityToJsonTransformer to return an empty array if the value is ""
|
9
.changes/v3.6.0.md
Normal file
9
.changes/v3.6.0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## v3.6.0 - 2025-01-16
|
||||
### Feature
|
||||
* Importer for addresses does not fails when the postal code is not found with some addresses, and compute a recap list of all addresses that could not be imported. This recap list can be send by email.
|
||||
* ([#346](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/346)) Create a driver for storing documents on disk (instead of openstack object store)
|
||||
|
||||
* Add address importer from french Base d'Adresse Nationale (BAN)
|
||||
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add csv export for social issues and social actions
|
||||
### Fixed
|
||||
* Export: fix missing alias in activity between certain dates filter. Condition added for alias.
|
62
.changes/v3.7.0.md
Normal file
62
.changes/v3.7.0.md
Normal file
@@ -0,0 +1,62 @@
|
||||
## v3.7.0 - 2025-01-21
|
||||
### Feature
|
||||
* Use the Notifier component from Symfony to sens short messages (SMS). This allow to use more provider.
|
||||
### Fixed
|
||||
* ([#348](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/348)) [export] Fix aggregation of referrer's scope and job: fix the date range comparison
|
||||
|
||||
### Warning on configuration of Notifier component
|
||||
|
||||
If installed in an symfony app where the recipes are activated, this configuration should be added automatically:
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||
channel_policy:
|
||||
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||
urgent: ['email']
|
||||
high: ['email']
|
||||
medium: ['email']
|
||||
low: ['email']
|
||||
admin_recipients:
|
||||
- { email: admin@example.com }
|
||||
```
|
||||
|
||||
Actually, you should either:
|
||||
|
||||
- remove the configuration of ovhcloud added by the recipe
|
||||
- or remove the previous configuration of chill, to avoid keeping legacy configuration
|
||||
|
||||
#### Remove the added configuration and keep the legacy configuration
|
||||
|
||||
To remove the configuration:
|
||||
|
||||
```diff
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
- ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||
```
|
||||
|
||||
In that case, the previous configuration, which was stored under the `chill_main.short_messages.dsn` will be reconfigured into the Notifier component's configuration.
|
||||
|
||||
#### Properly configure SMS
|
||||
|
||||
You can also properly configure it, as [described in the OVH cloud provider repository](https://github.com/symfony/ovh-cloud-notifier/tree/5.4?tab=readme-ov-file#dsn-example) (where the scheme is `ovhcloud`):
|
||||
|
||||
**NOTE**: You have access to all notifier available with the [Notifier component](https://symfony.com/doc/current/notifier.html#notifier-sms-channel). You are not restricted to use OVH as a provider.
|
||||
|
||||
```diff
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
+ ovhcloud: '%env(OVHCLOUD_DSN)%' # this value should be located in a variable, and have `ovhcloud://` as a scheme
|
||||
|
||||
chill_main:
|
||||
- short_messages:
|
||||
- dsn: '%env(string:SHORT_MESSAGE_DSN)%'
|
||||
```
|
3
.changes/v3.7.1.md
Normal file
3
.changes/v3.7.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v3.7.1 - 2025-01-21
|
||||
### Fixed
|
||||
* Fix legacy configuration processor for notifier component
|
11
.changes/v3.8.0.md
Normal file
11
.changes/v3.8.0.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## v3.8.0 - 2025-02-03
|
||||
### Feature
|
||||
* Improve the UX of the news item admin form to prevent wrong usage
|
||||
* ([#319](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/319)) Notification list: display the concerned person's badges in the list
|
||||
* ([#320](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/320)) Show the first 3 persons directly in the accompanying period's banner
|
||||
* ([#334](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/334)) Suggest current user when creating an activity
|
||||
* ([#331](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/331)) Add attachments to workflows
|
||||
### Fixed
|
||||
* ([#350](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/350)) Add validation error to manual selection of person in PersonDuplicateController
|
||||
* ([#354](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/354)) Fix document category creation
|
||||
* ([#351](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/351)) Add definitive whitespace between span elements in vue PersonText component
|
3
.changes/v3.8.1.md
Normal file
3
.changes/v3.8.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v3.8.1 - 2025-02-05
|
||||
### Fixed
|
||||
* Fix household link in the parcours banner
|
3
.changes/v3.8.2.md
Normal file
3
.changes/v3.8.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v3.8.2 - 2025-02-10
|
||||
### Fixed
|
||||
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal
|
@@ -7,15 +7,29 @@ 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.Long) (not (eq .Custom.Long "")) }}
|
||||
* {{ 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") }}
|
||||
|
||||
**Schema Change**: {{ .Custom.SchemaChange }}
|
||||
{{- end -}}
|
||||
|
||||
{{ if and (.Custom.Long) (not (eq .Custom.Long "")) }}{{ .Custom.Long }}{{ end }}
|
||||
|
||||
{{ .Custom.Long }}{{ end }}
|
||||
custom:
|
||||
- key: SchemaChange
|
||||
label: Is a schema change required?
|
||||
optional: false
|
||||
type: enum
|
||||
enumOptions:
|
||||
- "No schema change"
|
||||
- "Add columns or tables"
|
||||
- "Drop or rename table or columns, or enforce new constraint that must be manually fixed"
|
||||
|
||||
- key: Issue
|
||||
label: Issue number (on chill-bundles repository) (optional)
|
||||
optional: true
|
||||
type: int
|
||||
minInt: 1
|
||||
|
||||
body:
|
||||
# allow multiline messages
|
||||
block: true
|
||||
|
4
.env
4
.env
@@ -88,3 +88,7 @@ REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
||||
###< chill-project/chill-bundles ###
|
||||
|
||||
###> symfony/ovh-cloud-notifier ###
|
||||
# OVHCLOUD_DSN=ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME
|
||||
###< symfony/ovh-cloud-notifier ###
|
||||
|
2386
.eslint-baseline.json
Normal file
2386
.eslint-baseline.json
Normal file
File diff suppressed because it is too large
Load Diff
7
.gitignore
vendored
7
.gitignore
vendored
@@ -5,12 +5,14 @@ composer.lock
|
||||
docs/build/
|
||||
.php_cs.cache
|
||||
.cache/*
|
||||
yarn.lock
|
||||
|
||||
docker/db/data
|
||||
docker/rabbitmq/data
|
||||
|
||||
# in this development bundle, we want to ignore directories related to a real app
|
||||
assets/*
|
||||
!assets/translator.ts
|
||||
migrations/*
|
||||
templates/*
|
||||
translations/*
|
||||
@@ -51,3 +53,8 @@ phpstan.neon
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
||||
|
||||
###> friendsofphp/php-cs-fixer ###
|
||||
/.php-cs-fixer.php
|
||||
/.php-cs-fixer.cache
|
||||
###< friendsofphp/php-cs-fixer ###
|
||||
|
@@ -5,6 +5,7 @@ cache:
|
||||
paths:
|
||||
- /vendor/
|
||||
- .cache
|
||||
- node_modules/
|
||||
|
||||
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
|
||||
# See http://docs.gitlab.com/ee/ci/services/README.html for examples.
|
||||
@@ -103,6 +104,32 @@ rector_tests:
|
||||
paths:
|
||||
- vendor/
|
||||
|
||||
lint:
|
||||
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
|
||||
- npx eslint-baseline "src/**/*.{js,ts,vue}"
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
|
||||
# psalm_tests:
|
||||
# stage: Tests
|
||||
# image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82
|
||||
# script:
|
||||
# - bin/psalm
|
||||
# allow_failure: true
|
||||
# artifacts:
|
||||
# expire_in: 30 min
|
||||
# paths:
|
||||
# - bin
|
||||
# - tests/app/vendor/
|
||||
|
||||
unit_tests:
|
||||
stage: Tests
|
||||
image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82
|
||||
|
@@ -25,7 +25,7 @@ $config = new PhpCsFixer\Config();
|
||||
$config
|
||||
->setFinder($finder)
|
||||
->setRiskyAllowed(true)
|
||||
->setCacheFile('.cache/php-cs-fixer.cache')
|
||||
->setCacheFile('var/php-cs-fixer.cache')
|
||||
->setUsingCache(true)
|
||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||
;
|
||||
|
110
CHANGELOG.md
110
CHANGELOG.md
@@ -6,6 +6,116 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v3.8.2 - 2025-02-10
|
||||
### Fixed
|
||||
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal
|
||||
|
||||
## v3.8.1 - 2025-02-05
|
||||
### Fixed
|
||||
* Fix household link in the parcours banner
|
||||
|
||||
## v3.8.0 - 2025-02-03
|
||||
### Feature
|
||||
* Improve the UX of the news item admin form to prevent wrong usage
|
||||
* ([#319](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/319)) Notification list: display the concerned person's badges in the list
|
||||
* ([#320](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/320)) Show the first 3 persons directly in the accompanying period's banner
|
||||
* ([#334](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/334)) Suggest current user when creating an activity
|
||||
* ([#331](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/331)) Add attachments to workflows
|
||||
### Fixed
|
||||
* ([#350](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/350)) Add validation error to manual selection of person in PersonDuplicateController
|
||||
* ([#354](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/354)) Fix document category creation
|
||||
* ([#351](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/351)) Add definitive whitespace between span elements in vue PersonText component
|
||||
|
||||
## v3.7.1 - 2025-01-21
|
||||
### Fixed
|
||||
* Fix legacy configuration processor for notifier component
|
||||
|
||||
## v3.7.0 - 2025-01-21
|
||||
### Feature
|
||||
* Use the Notifier component from Symfony to sens short messages (SMS). This allow to use more provider.
|
||||
### Fixed
|
||||
* ([#348](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/348)) [export] Fix aggregation of referrer's scope and job: fix the date range comparison
|
||||
|
||||
### Warning on configuration of Notifier component
|
||||
|
||||
If installed in an symfony app where the recipes are activated, this configuration should be added automatically:
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||
channel_policy:
|
||||
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||
urgent: ['email']
|
||||
high: ['email']
|
||||
medium: ['email']
|
||||
low: ['email']
|
||||
admin_recipients:
|
||||
- { email: admin@example.com }
|
||||
```
|
||||
|
||||
Actually, you should either:
|
||||
|
||||
- remove the configuration of ovhcloud added by the recipe
|
||||
- or remove the previous configuration of chill, to avoid keeping legacy configuration
|
||||
|
||||
#### Remove the added configuration and keep the legacy configuration
|
||||
|
||||
To remove the configuration:
|
||||
|
||||
```diff
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
- ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||
```
|
||||
|
||||
In that case, the previous configuration, which was stored under the `chill_main.short_messages.dsn` will be reconfigured into the Notifier component's configuration.
|
||||
|
||||
#### Properly configure SMS
|
||||
|
||||
You can also properly configure it, as [described in the OVH cloud provider repository](https://github.com/symfony/ovh-cloud-notifier/tree/5.4?tab=readme-ov-file#dsn-example) (where the scheme is `ovhcloud`):
|
||||
|
||||
**NOTE**: You have access to all notifier available with the [Notifier component](https://symfony.com/doc/current/notifier.html#notifier-sms-channel). You are not restricted to use OVH as a provider.
|
||||
|
||||
```diff
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
+ ovhcloud: '%env(OVHCLOUD_DSN)%' # this value should be located in a variable, and have `ovhcloud://` as a scheme
|
||||
|
||||
chill_main:
|
||||
- short_messages:
|
||||
- dsn: '%env(string:SHORT_MESSAGE_DSN)%'
|
||||
```
|
||||
|
||||
## v3.6.0 - 2025-01-16
|
||||
### Feature
|
||||
* Importer for addresses does not fails when the postal code is not found with some addresses, and compute a recap list of all addresses that could not be imported. This recap list can be send by email.
|
||||
* ([#346](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/346)) Create a driver for storing documents on disk (instead of openstack object store)
|
||||
|
||||
* Add address importer from french Base d'Adresse Nationale (BAN)
|
||||
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add csv export for social issues and social actions
|
||||
### Fixed
|
||||
* Export: fix missing alias in activity between certain dates filter. Condition added for alias.
|
||||
|
||||
## v3.5.3 - 2025-01-07
|
||||
### Fixed
|
||||
* Fix the EntityToJsonTransformer to return an empty array if the value is ""
|
||||
|
||||
## v3.5.2 - 2024-12-19
|
||||
### Fixed
|
||||
* ([#345](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/345)) Export: activity filtering of users that were associated to an activity between certain dates. Results contained activities that were not within the specified date range"
|
||||
|
||||
## v3.5.1 - 2024-12-16
|
||||
### Fixed
|
||||
* Filiation: fix the display of the gender label in the graph
|
||||
* Wrap handling of PdfSignedMessage into transactions
|
||||
|
||||
## v3.5.0 - 2024-12-09
|
||||
### Feature
|
||||
* ([#318](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/318)) Show all the pages of the documents in the signature app
|
||||
|
386
CONVENTIONS-en.md
Normal file
386
CONVENTIONS-en.md
Normal file
@@ -0,0 +1,386 @@
|
||||
# Chill Conventions
|
||||
|
||||
In Progress
|
||||
|
||||
## Translations
|
||||
|
||||
Per bundle, all translations for Twig pages are located in a single file: translations/messages.fr.yaml.
|
||||
|
||||
## File Locations
|
||||
|
||||
Controllers, form types, and Twig templates are placed in the root folders Controller, Form, and Resources/views, respectively.
|
||||
Admin pages are no longer placed in subfolders under Admin.
|
||||
|
||||
## Assets: Entrypoint Naming
|
||||
|
||||
There are three types of entry points:
|
||||
|
||||
* Vue application-specific (often for a single page): Prefixed with vue_.
|
||||
* Reusable JavaScript/CSS code:
|
||||
Examples include:
|
||||
* ckeditor
|
||||
* async_upload (used for forms)
|
||||
* bootstrap
|
||||
* chill.js
|
||||
* ...
|
||||
=> We prefix with `mod_`
|
||||
|
||||
* Page-specific CSS/JS:
|
||||
* Often reuses functionalities like ShowHide.
|
||||
=> We prefix with `page_`.
|
||||
|
||||
## Folder Structure
|
||||
|
||||
````
|
||||
# Under Resources/public:
|
||||
|
||||
- chill/ => Contains the theme (Chill).
|
||||
- chillmain.scss: Compiled into the chill entrypoint.
|
||||
- lib/ => Libraries never used as entrypoints but reused elsewhere.
|
||||
- Examples: ShowHide, Collection, Select2.
|
||||
- module/: Ends up in reusable entrypoints (mod_).
|
||||
- bootstrap
|
||||
- custom.scss
|
||||
- custom/
|
||||
- variable.scss
|
||||
- ...
|
||||
- AsyncUpload.
|
||||
- vue/ => Vue applications only (vue_).
|
||||
- Examples: _components, app.
|
||||
- page/ => Page-specific assets (page_).
|
||||
- login
|
||||
- person
|
||||
- personvendee
|
||||
- household_edit_metadata
|
||||
- index.js
|
||||
````
|
||||
|
||||
# Stylesheet Organization
|
||||
|
||||
1. The mod_bootstrap (module bootstrap) entry point is the first level. All parts (modules) of bootstrap are included in the bootstrap.js file located in ChillMainBundle/Resources/public/module/bootstrap.
|
||||
|
||||
* At the beginning, this file imports the variables.scss file, which determines most of the bootstrap settings as customized. This file overrides the original, and many variables are adjusted for Chill.
|
||||
* Care must be taken to ensure this file can always be compared to the original bootstrap file. In the event of a bootstrap update, a diff must be generated and this diff applied to the variable file of the new version.
|
||||
* At the end, it imports the custom.scss file, which includes bootstrap adaptations to prepare it for our Chill theme.
|
||||
* This `custom.scss` file can be split into smaller files using `@import 'custom/...'`.
|
||||
* The idea is that this first bootstrap layer sets an important part of the application’s styles, particularly those related to layout positioning, responsive breakpoints, and the default margins and spacing applied to elements being manipulated.
|
||||
|
||||
2. The chill entry point is the second level. It contains the Chill theme, which is recognizable in the application.
|
||||
|
||||
* Each bundle has a `Resources/public/chill` folder containing a main sass file, which can optionally be split using `@imports`. All these files are compiled into a single Chill entry point, which serves as the application’s theme and overrides bootstrap.
|
||||
* The chillmain.scss file should contain the most general style cascades, those applied to many areas of the application.
|
||||
* The chillperson.scss file also includes styles specific to different contexts related to people: person, household, and accompanying course.
|
||||
* Some smaller bundles contain only styles specific to their functionality.
|
||||
|
||||
3. The vue_ entry points are used for Vue components. Vue files can contain an scss style block. These styles are specific to the component and its inheritance, with the scoped tag precisely defining their scope (see the documentation).
|
||||
|
||||
4. The page_ entry points are used to add assets specific to certain pages, most often scripts and styles.
|
||||
|
||||
## HTML Tagging and Style Cascades
|
||||
|
||||
The following example shows how to tag a code element without overdoing it. Note that:
|
||||
|
||||
* It is not necessary to tag all inner classes.
|
||||
* The parent class should not be repeated in all child classes. Sass cascading allows for flexible HTML structuring without overloading the tag hierarchy.
|
||||
* Often, the first class will have variations created with additional classes that start in the same way: bloc-dark simply adds the dark version of bloc. We do not use bloc dark because we don’t want the dark class of bloc to interact with the same dark class of table. As a result, we will have an element bloc bloc-dark and another element table table-dark.
|
||||
|
||||
```html
|
||||
<div class="bloc bloc-dark my-bloc">
|
||||
<h3>My Title</h3>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-edit"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
Finally, it is important to define what a block, an action zone, and a button are. These three elements exist independently and are the only ones we tag.
|
||||
|
||||
For example, to style the title, we simply specify h3 within the block cascade.
|
||||
|
||||
```scss
|
||||
div.bloc {
|
||||
// un bloc générique, utilisé à plusieurs endroits
|
||||
&.bloc-dark {
|
||||
// la version sombre du bloc
|
||||
}
|
||||
h3 {}
|
||||
ul {
|
||||
// une liste standard dans bloc
|
||||
li {
|
||||
// des items de liste standard dans bloc
|
||||
}
|
||||
}
|
||||
}
|
||||
div.mon-bloc {
|
||||
// des exceptions spécifiques à mon-bloc,
|
||||
// qui sont des adaptations de bloc
|
||||
}
|
||||
|
||||
ul.record_actions {
|
||||
// va uniformiser tous les record_actions de l'application
|
||||
li {
|
||||
//...
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
// les boutons de bootstrap
|
||||
.btn-edit {
|
||||
// chill étends les boutons bootstrap pour ses propres besoins
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
## Render box
|
||||
|
||||
## URL
|
||||
|
||||
## Route Naming Conventions
|
||||
|
||||
:::warning
|
||||
These rules have not always been followed in the past. They are desired for the future.
|
||||
:::
|
||||
|
||||
Routes follow this structure:
|
||||
|
||||
`chill_(api|crud)_bundle_(api)_entity_action`
|
||||
|
||||
1. First, chill_ (for all Chill modules).
|
||||
2. Then, crud or api, optional, automatically added if the route is generated by the configuration.
|
||||
3. Then, a string indicating the bundle (`main`, `person`, `activity`, ...).
|
||||
4. Then, api, if the route is an API route.
|
||||
5. Then, a string indicating the entity the route targets, and possibly the sub-entities.
|
||||
6. Then, an action (`list`, `view`, `edit`, `new`, ...).
|
||||
|
||||
Indicating `api` in the fourth position allows distinguishing API routes generated by the configuration (which are all prefixed with `chill_api`) from those generated manually. (For example: `chill_api_household__index` and `chill_person_api_household_members_move`).
|
||||
|
||||
If points 4 and 5 are missing, they are replaced by other elements to ensure the uniqueness of the route and its clear understanding.
|
||||
|
||||
#### HTML pages
|
||||
|
||||
:::warning
|
||||
These rules have not always been followed in the past. They are desired for the future.
|
||||
:::
|
||||
|
||||
Syntaxe:
|
||||
|
||||
```
|
||||
/{_locale}/bundle/entity/{id}/action
|
||||
/{_locale}/bundle/entity/sub-entity/{id}/action
|
||||
```
|
||||
|
||||
The following elements should be included in the list:
|
||||
|
||||
1. The locale;
|
||||
2. An identifier for the bundle;
|
||||
3. The entity it relates to;
|
||||
4. Any sub-entities that the URL refers to;
|
||||
5. The action.
|
||||
|
||||
```
|
||||
# list of activities for a person
|
||||
/fr/activity/person/25/activity/list
|
||||
|
||||
# new activity
|
||||
/fr/activity/activity/new?person_id=25
|
||||
|
||||
```
|
||||
|
||||
#### Pour les API
|
||||
|
||||
:::info
|
||||
Automatically generated routes are prefixed with `chill_api`
|
||||
:::
|
||||
|
||||
Syntaxe:
|
||||
|
||||
```
|
||||
/api/1.0/bundle/entity/{id}/action
|
||||
/api/1.0/bundle/entity/sub-entity/{id}/action
|
||||
```
|
||||
|
||||
The following elements should be included in the list:
|
||||
|
||||
1. The string `/api/` followed by the version (e.g., 1.0);
|
||||
2. An identifier for the bundle;
|
||||
3. The entity it relates to;
|
||||
4. Any sub-entities that the URL refers to;
|
||||
5. The action.
|
||||
|
||||
These elements may be interspersed with the entity identifier. In this case, the identifier should be placed immediately after the entity it relates to.
|
||||
|
||||
#### URLS for admin pages
|
||||
Same conventions as for other html pages, **but `admin` is added in second position**. Soit:
|
||||
|
||||
`/{_locale}/admin/bundle/entity/{id}/action`
|
||||
|
||||
## Database table naming convention
|
||||
|
||||
When creating a new entity and the corresponding database table, we follow the following naming convention for the database table:
|
||||
|
||||
`chill_{bundle_identifier}_{entity_name}`.
|
||||
Eg. chill_person_spoken_languages
|
||||
|
||||
## UI Rules
|
||||
|
||||
### Page Titles
|
||||
#### Every page must have a title (in the head tag and page header).
|
||||
|
||||
Each page contains a title in the <head> tag. This title is typically the same as the header title on the page.
|
||||
|
||||
Tip: It is possible to use the block function in Twig for this.
|
||||
|
||||
Example:
|
||||
|
||||
```htmlmixed=
|
||||
{% block title "Titre de la page" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{{ block('title')}}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### entity_render usage
|
||||
|
||||
#### in Twig
|
||||
Always use chill_entity_render_box for rendering entities like:
|
||||
* User
|
||||
* Person
|
||||
* SocialAction
|
||||
* SocialIssue
|
||||
* Address
|
||||
* Thirdparty
|
||||
* ...
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
address|chill_entity_render_box
|
||||
```
|
||||
Justification:
|
||||
|
||||
1. Customization by installation: Some elements are sometimes customized during installation (for example, the name of each user will be followed by the name of the service).
|
||||
2. To streamline and make displays consistent: Ensures uniformity in the way information is displayed across different pages or sections.
|
||||
3. To simplify Twig code: By using blocks and centralizing title logic, it reduces repetition and makes the Twig code easier to maintain.
|
||||
|
||||
* Three rendering modes:
|
||||
* inline
|
||||
* block
|
||||
* item.
|
||||
|
||||
#### In Vue
|
||||
|
||||
There is always a renderbox equivalent in vue.
|
||||
|
||||
#### HTML Links to Sections
|
||||
|
||||
Always include links/icons for accessing records, such as person or household details, if the user has access.
|
||||
|
||||
### Form Guidelines
|
||||
|
||||
#### Vocabulary:
|
||||
* `Create` in a `bt bt-create` for links to the form to create an entity (to access the form).
|
||||
* `Save` in a `bt bt-save` for "Save" buttons (in either an edit or create form).
|
||||
* `Save and new`
|
||||
* `Save and view`
|
||||
* `Edit` in a `bt bt-edit` for links to the edit form.
|
||||
* `Duplicate` (specify where it can be seen).
|
||||
* `Cancel` for leaving an edit page with a link to the list, or the `returnPath`.
|
||||
|
||||
#### After Saving:
|
||||
|
||||
Redirect to the returnPath if available; otherwise, redirect to the view page.
|
||||
|
||||
### Sticky Form Buttons:
|
||||
|
||||
Buttons like "Cancel" and "Save" must be within a "sticky-form" bar at the bottom of the form.
|
||||
|
||||
If relevant:
|
||||
|
||||
* The banner contains a "Cancel" button that returns to the previous page. It is mandatory for forms, but optional for lists or "summary" pages.
|
||||
* This "Cancel" button is always on the left.
|
||||
|
||||
```
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_entity_return_path('route_name' { 'route': 'option' } )}}">{{ return_path_label }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<!-- action 1 -->
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### Flash Messages
|
||||
|
||||
#### Display a flash message upon entity creation:
|
||||
|
||||
Everytime an entity has been created
|
||||
|
||||
> "The entity has been created."
|
||||
|
||||
The name of the element can be replaced with something more relevant:
|
||||
|
||||
> * The activity has been created
|
||||
> * The appointment has been created
|
||||
> * ...
|
||||
|
||||
#### On Saving an Entity
|
||||
|
||||
Each time an entity is saved, a flash message should appear:
|
||||
|
||||
> The data has been modified
|
||||
|
||||
#### Form Error (Validation Error)
|
||||
|
||||
At the top of the form, a flash message should indicate that validations have failed:
|
||||
|
||||
> This form contains errors
|
||||
|
||||
Errors should appear attached to the field they concern. However, it is acceptable to display errors at the root of the form if it is technically difficult to attach errors.
|
||||
|
||||
### Return Links
|
||||
|
||||
Each time a link is provided, check whether the function chill_return_path, chill_forward_return_path, or chill_return_path_or should be used.
|
||||
* From the list page, to the opening of an element, or the creation button => use chill_path_add_return_path
|
||||
* In these edit pages:
|
||||
* use chill_return_path_or in the "Cancel" button;
|
||||
* for the "Save and view" and "Save and close" buttons => ?
|
||||
|
||||
### Assets for Suggestion Lists
|
||||
|
||||
Create a list of suggestions to add (the entire item is clickable)
|
||||
|
||||
```html
|
||||
<ul class="list-suggest add-items">
|
||||
<li>
|
||||
<span>item</span>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Create a list of suggestions to remove (with a clickable red cross, the anchor <a> is empty)
|
||||
|
||||
```html
|
||||
<ul class="list-suggest remove-items">
|
||||
<li>
|
||||
<span>
|
||||
item
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Create a removable title (with a clickable red cross, the anchor <a> is empty)
|
||||
|
||||
```html
|
||||
<div class="item-title">
|
||||
<span>title</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
The classes `cols` or `inline` can be added alongside `list-suggest` to modify the layout of the list. In the last example, add a `removable` class to the `<span>` if you want to make the item removable.
|
@@ -4,7 +4,6 @@ en cours de rédaction
|
||||
|
||||
## Translations
|
||||
|
||||
|
||||
Par bundle, toutes les traductions des pages twig se trouvent dans un seul fichier `translations/messages.fr.yaml`.
|
||||
|
||||
## Emplacement des fichiers
|
||||
@@ -142,7 +141,6 @@ ul.record_actions {
|
||||
|
||||
## Render box
|
||||
|
||||
|
||||
## URL
|
||||
|
||||
### Nommage des routes
|
||||
@@ -234,6 +232,13 @@ Même conventions que dans les autres pages html de l'application, **mais `admin
|
||||
|
||||
`/{_locale}/admin/bundle/entity/{id}/action`
|
||||
|
||||
### Nommage des tables de base de donnée
|
||||
|
||||
Lors de la création d'une nouvelle entité et de la table de base de données correspondante, nous suivons la convention d'appellation suivante pour la table de base de données :
|
||||
|
||||
`chill_{bundle_identifier}_{nom_de_l'entité}`.
|
||||
|
||||
Par exemple : chill_person_spoken_languages
|
||||
|
||||
## Règles UI chill
|
||||
|
||||
@@ -293,8 +298,6 @@ A prevoir:
|
||||
|
||||
> quand on passe l’option render: bloc, on peut placer le render_box dans une boucle for plus large qui fonctionne avec la classe flex-table ou la classe flex-bloc, ce qui donnera un affichage en rangée (table) ou en blocs. [name=Mathieu]
|
||||
|
||||
|
||||
|
||||
#### En vue
|
||||
|
||||
Il existe systématiquement une "box" équivalente en vue.
|
9
assets/translator.ts
Normal file
9
assets/translator.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// @ts-ignore Cannot find module (when used within an app)
|
||||
import { trans, getLocale, setLocale, setLocaleFallbacks } from "@symfony/ux-translator";
|
||||
|
||||
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
|
||||
setLocale('fr');
|
||||
|
||||
export { trans };
|
||||
// @ts-ignore Cannot find module (when used within an app)
|
||||
export * from '../var/translations';
|
@@ -13,8 +13,10 @@
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-redis": "*",
|
||||
"ext-zlib": "*",
|
||||
"champs-libres/wopi-bundle": "dev-master@dev",
|
||||
"champs-libres/wopi-lib": "dev-master@dev",
|
||||
"doctrine/data-fixtures": "^1.8",
|
||||
"doctrine/doctrine-bundle": "^2.1",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.13.0",
|
||||
@@ -56,7 +58,9 @@
|
||||
"symfony/messenger": "^5.4",
|
||||
"symfony/mime": "^5.4",
|
||||
"symfony/monolog-bundle": "^3.5",
|
||||
"symfony/notifier": "^5.4",
|
||||
"symfony/options-resolver": "^5.4",
|
||||
"symfony/ovh-cloud-notifier": "^5.4",
|
||||
"symfony/process": "^5.4",
|
||||
"symfony/property-access": "^5.4",
|
||||
"symfony/property-info": "^5.4",
|
||||
@@ -71,6 +75,7 @@
|
||||
"symfony/templating": "^5.4",
|
||||
"symfony/translation": "^5.4",
|
||||
"symfony/twig-bundle": "^5.4",
|
||||
"symfony/ux-translator": "^2.22",
|
||||
"symfony/validator": "^5.4",
|
||||
"symfony/webpack-encore-bundle": "^1.11",
|
||||
"symfony/workflow": "^5.4",
|
||||
@@ -85,6 +90,7 @@
|
||||
"require-dev": {
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.3",
|
||||
"fakerphp/faker": "^1.13",
|
||||
"friendsofphp/php-cs-fixer": "3.65.0",
|
||||
"jangregor/phpstan-prophecy": "^1.0",
|
||||
"nelmio/alice": "^3.8",
|
||||
"nikic/php-parser": "^4.15",
|
||||
@@ -157,7 +163,9 @@
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||
},
|
||||
"php-cs-fixer": "php-cs-fixer fix --config=./.php-cs-fixer.dist.php --show-progress=none"
|
||||
"php-cs-fixer": "php-cs-fixer fix --config=./.php-cs-fixer.dist.php --show-progress=none",
|
||||
"phpstan": "phpstan --no-progress",
|
||||
"rector": "rector --no-progress-bar"
|
||||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
|
@@ -36,4 +36,5 @@ return [
|
||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
];
|
||||
|
@@ -1,4 +1,7 @@
|
||||
chill_doc_store:
|
||||
use_driver: openstack
|
||||
local_storage:
|
||||
storage_path: '%kernel.project_dir%/var/storage'
|
||||
openstack:
|
||||
temp_url:
|
||||
temp_url_key: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_KEY)%' # Required
|
||||
|
13
config/packages/notifier.yaml
Normal file
13
config/packages/notifier.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
framework:
|
||||
notifier:
|
||||
texter_transports:
|
||||
#ovhcloud: '%env(OVHCLOUD_DSN)%'
|
||||
#ovhcloud: '%env(SHORT_MESSAGE_DSN)%'
|
||||
channel_policy:
|
||||
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||
urgent: ['email']
|
||||
high: ['email']
|
||||
medium: ['email']
|
||||
low: ['email']
|
||||
admin_recipients:
|
||||
- { email: admin@example.com }
|
3
config/packages/ux_translator.yaml
Normal file
3
config/packages/ux_translator.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
ux_translator:
|
||||
# The directory where the JavaScript translations are dumped
|
||||
dump_directory: '%kernel.project_dir%/var/translations'
|
19
config/routes/chill_assets_dev.yaml
Normal file
19
config/routes/chill_assets_dev.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
when@dev:
|
||||
sass_assets:
|
||||
path: /_dev/assets
|
||||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||
defaults:
|
||||
template: '@ChillMain/Dev/dev.assets.html.twig'
|
||||
|
||||
sass_assets_test1:
|
||||
path: /_dev/assets_test1
|
||||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||
defaults:
|
||||
template: '@ChillMain/Dev/dev.assets.test1.html.twig'
|
||||
|
||||
sass_assets_test2:
|
||||
path: /_dev/assets_test2
|
||||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||
defaults:
|
||||
template: '@ChillMain/Dev/dev.assets.test2.html.twig'
|
||||
|
12
config/routes/chill_swagger.yaml
Normal file
12
config/routes/chill_swagger.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
when@dev:
|
||||
swagger_ui:
|
||||
path: /_dev/swagger
|
||||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||
defaults:
|
||||
template: '@ChillMain/Dev/swagger-ui/index.html.twig'
|
||||
|
||||
swagger_specs:
|
||||
path: /_dev/specs.yaml
|
||||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||
defaults:
|
||||
template: api/specs.yaml
|
71
docs/source/development/es-lint.rst
Normal file
71
docs/source/development/es-lint.rst
Normal file
@@ -0,0 +1,71 @@
|
||||
ESLint
|
||||
======
|
||||
|
||||
To improve the quality of our JS and VueJS code, ESLint and eslint-plugin-vue are implemented within the chill-bundles project.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
To run ESLint, you can simply use the ``eslint`` command within the chill-bundles directory.
|
||||
This runs eslint **not** taking the baseline into account, thus showing all existing errors in the project.
|
||||
|
||||
A script was also added to package.json allowing you to execute ``yarn run eslint``.
|
||||
This will run eslint, but **taking the baseline into account**, thus only alerting to newly created errors.
|
||||
|
||||
The eslint command is configured to also run ``prettier`` which will simply format the code to look more uniform (takes care indentation for example).
|
||||
|
||||
Interesting options that can be used in combination with eslint are:
|
||||
|
||||
- ``--quiet`` to only get errors and silence the warnings
|
||||
- ``--fix`` to have ESLint fix what it can, automatically. This will not fix everything.
|
||||
|
||||
Baseline
|
||||
--------
|
||||
|
||||
To allow us the time to fix linting errors/warnings a baseline has been created using the following command
|
||||
- ``npx eslint-baseline "**/*.{js,vue}"``
|
||||
|
||||
The baseline has been commited and the gitlab CI setup to only fail upon new errors/warnings being created.
|
||||
When fixing errors/warnings manually, please update the baseline.
|
||||
|
||||
1. Delete the current baseline file
|
||||
2. Run the above command locally, this will automatically create a new baseline that should be commited
|
||||
|
||||
Rules
|
||||
-----
|
||||
|
||||
We use Vue 3, so the rules can be configured as follows within the ``eslint.config.mjs`` file:
|
||||
|
||||
- ``.configs["flat/base"]`` ... Settings and rules to enable correct ESLint parsing.
|
||||
|
||||
Configurations for using Vue.js 3.x:
|
||||
|
||||
- ``.configs["flat/essential"]`` : Base rules plus rules to prevent errors or unintended behavior.
|
||||
- ``.configs["flat/strongly-recommended"]`` ... Above, plus rules to considerably improve code readability and/or dev experience.
|
||||
- ``.configs["flat/recommended"]`` ... Above, plus rules to enforce subjective community defaults to ensure consistency.
|
||||
|
||||
Detailed information about which rules each set includes can be found here:
|
||||
`https://eslint.vuejs.org/rules/ <https://eslint.vuejs.org/rules/>`_
|
||||
|
||||
Manual Rule Configuration
|
||||
-------------------------
|
||||
|
||||
We can also manually configure certain rules or override rules that are part of the ruleset specified above.
|
||||
|
||||
For example, if we want to turn off a certain rule, we can do so as follows:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
rules: {
|
||||
'vue/multi-word-component': 'off'
|
||||
}
|
||||
|
||||
We could also change the severity of a certain rule from 'error' to 'warning', for example.
|
||||
|
||||
Within specific ``.js`` or ``.vue`` files, we can also override a certain rule only for that specific file by adding a comment:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/* eslint multi-word-component: "off", no-child-content: "error"
|
||||
--------
|
||||
Here's a description about why this configuration is necessary. */
|
@@ -31,6 +31,7 @@ As Chill relies on the `symfony <http://symfony.com>`_ framework, reading the fr
|
||||
Exports <exports.rst>
|
||||
Embeddable comments <embeddable-comments.rst>
|
||||
Run tests <run-tests.rst>
|
||||
ESLint <es-lint.rst>
|
||||
Useful snippets <useful-snippets.rst>
|
||||
manual/index.rst
|
||||
Assets <assets.rst>
|
||||
|
@@ -1,39 +1,37 @@
|
||||
import { ShowHide } from 'ShowHide/show_hide.js';
|
||||
|
||||
var
|
||||
div_accompagnement = document.getElementById("form_accompagnement"),
|
||||
div_accompagnement_comment = document.getElementById("form_accompagnement_comment"),
|
||||
div_caf_id = document.getElementById("cafId"),
|
||||
div_caf_inscription_date = document.getElementById("cafInscriptionDate"),
|
||||
;
|
||||
import { ShowHide } from "ShowHide/show_hide.js";
|
||||
|
||||
var div_accompagnement = document.getElementById("form_accompagnement"),
|
||||
div_accompagnement_comment = document.getElementById(
|
||||
"form_accompagnement_comment",
|
||||
),
|
||||
div_caf_id = document.getElementById("cafId"),
|
||||
div_caf_inscription_date = document.getElementById("cafInscriptionDate");
|
||||
// let show/hide the div_accompagnement_comment if the input with value `'autre'` is checked
|
||||
new ShowHide({
|
||||
"froms": [div_accompagnement],
|
||||
"test": function(froms, event) {
|
||||
for (let el of froms.values()) {
|
||||
for (let input of el.querySelectorAll('input').values()) {
|
||||
if (input.value === 'autre') {
|
||||
return input.checked;
|
||||
}
|
||||
}
|
||||
froms: [div_accompagnement],
|
||||
test: function (froms, event) {
|
||||
for (let el of froms.values()) {
|
||||
for (let input of el.querySelectorAll("input").values()) {
|
||||
if (input.value === "autre") {
|
||||
return input.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"container": [div_accompagnement_comment]
|
||||
return false;
|
||||
},
|
||||
container: [div_accompagnement_comment],
|
||||
});
|
||||
|
||||
// let show the date input only if the the id is filled
|
||||
new ShowHide({
|
||||
froms: [ div_caf_id ],
|
||||
test: function(froms, event) {
|
||||
for (let el of froms.values()) {
|
||||
return el.querySelector("input").value !== "";
|
||||
}
|
||||
},
|
||||
container: [ div_caf_inscription_date ],
|
||||
// using this option, we use the event `input` instead of `change`
|
||||
event_name: 'input'
|
||||
froms: [div_caf_id],
|
||||
test: function (froms, event) {
|
||||
for (let el of froms.values()) {
|
||||
return el.querySelector("input").value !== "";
|
||||
}
|
||||
},
|
||||
container: [div_caf_inscription_date],
|
||||
// using this option, we use the event `input` instead of `change`
|
||||
event_name: "input",
|
||||
});
|
||||
|
||||
|
@@ -60,7 +60,6 @@ Available bundles
|
||||
* chill docs store: to store documents to people, but also entities,
|
||||
* chill task: to register task with people,
|
||||
* chill third party: to register third parties,
|
||||
* chill family members: to register family members
|
||||
|
||||
You will also found the following projects :
|
||||
|
||||
|
84
docs/source/installation/document-storage.rst
Normal file
84
docs/source/installation/document-storage.rst
Normal file
@@ -0,0 +1,84 @@
|
||||
Document storage
|
||||
################
|
||||
|
||||
You can store document on two different ways:
|
||||
|
||||
- on disk
|
||||
- in the cloud, using object storage: currently only `openstack swift <https://docs.openstack.org/api-ref/object-store/index.html>`_ is supported.
|
||||
|
||||
Comparison
|
||||
==========
|
||||
|
||||
Storing documents within the cloud is particularily suitable for "portable" deployments, like in kubernetes, or within container
|
||||
without having to manage volumes to store documents. But you'll have to subscribe on a commercial offer.
|
||||
|
||||
Storing documents on disk is more easy to configure, but more difficult to manage: if you use container, you will have to
|
||||
manager volumes to attach documents on disk. You'll have to do some backup of the directory. If chill is load-balanced (and
|
||||
multiple instances of chill are run), you will have to find a way to share the directories in read-write mode for every instance.
|
||||
|
||||
On Disk
|
||||
=======
|
||||
|
||||
Configure Chill like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# file config/packages/chill_doc_store.yaml
|
||||
chill_doc_store:
|
||||
use_driver: local_storage
|
||||
local_storage:
|
||||
storage_path: '%kernel.project_dir%/var/storage'
|
||||
|
||||
In this configuration, documents will be stored in :code:`var/storage` within your app directory. But this path can be
|
||||
elsewhere on the disk. Be aware that the directory must be writable by the user executing the chill app (php-fpm or www-data).
|
||||
|
||||
Documents will be stored in subpathes within that directory. The files will be encrypted, the key is stored in the database.
|
||||
|
||||
In the cloud, using openstack object store
|
||||
##########################################
|
||||
|
||||
You must subscribe to a commercial offer for object store.
|
||||
|
||||
Chill use some features to allow documents to be stored in the cloud without being uploaded first to the chill server:
|
||||
|
||||
- `Form POST Middelware <https://docs.openstack.org/swift/latest/api/form_post_middleware.html>`_;
|
||||
- `Temporary URL Middelware <https://docs.openstack.org/swift/latest/api/temporary_url_middleware.html>`_.
|
||||
|
||||
A secret key must be generated and configured, and CORS must be configured depending on the domain you will use to serve Chill.
|
||||
|
||||
At first, create a container and get the base path to the container. For instance, on OVH, if you create a container named "mychill",
|
||||
you will be able to retrieve the base path of the container within the OVH interface, like this:
|
||||
|
||||
- base_path: :code:`https://storage.gra.cloud.ovh.net/v1/AUTH_123456789/mychill/` => will be variable :code:`ASYNC_UPLOAD_TEMP_URL_BASE_PATH`
|
||||
- container: :code:`mychill` => will be variable :code:`ASYNC_UPLOAD_TEMP_URL_CONTAINER`
|
||||
|
||||
You can also generate a key, which should have at least 20 characters. This key will go in the variable :code:`ASYNC_UPLOAD_TEMP_URL_KEY`.
|
||||
|
||||
.. note::
|
||||
|
||||
See the `documentation of symfony <https://symfony.com/doc/current/configuration.html#config-env-vars>`_ on how to store variables, and how to encrypt them if needed.
|
||||
|
||||
Configure the storage like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# file config/packages/chill_doc_store.yaml
|
||||
chill_doc_store:
|
||||
use_driver: openstack
|
||||
openstack:
|
||||
temp_url:
|
||||
temp_url_key: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_KEY)%' # Required
|
||||
container: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_CONTAINER)%' # Required
|
||||
temp_url_base_path: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_BASE_PATH)%' # Required
|
||||
|
||||
Chill is able to configure the container in order to store document. Grab an Openstack Token (for instance, using :code:`openstack token issue` or
|
||||
the web interface of your openstack provider), and run this command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
symfony console async-upload:configure --os_token=OPENSTACK_TOKEN -d https://mychill.mydomain.example
|
||||
|
||||
# or, without symfony-cli
|
||||
bin/console async-upload:configure --os_token=OPENSTACK_TOKEN -d https://mychill.mydomain.example
|
||||
|
||||
|
@@ -29,8 +29,7 @@ We strongly encourage you to initialize a git repository at this step, to track
|
||||
# add the flex endpoints required for custom recipes
|
||||
cat <<< "$(jq '.extra.symfony += {"endpoint": ["flex://defaults", "https://gitlab.com/api/v4/projects/57371968/repository/files/index.json/raw?ref=main"]}' composer.json)" > composer.json
|
||||
# install chill and some dependencies
|
||||
# TODO fix the suffix "alpha1" and replace by ^3.0.0 when version 3.0.0 will be released
|
||||
symfony composer require chill-project/chill-bundles v3.0.0-RC3 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev
|
||||
symfony composer require chill-project/chill-bundles ^3.7.1 champs-libres/wopi-lib dev-master@dev champs-libres/wopi-bundle dev-master@dev symfony/amqp-messenger
|
||||
|
||||
We encourage you to accept the inclusion of the "Docker configuration from recipes": this is the documented way to run the database.
|
||||
You must also accept to configure recipes from the contrib repository, unless you want to configure the bundles manually).
|
||||
@@ -48,7 +47,7 @@ You must also accept to configure recipes from the contrib repository, unless yo
|
||||
|
||||
If you encounter this error during assets compilation (:code:`yarn run encore production`) (repeated multiple times):
|
||||
|
||||
.. code-block:: txt
|
||||
.. code-block::
|
||||
|
||||
[tsl] ERROR in /tmp/chill/v1/public/bundles/chillcalendar/types.ts(2,65)
|
||||
TS2307: Cannot find module '../../../ChillMainBundle/Resources/public/types' or its corresponding type declarations.
|
||||
@@ -74,14 +73,22 @@ or in the :code:`.env.local` file, which should not be committed to the git repo
|
||||
You do not need to set variables for the smtp server, redis server and relatorio server, as they are generated automatically
|
||||
by the symfony server, from the docker compose services.
|
||||
|
||||
The only required variable is the :code:`ADMIN_PASSWORD`. You can generate a hashed and salted admin password using the command
|
||||
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`. Then,
|
||||
The required variables are:
|
||||
|
||||
- the :code:`ADMIN_PASSWORD`;
|
||||
- the :code:`OVHCLOUD_DSN` variable;
|
||||
|
||||
:code:`ADMIN_PASSWORD`
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can generate a hashed and salted admin password using the command
|
||||
:code:`symfony console security:hash-password <your password> 'Symfony\Component\Security\Core\User\User'`.Then,
|
||||
you can either:
|
||||
|
||||
- add this password to the :code:`.env.local` file, you must escape the character :code:`$`: if the generated password
|
||||
is :code:`$2y$13$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm`, your :code:`.env.local` file will be:
|
||||
|
||||
.. code-block:: env
|
||||
.. code-block:: bash
|
||||
|
||||
ADMIN_PASSWORD=\$2y\$13\$iyvJLuT4YEa6iWXyQV4/N.hNHpNG8kXlYDkkt5MkYy4FXcSwYAwmm
|
||||
# note: if you copy-paste the line above, the password will be "admin".
|
||||
@@ -89,12 +96,24 @@ you can either:
|
||||
- add the generated password to the secrets manager (**note**: you must add the generated hashed password to the secrets env,
|
||||
not the password in clear text).
|
||||
|
||||
- set up the jwt authentication bundle
|
||||
:code:`OVHCLOUD_DSN` and sending SMS messages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This is a temporary dependency, for ensuring compatibility for previous behaviour.
|
||||
|
||||
You can set it to :code:`null://null` if you do not plan to use sending SMS.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
OVHCLOUD_DSN=null://null
|
||||
|
||||
If you plan to do it, you can configure the notifier component `as described in the symfony documentation <https://symfony.com/doc/current/notifier.html#notifier-sms-channel>`_.
|
||||
|
||||
|
||||
Some environment variables are available for the JWT authentication bundle in the :code:`.env` file.
|
||||
|
||||
Prepare migrations and other tools
|
||||
----------------------------------
|
||||
Prepare database, messenger queue, and other configuration
|
||||
----------------------------------------------------------
|
||||
|
||||
To continue the installation process, you will have to run migrations:
|
||||
|
||||
@@ -109,17 +128,22 @@ To continue the installation process, you will have to run migrations:
|
||||
symfony console messenger:setup-transports
|
||||
# prepare some views
|
||||
symfony console chill:db:sync-views
|
||||
# load languages data
|
||||
symfony console chill:main:languages:populate
|
||||
# generate jwt token, required for some api features (webdav access, ...)
|
||||
symfony console lexik:jwt:generate-keypair
|
||||
|
||||
.. warning::
|
||||
.. note::
|
||||
|
||||
If you encounter an error while running :code:`symfony console messenger:setup-transports`, you can set up the messenger
|
||||
transport to redis, by adding this in the :code:`.env.local` or :code:`.env` file:
|
||||
If you encounter this error:
|
||||
|
||||
.. code-block::
|
||||
|
||||
No transport supports the given Messenger DSN.
|
||||
|
||||
Please check that you installed the package `symfony/amqp-messenger`.
|
||||
|
||||
.. code-block:: env
|
||||
|
||||
MESSENGER_TRANSPORT_DSN=redis://${REDIS_HOST}:${REDIS_PORT}/messages
|
||||
|
||||
Start your web server locally
|
||||
-----------------------------
|
||||
@@ -323,6 +347,7 @@ Going further
|
||||
:maxdepth: 2
|
||||
|
||||
prod.rst
|
||||
document-storage.rst
|
||||
load-addresses.rst
|
||||
prod-calendar-sms-sending.rst
|
||||
msgraph-configure.rst
|
||||
|
@@ -41,16 +41,18 @@ Postal code are loaded from this database. There is no need to load postal codes
|
||||
The data are prepared for Chill (`See this repository <https://gitea.champs-libres.be/Chill-project/belgian-bestaddresses-transform/releases>`_).
|
||||
One can select postal code by his first number (:code:`1xxx` for postal codes from 1000 to 1999), or a limited list for development purpose.
|
||||
|
||||
The command expects a language code as first argument.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# load postal code from 1000 to 3999:
|
||||
bin/console chill:main:address-ref-from-best-addresse 1xxx 2xxx 3xxx
|
||||
bin/console chill:main:address-ref-from-best-addresse fr 1xxx 2xxx 3xxx
|
||||
|
||||
# load only an extract (for dev purposes)
|
||||
bin/console chill:main:address-ref-from-best-addresse extract
|
||||
bin/console chill:main:address-ref-from-best-addresse fr extract
|
||||
|
||||
# load full addresses (discouraged)
|
||||
bin/console chill:main:address-ref-from-best-addresse full
|
||||
bin/console chill:main:address-ref-from-best-addresse fr full
|
||||
|
||||
.. note::
|
||||
|
||||
|
39
eslint.config.mjs
Normal file
39
eslint.config.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
import eslintPluginVue from "eslint-plugin-vue";
|
||||
import ts from "typescript-eslint";
|
||||
import eslintPluginPrettier from "eslint-plugin-prettier";
|
||||
|
||||
export default ts.config(
|
||||
...ts.configs.recommended,
|
||||
...ts.configs.stylistic,
|
||||
...eslintPluginVue.configs["flat/essential"],
|
||||
{
|
||||
files: ["**/*.vue"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"**/vendor/*",
|
||||
"**/import-png.d.ts",
|
||||
"**/chill.webpack.config.js",
|
||||
"**/var/*",
|
||||
"**/docker/*",
|
||||
"**/node_modules/*",
|
||||
"**/public/build/*"
|
||||
],
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
prettier: eslintPluginPrettier,
|
||||
},
|
||||
rules: {
|
||||
"prettier/prettier": "error",
|
||||
// override/add rules settings here, such as:
|
||||
"vue/multi-word-component-names": "off",
|
||||
"@typescript-eslint/no-require-imports": "off",
|
||||
},
|
||||
},
|
||||
);
|
39
package.json
39
package.json
@@ -6,33 +6,41 @@
|
||||
"@apidevtools/swagger-cli": "^4.0.4",
|
||||
"@babel/core": "^7.20.5",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
|
||||
"@ckeditor/ckeditor5-dev-translations": "^40.2.0",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^40.2.0",
|
||||
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
|
||||
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
||||
"@ckeditor/ckeditor5-vue": "^7.3.0",
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@hotwired/stimulus": "^3.0.0",
|
||||
"@luminateone/eslint-baseline": "^1.0.9",
|
||||
"@symfony/stimulus-bridge": "^3.2.0",
|
||||
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||
"@symfony/webpack-encore": "^4.1.0",
|
||||
"@tsconfig/node14": "^1.0.1",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@typescript-eslint/parser": "^8.12.2",
|
||||
"bindings": "^1.5.0",
|
||||
"bootstrap": "5.2.3",
|
||||
"chokidar": "^3.5.1",
|
||||
"ckeditor5": "^44.1.0",
|
||||
"dompurify": "^3.1.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"fork-awesome": "^1.1.7",
|
||||
"intl-messageformat": "^10.5.11",
|
||||
"jquery": "^3.6.0",
|
||||
"marked": "^12.0.1",
|
||||
"node-sass": "^8.0.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"postcss-loader": "^7.0.2",
|
||||
"prettier": "^3.3.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass-loader": "^14.0.0",
|
||||
"select2": "^4.0.13",
|
||||
"select2-bootstrap-theme": "0.1.0-beta.10",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^9.3.1",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"vue-loader": "^17.0.0",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
@@ -45,6 +53,7 @@
|
||||
"@fullcalendar/timegrid": "^6.1.4",
|
||||
"@fullcalendar/vue3": "^6.1.4",
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/leaflet": "^1.9.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
@@ -63,13 +72,19 @@
|
||||
"vuex": "^4.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"Firefox ESR"
|
||||
"defaults and fully supports es6-module and not dead"
|
||||
],
|
||||
"scripts": {
|
||||
"dev-server": "encore dev-server",
|
||||
"dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build": "encore production --progress"
|
||||
"build": "encore production --progress",
|
||||
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||
"specs-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}\""
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
@@ -20,6 +20,10 @@ return static function (RectorConfig $rectorConfig): void {
|
||||
__DIR__ . '/src',
|
||||
]);
|
||||
|
||||
$rectorConfig->skip([
|
||||
\Rector\Php55\Rector\String_\StringClassNameToClassConstantRector::class => __DIR__ . 'src/Bundle/ChillMainBundle/Service/Notifier/LegacyOvhCloudFactory.php'
|
||||
]);
|
||||
|
||||
$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/test/App_KernelTestDebugContainer.xml ');
|
||||
$rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php');
|
||||
|
||||
|
@@ -55,6 +55,10 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
||||
.' AND '
|
||||
.'(person_person_having_activity.id = person.id OR person MEMBER OF activity_person_having_activity.persons)');
|
||||
|
||||
if (\in_array('activity', $qb->getAllAliases(), true)) {
|
||||
$sqb->andWhere('activity_person_having_activity.id = activity.id');
|
||||
}
|
||||
|
||||
if (isset($data['reasons']) && [] !== $data['reasons']) {
|
||||
// add clause activity reason
|
||||
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
|
||||
|
@@ -15,10 +15,13 @@ use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Notification\NotificationHandlerInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
final readonly class ActivityNotificationHandler implements NotificationHandlerInterface
|
||||
{
|
||||
public function __construct(private ActivityRepository $activityRepository) {}
|
||||
public function __construct(private ActivityRepository $activityRepository, private TranslatableStringHelperInterface $translatableStringHelper) {}
|
||||
|
||||
public function getTemplate(Notification $notification, array $options = []): string
|
||||
{
|
||||
@@ -37,4 +40,30 @@ final readonly class ActivityNotificationHandler implements NotificationHandlerI
|
||||
{
|
||||
return Activity::class === $notification->getRelatedEntityClass();
|
||||
}
|
||||
|
||||
public function getTitle(Notification $notification, array $options = []): TranslatableInterface
|
||||
{
|
||||
if (null === $activity = $this->getRelatedEntity($notification)) {
|
||||
return new TranslatableMessage('activity.deleted');
|
||||
}
|
||||
|
||||
return new TranslatableMessage('activity.title', [
|
||||
'date' => $activity->getDate(),
|
||||
'type' => $this->translatableStringHelper->localize($activity->getActivityType()->getName()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getAssociatedPersons(Notification $notification, array $options = []): array
|
||||
{
|
||||
if (null === $activity = $this->getRelatedEntity($notification)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $activity->getPersonsAssociated();
|
||||
}
|
||||
|
||||
public function getRelatedEntity(Notification $notification): ?Activity
|
||||
{
|
||||
return $this->activityRepository->find($notification->getRelatedEntityId());
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
require('./chillactivity.scss');
|
||||
require("./chillactivity.scss");
|
||||
|
@@ -1,21 +1,21 @@
|
||||
<template>
|
||||
<concerned-groups v-if="hasPerson"></concerned-groups>
|
||||
<social-issues-acc v-if="hasSocialIssues"></social-issues-acc>
|
||||
<location v-if="hasLocation"></location>
|
||||
<concerned-groups v-if="hasPerson" />
|
||||
<social-issues-acc v-if="hasSocialIssues" />
|
||||
<location v-if="hasLocation" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConcernedGroups from './components/ConcernedGroups.vue';
|
||||
import SocialIssuesAcc from './components/SocialIssuesAcc.vue';
|
||||
import Location from './components/Location.vue';
|
||||
import ConcernedGroups from "./components/ConcernedGroups.vue";
|
||||
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,38 +1,39 @@
|
||||
import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.js';
|
||||
import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods';
|
||||
import { getSocialIssues } from "ChillPersonAssets/vuejs/AccompanyingCourse/api.js";
|
||||
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
||||
|
||||
/*
|
||||
* Load socialActions by socialIssue (id)
|
||||
*/
|
||||
* Load socialActions by socialIssue (id)
|
||||
*/
|
||||
const getSocialActionByIssue = (id) => {
|
||||
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
|
||||
return fetch(url).then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error("Error with request resource response");
|
||||
});
|
||||
};
|
||||
|
||||
const getLocations = () => fetchResults('/api/1.0/main/location.json');
|
||||
const getLocations = () => fetchResults("/api/1.0/main/location.json");
|
||||
|
||||
const getLocationTypes = () => fetchResults('/api/1.0/main/location-type.json');
|
||||
|
||||
const getUserCurrentLocation =
|
||||
() => fetch('/api/1.0/main/user-current-location.json')
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
const getLocationTypes = () => fetchResults("/api/1.0/main/location-type.json");
|
||||
|
||||
const getUserCurrentLocation = () =>
|
||||
fetch("/api/1.0/main/user-current-location.json").then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error("Error with request resource response");
|
||||
});
|
||||
|
||||
/*
|
||||
* Load Location Type by defaultFor
|
||||
* @param {string} entity - can be "person" or "thirdparty"
|
||||
* Load Location Type by defaultFor
|
||||
* @param {string} entity - can be "person" or "thirdparty"
|
||||
*/
|
||||
const getLocationTypeByDefaultFor = (entity) => {
|
||||
return getLocationTypes().then(results =>
|
||||
results.filter(t => t.defaultFor === entity)[0]
|
||||
);
|
||||
return getLocationTypes().then(
|
||||
(results) => results.filter((t) => t.defaultFor === entity)[0],
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -43,26 +44,27 @@ const getLocationTypeByDefaultFor = (entity) => {
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
const postLocation = (body) => {
|
||||
const url = `/api/1.0/main/location.json`;
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
const url = `/api/1.0/main/location.json`;
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw Error("Error with request resource response");
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
getSocialIssues,
|
||||
getSocialActionByIssue,
|
||||
getLocations,
|
||||
getLocationTypes,
|
||||
getLocationTypeByDefaultFor,
|
||||
postLocation,
|
||||
getUserCurrentLocation
|
||||
getSocialIssues,
|
||||
getSocialActionByIssue,
|
||||
getLocations,
|
||||
getLocationTypes,
|
||||
getLocationTypeByDefaultFor,
|
||||
postLocation,
|
||||
getUserCurrentLocation,
|
||||
};
|
||||
|
@@ -1,219 +1,257 @@
|
||||
<template>
|
||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||
<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}`"
|
||||
>
|
||||
<person-text v-if="p.type === 'person'" :person="p" />
|
||||
<span v-else>{{ p.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||
<persons-bloc
|
||||
v-for="bloc in contextPersonsBlocs"
|
||||
v-bind:key="bloc.key"
|
||||
v-bind:bloc="bloc"
|
||||
v-bind:blocWidth="getBlocWidth"
|
||||
v-bind:setPersonsInBloc="setPersonsInBloc">
|
||||
</persons-bloc>
|
||||
</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}`">
|
||||
<person-text v-if="p.type === 'person'" :person="p"></person-text>
|
||||
<span v-else>{{ p.text }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="add-persons">
|
||||
<add-persons
|
||||
buttonTitle="activity.add_persons"
|
||||
modalTitle="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>
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
||||
import PersonsBloc from './ConcernedGroups/PersonsBloc.vue';
|
||||
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
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,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "ConcernedGroups",
|
||||
components: {
|
||||
AddPersons,
|
||||
PersonsBloc,
|
||||
PersonText
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
personsBlocs: [
|
||||
{ key: 'persons',
|
||||
title: 'activity.bloc_persons',
|
||||
persons: [],
|
||||
included: false
|
||||
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",
|
||||
},
|
||||
{ key: 'personsAssociated',
|
||||
title: '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: 'activity.bloc_thirdparty',
|
||||
persons: [],
|
||||
included: window.activity ? window.activity.activityType.thirdPartiesVisible !== 0 : true
|
||||
},
|
||||
{ key: 'users',
|
||||
title: '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
|
||||
},
|
||||
...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)) + '%';
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
};
|
||||
},
|
||||
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 {
|
||||
switch (bloc.key) {
|
||||
case 'persons':
|
||||
bloc.persons = this.persons;
|
||||
bloc.included = true;
|
||||
break;
|
||||
}
|
||||
optionsType = ["person", "thirdparty", "user"];
|
||||
}
|
||||
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' });
|
||||
return {
|
||||
type: optionsType,
|
||||
priority: null,
|
||||
uniq: false,
|
||||
button: {
|
||||
size: "btn-sm",
|
||||
},
|
||||
};
|
||||
},
|
||||
getBlocWidth() {
|
||||
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
||||
},
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
@@ -1,32 +1,30 @@
|
||||
<template>
|
||||
<li>
|
||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||
<span class="chill_denomination">
|
||||
<person-text :person="person" :isCut="true"></person-text>
|
||||
</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';
|
||||
|
||||
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>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
<style lang="css" scoped></style>
|
||||
|
@@ -1,41 +1,39 @@
|
||||
<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"
|
||||
v-bind:key="person.id"
|
||||
v-bind:person="person"
|
||||
@remove="removePerson">
|
||||
</person-badge>
|
||||
</ul>
|
||||
</div>
|
||||
</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';
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
<style lang="scss"></style>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<teleport to="#location">
|
||||
<div class="mb-3 row">
|
||||
<label :class="locationClassList">
|
||||
{{ $t("activity.location") }}
|
||||
{{ trans(ACTIVITY_LOCATION) }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<VueMultiselect
|
||||
@@ -13,18 +13,17 @@
|
||||
open-direction="top"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('activity.choose_location')"
|
||||
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||
:custom-label="customLabel"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
: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"
|
||||
>
|
||||
</VueMultiselect>
|
||||
<new-location v-bind:availableLocations="availableLocations"></new-location>
|
||||
/>
|
||||
<new-location v-bind:available-locations="availableLocations" />
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
@@ -34,6 +33,14 @@
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import NewLocation from "./Location/NewLocation.vue";
|
||||
import {
|
||||
trans,
|
||||
ACTIVITY_LOCATION,
|
||||
ACTIVITY_CHOOSE_LOCATION,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "Location",
|
||||
@@ -41,11 +48,20 @@ export default {
|
||||
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' : ''}`,
|
||||
}
|
||||
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activity", "availableLocations"]),
|
||||
@@ -61,16 +77,16 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
labelAccompanyingCourseLocation(value) {
|
||||
return `${value.address.text} (${value.locationType.title.fr})`
|
||||
return `${value.address.text} (${value.locationType.title.fr})`;
|
||||
},
|
||||
customLabel(value) {
|
||||
return value.locationType
|
||||
? value.name
|
||||
? value.name === '__AccompanyingCourseLocation__'
|
||||
? value.name === "__AccompanyingCourseLocation__"
|
||||
? this.labelAccompanyingCourseLocation(value)
|
||||
: `${value.name} (${value.locationType.title.fr})`
|
||||
: value.locationType.title.fr
|
||||
: '';
|
||||
: "";
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -3,40 +3,65 @@
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-sm btn-create" @click="openModal">
|
||||
{{ $t('activity.create_new_location') }}
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal.showModal"
|
||||
<modal
|
||||
v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
|
||||
@close="modal.showModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">
|
||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||
</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<template #body>
|
||||
<form>
|
||||
<div class="alert alert-warning" v-if="errors.length">
|
||||
<ul>
|
||||
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
|
||||
<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="">{{ $t('activity.choose_location_type') }}</option>
|
||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
||||
<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"
|
||||
>
|
||||
{{ t.title.fr }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ $t('activity.location_fields.type') }}</label>
|
||||
<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">{{ $t('activity.location_fields.name') }}</label>
|
||||
<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
|
||||
@@ -44,43 +69,74 @@
|
||||
:options="addAddress.options"
|
||||
:addressChangedCallback="submitNewAddress"
|
||||
v-if="showAddAddress"
|
||||
ref="addAddress">
|
||||
</add-address>
|
||||
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">{{ $t('activity.location_fields.phonenumber1') }}</label>
|
||||
<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">{{ $t('activity.location_fields.phonenumber2') }}</label>
|
||||
<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">{{ $t('activity.location_fields.email') }}</label>
|
||||
<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 v-slot:footer>
|
||||
<button class="btn btn-save"
|
||||
<template #footer>
|
||||
<button
|
||||
class="btn btn-save"
|
||||
@click.prevent="saveNewLocation"
|
||||
>
|
||||
{{ $t('action.save') }}
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
|
||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getLocationTypes } from "../../api";
|
||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
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,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "NewLocation",
|
||||
@@ -88,7 +144,20 @@ export default {
|
||||
Modal,
|
||||
AddAddress,
|
||||
},
|
||||
props: ['availableLocations'],
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
SAVE,
|
||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||
ACTIVITY_CREATE_NEW_LOCATION,
|
||||
};
|
||||
},
|
||||
props: ["availableLocations"],
|
||||
data() {
|
||||
return {
|
||||
errors: [],
|
||||
@@ -103,35 +172,42 @@ export default {
|
||||
locationTypes: [],
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||
},
|
||||
addAddress: {
|
||||
options: {
|
||||
button: {
|
||||
text: { create: 'activity.create_address', edit: 'activity.edit_address' },
|
||||
size: 'btn-sm'
|
||||
text: {
|
||||
create: "activity.create_address",
|
||||
edit: "activity.edit_address",
|
||||
},
|
||||
size: "btn-sm",
|
||||
},
|
||||
title: {
|
||||
create: "activity.create_address",
|
||||
edit: "activity.edit_address",
|
||||
},
|
||||
title: { create: 'activity.create_address', edit: 'activity.edit_address' },
|
||||
},
|
||||
context: {
|
||||
target: { //name, id
|
||||
},
|
||||
target: {
|
||||
//name, id
|
||||
},
|
||||
edit: false,
|
||||
addressId: null,
|
||||
defaults: window.addaddress
|
||||
}
|
||||
}
|
||||
}
|
||||
defaults: window.addaddress,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activity']),
|
||||
...mapState(["activity"]),
|
||||
selectType: {
|
||||
get() {
|
||||
return this.selected.type;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.type = value;
|
||||
}
|
||||
},
|
||||
},
|
||||
inputName: {
|
||||
get() {
|
||||
@@ -139,7 +215,7 @@ export default {
|
||||
},
|
||||
set(value) {
|
||||
this.selected.name = value;
|
||||
}
|
||||
},
|
||||
},
|
||||
inputEmail: {
|
||||
get() {
|
||||
@@ -147,7 +223,7 @@ export default {
|
||||
},
|
||||
set(value) {
|
||||
this.selected.email = value;
|
||||
}
|
||||
},
|
||||
},
|
||||
inputPhonenumber1: {
|
||||
get() {
|
||||
@@ -155,7 +231,7 @@ export default {
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber1 = value;
|
||||
}
|
||||
},
|
||||
},
|
||||
inputPhonenumber2: {
|
||||
get() {
|
||||
@@ -163,15 +239,18 @@ export default {
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber2 = value;
|
||||
}
|
||||
},
|
||||
},
|
||||
hasPhonenumber1() {
|
||||
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== "";
|
||||
return (
|
||||
this.selected.phonenumber1 !== null &&
|
||||
this.selected.phonenumber1 !== ""
|
||||
);
|
||||
},
|
||||
showAddAddress() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.addressRequired !== 'never') {
|
||||
if (this.selected.type.addressRequired !== "never") {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
@@ -180,7 +259,7 @@ export default {
|
||||
showContactData() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.contactData !== 'never') {
|
||||
if (this.selected.type.contactData !== "never") {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
@@ -195,28 +274,39 @@ export default {
|
||||
let cond = true;
|
||||
this.errors = [];
|
||||
if (!this.selected.type) {
|
||||
this.errors.push('Type de localisation requis');
|
||||
this.errors.push("Type de localisation requis");
|
||||
cond = false;
|
||||
} else {
|
||||
if (this.selected.type.addressRequired === 'required' && !this.selected.addressId) {
|
||||
this.errors.push('Adresse requise');
|
||||
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');
|
||||
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');
|
||||
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);
|
||||
})
|
||||
getLocationTypes().then((results) => {
|
||||
this.locationTypes = results.filter(
|
||||
(t) => t.availableForUsers === true,
|
||||
);
|
||||
});
|
||||
},
|
||||
openModal() {
|
||||
this.modal.showModal = true;
|
||||
@@ -224,11 +314,11 @@ export default {
|
||||
saveNewLocation() {
|
||||
if (this.checkForm()) {
|
||||
let body = {
|
||||
type: 'location',
|
||||
type: "location",
|
||||
name: this.selected.name,
|
||||
locationType: {
|
||||
id: this.selected.type.id,
|
||||
type: 'location-type'
|
||||
type: "location-type",
|
||||
},
|
||||
phonenumber1: this.selected.phonenumber1,
|
||||
phonenumber2: this.selected.phonenumber2,
|
||||
@@ -237,36 +327,36 @@ export default {
|
||||
if (this.selected.addressId) {
|
||||
body = Object.assign(body, {
|
||||
address: {
|
||||
id: this.selected.addressId
|
||||
}
|
||||
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]
|
||||
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.$store.dispatch("updateLocation", response);
|
||||
this.modal.showModal = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.name === 'ValidationException') {
|
||||
if (error.name === "ValidationException") {
|
||||
for (let v of error.violations) {
|
||||
this.errors.push(v);
|
||||
}
|
||||
} else {
|
||||
this.errors.push('An error occurred');
|
||||
this.errors.push("An error occurred");
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
submitNewAddress(payload) {
|
||||
this.selected.addressId = payload.addressId;
|
||||
this.addAddress.context.addressId = payload.addressId;
|
||||
this.addAddress.context.edit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@@ -1,228 +1,264 @@
|
||||
<template>
|
||||
<teleport to="#social-issues-acc">
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialIssuesClassList">{{ $t('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="$t('activity.choose_other_social_issue')"
|
||||
:options="socialIssuesOther"
|
||||
@select="addIssueInList">
|
||||
</VueMultiselect>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label :class="socialActionsClassList">{{ $t('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 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>
|
||||
|
||||
<span v-else-if="socialIssuesSelected.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
||||
{{ $t('activity.select_first_a_social_issue') }}
|
||||
</span>
|
||||
<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>
|
||||
|
||||
<template v-else-if="socialActionsList.length > 0">
|
||||
<check-social-action
|
||||
v-if="socialIssuesSelected.length || socialActionsSelected.length"
|
||||
v-for="action in socialActionsList"
|
||||
:key="action.id"
|
||||
:action="action"
|
||||
:selection="socialActionsSelected"
|
||||
@updateSelected="updateActionsSelected">
|
||||
</check-social-action>
|
||||
</template>
|
||||
<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="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
||||
{{ $t('activity.social_action_list_empty') }}
|
||||
</span>
|
||||
<template
|
||||
v-else-if="
|
||||
socialActionsList.length > 0 &&
|
||||
(socialIssuesSelected.length ||
|
||||
socialActionsSelected.length)
|
||||
"
|
||||
>
|
||||
<check-social-action
|
||||
v-for="action in socialActionsList"
|
||||
:key="action.id"
|
||||
:action="action"
|
||||
:selection="socialActionsSelected"
|
||||
@updateSelected="updateActionsSelected"
|
||||
>
|
||||
</check-social-action>
|
||||
</template>
|
||||
|
||||
|
||||
</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>
|
||||
import VueMultiselect from 'vue-multiselect';
|
||||
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue';
|
||||
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue';
|
||||
import { getSocialIssues, getSocialActionByIssue } from '../api.js';
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
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,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "SocialIssuesAcc",
|
||||
components: {
|
||||
CheckSocialIssue,
|
||||
CheckSocialAction,
|
||||
VueMultiselect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
issueIsLoading: false,
|
||||
actionIsLoading: false,
|
||||
actionAreLoaded: false,
|
||||
socialIssuesClassList:
|
||||
`col-form-label ${document.querySelector('input#chill_activitybundle_activity_socialIssues').getAttribute('required') ? 'required' : ''}`,
|
||||
socialActionsClassList:
|
||||
`col-form-label ${document.querySelector('input#chill_activitybundle_activity_socialActions').getAttribute('required') ? 'required' : ''}`,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
socialIssuesList() {
|
||||
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
||||
},
|
||||
socialIssuesSelected() {
|
||||
return this.$store.state.activity.socialIssues;
|
||||
},
|
||||
socialIssuesOther() {
|
||||
return this.$store.state.socialIssuesOther;
|
||||
},
|
||||
socialActionsList() {
|
||||
return this.$store.getters.socialActionsListSorted;
|
||||
},
|
||||
socialActionsSelected() {
|
||||
return this.$store.state.activity.socialActions;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
/* Load others issues in multiselect
|
||||
*/
|
||||
this.issueIsLoading = true;
|
||||
this.actionAreLoaded = false;
|
||||
getSocialIssues().then(response => new Promise((resolve, reject) => {
|
||||
this.$store.commit('updateIssuesOther', response.results);
|
||||
|
||||
/* Add in list the issues already associated (if not yet listed)
|
||||
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 ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
||||
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
socialIssuesList() {
|
||||
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
||||
},
|
||||
socialIssuesSelected() {
|
||||
return this.$store.state.activity.socialIssues;
|
||||
},
|
||||
socialIssuesOther() {
|
||||
return this.$store.state.socialIssuesOther;
|
||||
},
|
||||
socialActionsList() {
|
||||
return this.$store.getters.socialActionsListSorted;
|
||||
},
|
||||
socialActionsSelected() {
|
||||
return this.$store.state.activity.socialActions;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
/* Load others issues in multiselect
|
||||
*/
|
||||
this.socialIssuesSelected.forEach(issue => {
|
||||
if (this.socialIssuesList.filter(i => i.id === issue.id).length !== 1) {
|
||||
this.$store.commit('addIssueInList', issue);
|
||||
}
|
||||
}, this);
|
||||
this.issueIsLoading = true;
|
||||
this.actionAreLoaded = false;
|
||||
getSocialIssues().then(
|
||||
(response) =>
|
||||
new Promise((resolve) => {
|
||||
this.$store.commit("updateIssuesOther", response.results);
|
||||
|
||||
/* Remove from multiselect the issues that are not yet in checkbox list
|
||||
*/
|
||||
this.socialIssuesList.forEach(issue => {
|
||||
this.$store.commit('removeIssueInOther', issue);
|
||||
}, this);
|
||||
/* 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);
|
||||
}
|
||||
}, this);
|
||||
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit('filterList', 'issues');
|
||||
/* Remove from multiselect the issues that are not yet in checkbox list
|
||||
*/
|
||||
this.socialIssuesList.forEach((issue) => {
|
||||
this.$store.commit("removeIssueInOther", issue);
|
||||
}, this);
|
||||
|
||||
/* Add in list the actions already associated (if not yet listed)
|
||||
*/
|
||||
this.socialActionsSelected.forEach(action => {
|
||||
this.$store.commit('addActionInList', action);
|
||||
}, this);
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit("filterList", "issues");
|
||||
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit('filterList', 'actions');
|
||||
/* Add in list the actions already associated (if not yet listed)
|
||||
*/
|
||||
this.socialActionsSelected.forEach((action) => {
|
||||
this.$store.commit("addActionInList", action);
|
||||
}, this);
|
||||
|
||||
this.issueIsLoading = false;
|
||||
this.actionAreLoaded = true;
|
||||
this.updateActionsList();
|
||||
resolve();
|
||||
}));
|
||||
},
|
||||
methods: {
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit("filterList", "actions");
|
||||
|
||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||
this.issueIsLoading = false;
|
||||
this.actionAreLoaded = true;
|
||||
this.updateActionsList();
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
},
|
||||
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 => {
|
||||
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.actionIsLoading = true;
|
||||
getSocialActionByIssue(item.id)
|
||||
.then(actions => new Promise((resolve, reject) => {
|
||||
this.$store.commit("filterList", "actions");
|
||||
|
||||
actions.results.forEach(action => {
|
||||
this.$store.commit('addActionInList', action);
|
||||
}, this);
|
||||
|
||||
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>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
<style lang="scss" scoped>
|
||||
span.multiselect__single {
|
||||
display: none !important;
|
||||
}
|
||||
span.multiselect__single {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,44 +1,43 @@
|
||||
<template>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="action"
|
||||
v-bind:id="action.id"
|
||||
v-bind:value="action"
|
||||
<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" v-bind:for="action.id">
|
||||
<span class="badge bg-light text-dark">{{ action.text }}</span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</span>
|
||||
<label class="form-check-label" :for="action.id">
|
||||
<span class="badge bg-light text-dark">{{ 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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'ChillMainAssets/module/bootstrap/shared';
|
||||
@import 'ChillPersonAssets/chill/scss/mixins';
|
||||
@import 'ChillMainAssets/chill/scss/chill_variables';
|
||||
@import "ChillMainAssets/module/bootstrap/shared";
|
||||
@import "ChillPersonAssets/chill/scss/mixins";
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
span.badge {
|
||||
@include badge_social($social-action-color);
|
||||
font-size: 95%;
|
||||
|
@@ -1,44 +1,45 @@
|
||||
<template>
|
||||
<span class="inline-choice">
|
||||
<div class="form-check">
|
||||
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
v-model="selected"
|
||||
name="issue"
|
||||
v-bind:id="issue.id"
|
||||
v-bind:value="issue"
|
||||
<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" v-bind:for="issue.id">
|
||||
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</span>
|
||||
<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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'ChillMainAssets/module/bootstrap/shared';
|
||||
@import 'ChillPersonAssets/chill/scss/mixins';
|
||||
@import 'ChillMainAssets/chill/scss/chill_variables';
|
||||
@import "ChillMainAssets/module/bootstrap/shared";
|
||||
@import "ChillPersonAssets/chill/scss/mixins";
|
||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||
span.badge {
|
||||
@include badge_social($social-issue-color);
|
||||
font-size: 95%;
|
||||
|
@@ -1,45 +1,44 @@
|
||||
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
|
||||
import { multiSelectMessages } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||
import { personMessages } from "ChillPersonAssets/vuejs/_js/i18n";
|
||||
import { multiSelectMessages } from "ChillMainAssets/vuejs/_js/i18n";
|
||||
|
||||
const activityMessages = {
|
||||
fr: {
|
||||
activity: {
|
||||
//
|
||||
errors: "Le formulaire contient des erreurs",
|
||||
social_issues: "Problématiques sociales",
|
||||
choose_other_social_issue: "Ajouter une autre problématique sociale...",
|
||||
social_actions: "Actions d'accompagnement",
|
||||
select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale",
|
||||
social_action_list_empty: "Aucune action sociale disponible",
|
||||
fr: {
|
||||
activity: {
|
||||
//
|
||||
errors: "Le formulaire contient des erreurs",
|
||||
social_issues: "Problématiques sociales",
|
||||
choose_other_social_issue: "Ajouter une autre problématique sociale...",
|
||||
social_actions: "Actions d'accompagnement",
|
||||
select_first_a_social_issue:
|
||||
"Sélectionnez d'abord une problématique sociale",
|
||||
social_action_list_empty: "Aucune action sociale disponible",
|
||||
|
||||
//
|
||||
add_persons: "Ajouter des personnes concernées",
|
||||
bloc_persons: "Usagers",
|
||||
bloc_persons_associated: "Usagers du parcours",
|
||||
bloc_persons_not_associated: "Tiers non-pro.",
|
||||
bloc_thirdparty: "Tiers professionnels",
|
||||
bloc_users: "T(M)S",
|
||||
//
|
||||
add_persons: "Ajouter des personnes concernées",
|
||||
bloc_persons: "Usagers",
|
||||
bloc_persons_associated: "Usagers du parcours",
|
||||
bloc_persons_not_associated: "Tiers non-pro.",
|
||||
bloc_thirdparty: "Tiers professionnels",
|
||||
bloc_users: "T(M)S",
|
||||
|
||||
//
|
||||
location: "Localisation",
|
||||
choose_location: "Choisissez une localisation",
|
||||
choose_location_type: "Choisissez un type de localisation",
|
||||
create_new_location: "Créer une nouvelle localisation",
|
||||
location_fields: {
|
||||
name: "Nom",
|
||||
type: "Type",
|
||||
phonenumber1: "Téléphone",
|
||||
phonenumber2: "Autre téléphone",
|
||||
email: "Adresse courriel",
|
||||
},
|
||||
create_address: 'Créer une adresse',
|
||||
edit_address: "Modifier l'adresse"
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
location: "Localisation",
|
||||
choose_location: "Choisissez une localisation",
|
||||
choose_location_type: "Choisissez un type de localisation",
|
||||
create_new_location: "Créer une nouvelle localisation",
|
||||
location_fields: {
|
||||
name: "Nom",
|
||||
type: "Type",
|
||||
phonenumber1: "Téléphone",
|
||||
phonenumber2: "Autre téléphone",
|
||||
email: "Adresse courriel",
|
||||
},
|
||||
create_address: "Créer une adresse",
|
||||
edit_address: "Modifier l'adresse",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(activityMessages.fr, personMessages.fr, multiSelectMessages.fr);
|
||||
|
||||
export {
|
||||
activityMessages
|
||||
};
|
||||
export { activityMessages };
|
||||
|
@@ -1,86 +1,86 @@
|
||||
import { createApp } from 'vue';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||
import { activityMessages } from './i18n'
|
||||
import store from './store'
|
||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
|
||||
import { createApp } from "vue";
|
||||
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||
import { activityMessages } from "./i18n";
|
||||
import store from "./store";
|
||||
import PickTemplate from "ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue";
|
||||
import { fetchTemplates } from "ChillDocGeneratorAssets/api/pickTemplate.js";
|
||||
|
||||
import App from './App.vue';
|
||||
import App from "./App.vue";
|
||||
|
||||
const i18n = _createI18n(activityMessages);
|
||||
|
||||
// app for activity
|
||||
|
||||
const hasSocialIssues = document.querySelector('#social-issues-acc') !== null;
|
||||
const hasLocation = document.querySelector('#location') !== null;
|
||||
const hasPerson = document.querySelector('#add-persons') !== null;
|
||||
const hasSocialIssues = document.querySelector("#social-issues-acc") !== null;
|
||||
const hasLocation = document.querySelector("#location") !== null;
|
||||
const hasPerson = document.querySelector("#add-persons") !== null;
|
||||
|
||||
const app = createApp({
|
||||
template: `<app
|
||||
template: `<app
|
||||
:hasSocialIssues="hasSocialIssues"
|
||||
:hasLocation="hasLocation"
|
||||
:hasPerson="hasPerson"
|
||||
></app>`,
|
||||
data() {
|
||||
return {
|
||||
hasSocialIssues,
|
||||
hasLocation,
|
||||
hasPerson,
|
||||
};
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
hasSocialIssues,
|
||||
hasLocation,
|
||||
hasPerson,
|
||||
};
|
||||
},
|
||||
})
|
||||
.use(store)
|
||||
.use(i18n)
|
||||
.component('app', App)
|
||||
.mount('#activity');
|
||||
|
||||
.use(store)
|
||||
.use(i18n)
|
||||
.component("app", App)
|
||||
.mount("#activity");
|
||||
|
||||
// app for picking template
|
||||
|
||||
const i18nGendoc = _createI18n({});
|
||||
|
||||
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
|
||||
fetchTemplates(el.dataset.entityClass).then(templates => {
|
||||
const picker = {
|
||||
template:
|
||||
'<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' +
|
||||
':entityClass="faked" @go-to-generate-document="generateDoc"></pick-template>',
|
||||
components: {
|
||||
PickTemplate,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templates: templates,
|
||||
entityId: el.dataset.entityId,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateDoc({event, link, template}) {
|
||||
console.log('generateDoc');
|
||||
console.log('link', link);
|
||||
console.log('template', template);
|
||||
|
||||
let hiddenInput = document.querySelector("input[data-template-id]");
|
||||
|
||||
if (hiddenInput === null) {
|
||||
console.error('hidden input not found');
|
||||
return;
|
||||
}
|
||||
|
||||
hiddenInput.value = template;
|
||||
|
||||
let form = document.querySelector('form[name="chill_activitybundle_activity"');
|
||||
|
||||
if (form === null) {
|
||||
console.error('form not found');
|
||||
return;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
document.querySelectorAll("div[data-docgen-template-picker]").forEach((el) => {
|
||||
fetchTemplates(el.dataset.entityClass).then((templates) => {
|
||||
const picker = {
|
||||
template:
|
||||
'<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' +
|
||||
':entityClass="faked" @go-to-generate-document="generateDoc"></pick-template>',
|
||||
components: {
|
||||
PickTemplate,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templates: templates,
|
||||
entityId: el.dataset.entityId,
|
||||
};
|
||||
createApp(picker).use(i18nGendoc).mount(el);
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
generateDoc({ event, link, template }) {
|
||||
console.log("generateDoc");
|
||||
console.log("link", link);
|
||||
console.log("template", template);
|
||||
|
||||
let hiddenInput = document.querySelector("input[data-template-id]");
|
||||
|
||||
if (hiddenInput === null) {
|
||||
console.error("hidden input not found");
|
||||
return;
|
||||
}
|
||||
|
||||
hiddenInput.value = template;
|
||||
|
||||
let form = document.querySelector(
|
||||
'form[name="chill_activitybundle_activity"',
|
||||
);
|
||||
|
||||
if (form === null) {
|
||||
console.error("form not found");
|
||||
return;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
},
|
||||
},
|
||||
};
|
||||
createApp(picker).use(i18nGendoc).mount(el);
|
||||
});
|
||||
});
|
||||
|
@@ -1,350 +1,379 @@
|
||||
import 'es6-promise/auto';
|
||||
import { createStore } from 'vuex';
|
||||
import { postLocation } from './api';
|
||||
import prepareLocations from './store.locations.js';
|
||||
import "es6-promise/auto";
|
||||
import { createStore } from "vuex";
|
||||
import { postLocation } from "./api";
|
||||
import prepareLocations from "./store.locations.js";
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
const debug = process.env.NODE_ENV !== "production";
|
||||
//console.log('window.activity', window.activity);
|
||||
|
||||
const addIdToValue = (string, id) => {
|
||||
let array = string ? string.split(',') : [];
|
||||
array.push(id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
let array = string ? string.split(",") : [];
|
||||
array.push(id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
const removeIdFromValue = (string, id) => {
|
||||
let array = string.split(',');
|
||||
array = array.filter(el => el !== id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
let array = string.split(",");
|
||||
array = array.filter((el) => el !== id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
const store = createStore({
|
||||
strict: debug,
|
||||
state: {
|
||||
activity: window.activity,
|
||||
socialIssuesOther: [],
|
||||
socialActionsList: [],
|
||||
availableLocations: [],
|
||||
strict: debug,
|
||||
state: {
|
||||
me: null,
|
||||
activity: window.activity,
|
||||
accompanyingPeriodWorks: [],
|
||||
socialIssuesOther: [],
|
||||
socialActionsList: [],
|
||||
availableLocations: [],
|
||||
},
|
||||
getters: {
|
||||
suggestedEntities(state) {
|
||||
if (
|
||||
typeof state.activity.accompanyingPeriod === "undefined" ||
|
||||
state.activity.accompanyingPeriod === null
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const allEntities = [
|
||||
...store.getters.suggestedPersons,
|
||||
...store.getters.suggestedRequestor,
|
||||
...store.getters.suggestedUsers,
|
||||
...store.getters.suggestedResources,
|
||||
];
|
||||
const uniqueIds = [
|
||||
...new Set(allEntities.map((i) => `${i.type}-${i.id}`)),
|
||||
];
|
||||
return Array.from(
|
||||
uniqueIds,
|
||||
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0],
|
||||
);
|
||||
},
|
||||
getters: {
|
||||
suggestedEntities(state) {
|
||||
if (typeof state.activity.accompanyingPeriod === "undefined" || state.activity.accompanyingPeriod === null) {
|
||||
return [];
|
||||
}
|
||||
const allEntities = [
|
||||
...store.getters.suggestedPersons,
|
||||
...store.getters.suggestedRequestor,
|
||||
...store.getters.suggestedUser,
|
||||
...store.getters.suggestedResources,
|
||||
];
|
||||
const uniqueIds = [
|
||||
...new Set(allEntities.map((i) => `${i.type}-${i.id}`)),
|
||||
];
|
||||
return Array.from(
|
||||
uniqueIds,
|
||||
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0]
|
||||
);
|
||||
},
|
||||
suggestedPersons(state) {
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
return state.activity.activityType.personsVisible === 0
|
||||
? []
|
||||
: state.activity.accompanyingPeriod.participations
|
||||
.filter((p) => p.endDate === null)
|
||||
.map((p) => p.person)
|
||||
.filter((p) => !existingPersonIds.includes(p.id));
|
||||
},
|
||||
suggestedRequestor(state) {
|
||||
if (state.activity.accompanyingPeriod.requestor === null) {
|
||||
return [];
|
||||
}
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map(
|
||||
(p) => p.id
|
||||
suggestedPersons(state) {
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
return state.activity.activityType.personsVisible === 0
|
||||
? []
|
||||
: state.activity.accompanyingPeriod.participations
|
||||
.filter((p) => p.endDate === null)
|
||||
.map((p) => p.person)
|
||||
.filter((p) => !existingPersonIds.includes(p.id));
|
||||
},
|
||||
suggestedRequestor(state) {
|
||||
if (state.activity.accompanyingPeriod.requestor === null) {
|
||||
return [];
|
||||
}
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map(
|
||||
(p) => p.id,
|
||||
);
|
||||
|
||||
return [state.activity.accompanyingPeriod.requestor].filter(
|
||||
(r) =>
|
||||
(r.type === "person" &&
|
||||
!existingPersonIds.includes(r.id) &&
|
||||
state.activity.activityType.personsVisible !== 0) ||
|
||||
(r.type === "thirdparty" &&
|
||||
!existingThirdPartyIds.includes(r.id) &&
|
||||
state.activity.activityType.thirdPartiesVisible !== 0),
|
||||
);
|
||||
},
|
||||
suggestedUsers(state) {
|
||||
const existingUserIds = state.activity.users.map((p) => p.id);
|
||||
let suggestedUsers =
|
||||
state.activity.activityType.usersVisible === 0
|
||||
? []
|
||||
: [state.activity.accompanyingPeriod.user].filter(
|
||||
(u) => u !== null && !existingUserIds.includes(u.id),
|
||||
);
|
||||
|
||||
return [state.activity.accompanyingPeriod.requestor].filter(
|
||||
(r) =>
|
||||
(r.type === "person" &&
|
||||
!existingPersonIds.includes(r.id) &&
|
||||
state.activity.activityType.personsVisible !== 0) ||
|
||||
(r.type === "thirdparty" &&
|
||||
!existingThirdPartyIds.includes(r.id) &&
|
||||
state.activity.activityType.thirdPartiesVisible !== 0)
|
||||
);
|
||||
},
|
||||
suggestedUser(state) {
|
||||
const existingUserIds = state.activity.users.map((p) => p.id);
|
||||
return state.activity.activityType.usersVisible === 0
|
||||
? []
|
||||
: [state.activity.accompanyingPeriod.user].filter(
|
||||
(u) => u !== null && !existingUserIds.includes(u.id)
|
||||
);
|
||||
},
|
||||
suggestedResources(state) {
|
||||
const resources = state.activity.accompanyingPeriod.resources;
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map(
|
||||
(p) => p.id
|
||||
);
|
||||
return state.activity.accompanyingPeriod.resources
|
||||
.map((r) => r.resource)
|
||||
.filter(
|
||||
(r) =>
|
||||
(r.type === "person" &&
|
||||
!existingPersonIds.includes(r.id) &&
|
||||
state.activity.activityType.personsVisible !== 0) ||
|
||||
(r.type === "thirdparty" &&
|
||||
!existingThirdPartyIds.includes(r.id) &&
|
||||
state.activity.activityType.thirdPartiesVisible !== 0)
|
||||
);
|
||||
},
|
||||
socialActionsListSorted(state) {
|
||||
return [ ...state.socialActionsList].sort((a, b) => a.ordering - b.ordering);
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
// SocialIssueAcc
|
||||
addIssueInList(state, issue) {
|
||||
//console.log('add issue list', issue.id);
|
||||
state.activity.accompanyingPeriod.socialIssues.push(issue);
|
||||
},
|
||||
addIssueSelected(state, issue) {
|
||||
//console.log('add issue selected', issue.id);
|
||||
state.activity.socialIssues.push(issue);
|
||||
},
|
||||
updateIssuesSelected(state, issues) {
|
||||
//console.log('update issues selected', issues);
|
||||
state.activity.socialIssues = issues;
|
||||
},
|
||||
updateIssuesOther(state, payload) {
|
||||
//console.log('update issues other');
|
||||
state.socialIssuesOther = payload;
|
||||
},
|
||||
removeIssueInOther(state, issue) {
|
||||
//console.log('remove issue other', issue.id);
|
||||
state.socialIssuesOther = state.socialIssuesOther.filter(
|
||||
(i) => i.id !== issue.id
|
||||
);
|
||||
},
|
||||
resetActionsList(state) {
|
||||
//console.log('reset list actions');
|
||||
state.socialActionsList = [];
|
||||
},
|
||||
addActionInList(state, action) {
|
||||
state.socialActionsList.push(action);
|
||||
},
|
||||
updateActionsSelected(state, actions) {
|
||||
//console.log('update actions selected', actions);
|
||||
state.activity.socialActions = actions;
|
||||
},
|
||||
filterList(state, list) {
|
||||
const filterList = (list) => {
|
||||
// remove duplicates entries
|
||||
list = list.filter(
|
||||
(value, index) =>
|
||||
list.findIndex((array) => array.id === value.id) ===
|
||||
index
|
||||
);
|
||||
// alpha sort
|
||||
list.sort((a, b) =>
|
||||
a.text > b.text ? 1 : b.text > a.text ? -1 : 0
|
||||
);
|
||||
return list;
|
||||
};
|
||||
if (list === "issues") {
|
||||
state.activity.accompanyingPeriod.socialIssues = filterList(
|
||||
state.activity.accompanyingPeriod.socialIssues
|
||||
);
|
||||
}
|
||||
if (list === "actions") {
|
||||
state.socialActionsList = filterList(state.socialActionsList);
|
||||
}
|
||||
},
|
||||
state.accompanyingPeriodWorks.forEach((work) => {
|
||||
work.referrers.forEach((r) => {
|
||||
if (!existingUserIds.includes(r.id)) {
|
||||
suggestedUsers.push(r);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Add the current user from the state
|
||||
if (state.me && !existingUserIds.includes(state.me.id)) {
|
||||
suggestedUsers.push(state.me);
|
||||
}
|
||||
// console.log("suggested users", suggestedUsers);
|
||||
|
||||
// ConcernedGroups
|
||||
addPersonsInvolved(state, payload) {
|
||||
console.log("### mutation addPersonsInvolved", payload);
|
||||
switch (payload.result.type) {
|
||||
case "person":
|
||||
state.activity.persons.push(payload.result);
|
||||
break;
|
||||
case "thirdparty":
|
||||
state.activity.thirdParties.push(payload.result);
|
||||
break;
|
||||
case "user":
|
||||
state.activity.users.push(payload.result);
|
||||
break;
|
||||
}
|
||||
},
|
||||
removePersonInvolved(state, payload) {
|
||||
//console.log('### mutation removePersonInvolved', payload.type);
|
||||
switch (payload.type) {
|
||||
case "person":
|
||||
state.activity.persons = state.activity.persons.filter(
|
||||
(person) => person !== payload
|
||||
);
|
||||
break;
|
||||
case "thirdparty":
|
||||
state.activity.thirdParties =
|
||||
state.activity.thirdParties.filter(
|
||||
(thirdparty) => thirdparty !== payload
|
||||
);
|
||||
break;
|
||||
case "user":
|
||||
state.activity.users = state.activity.users.filter(
|
||||
(user) => user !== payload
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
updateLocation(state, value) {
|
||||
console.log("### mutation: updateLocation", value);
|
||||
state.activity.location = value;
|
||||
},
|
||||
addAvailableLocationGroup(state, group) {
|
||||
state.availableLocations.push(group);
|
||||
return suggestedUsers;
|
||||
},
|
||||
suggestedResources(state) {
|
||||
// const resources = state.activity.accompanyingPeriod.resources;
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map(
|
||||
(p) => p.id,
|
||||
);
|
||||
return state.activity.accompanyingPeriod.resources
|
||||
.map((r) => r.resource)
|
||||
.filter(
|
||||
(r) =>
|
||||
(r.type === "person" &&
|
||||
!existingPersonIds.includes(r.id) &&
|
||||
state.activity.activityType.personsVisible !== 0) ||
|
||||
(r.type === "thirdparty" &&
|
||||
!existingThirdPartyIds.includes(r.id) &&
|
||||
state.activity.activityType.thirdPartiesVisible !== 0),
|
||||
);
|
||||
},
|
||||
socialActionsListSorted(state) {
|
||||
return [...state.socialActionsList].sort(
|
||||
(a, b) => a.ordering - b.ordering,
|
||||
);
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setWhoAmI(state, me) {
|
||||
state.me = me;
|
||||
},
|
||||
// SocialIssueAcc
|
||||
addIssueInList(state, issue) {
|
||||
//console.log('add issue list', issue.id);
|
||||
state.activity.accompanyingPeriod.socialIssues.push(issue);
|
||||
},
|
||||
addIssueSelected(state, issue) {
|
||||
//console.log('add issue selected', issue.id);
|
||||
state.activity.socialIssues.push(issue);
|
||||
},
|
||||
updateIssuesSelected(state, issues) {
|
||||
//console.log('update issues selected', issues);
|
||||
state.activity.socialIssues = issues;
|
||||
},
|
||||
updateIssuesOther(state, payload) {
|
||||
//console.log('update issues other');
|
||||
state.socialIssuesOther = payload;
|
||||
},
|
||||
removeIssueInOther(state, issue) {
|
||||
//console.log('remove issue other', issue.id);
|
||||
state.socialIssuesOther = state.socialIssuesOther.filter(
|
||||
(i) => i.id !== issue.id,
|
||||
);
|
||||
},
|
||||
resetActionsList(state) {
|
||||
//console.log('reset list actions');
|
||||
state.socialActionsList = [];
|
||||
},
|
||||
addActionInList(state, action) {
|
||||
state.socialActionsList.push(action);
|
||||
},
|
||||
updateActionsSelected(state, actions) {
|
||||
//console.log('update actions selected', actions);
|
||||
state.activity.socialActions = actions;
|
||||
},
|
||||
filterList(state, list) {
|
||||
const filterList = (list) => {
|
||||
// remove duplicates entries
|
||||
list = list.filter(
|
||||
(value, index) =>
|
||||
list.findIndex((array) => array.id === value.id) === index,
|
||||
);
|
||||
// alpha sort
|
||||
list.sort((a, b) => (a.text > b.text ? 1 : b.text > a.text ? -1 : 0));
|
||||
return list;
|
||||
};
|
||||
if (list === "issues") {
|
||||
state.activity.accompanyingPeriod.socialIssues = filterList(
|
||||
state.activity.accompanyingPeriod.socialIssues,
|
||||
);
|
||||
}
|
||||
if (list === "actions") {
|
||||
state.socialActionsList = filterList(state.socialActionsList);
|
||||
}
|
||||
},
|
||||
|
||||
// ConcernedGroups
|
||||
addPersonsInvolved(state, payload) {
|
||||
console.log("### mutation addPersonsInvolved", payload);
|
||||
switch (payload.result.type) {
|
||||
case "person":
|
||||
state.activity.persons.push(payload.result);
|
||||
break;
|
||||
case "thirdparty":
|
||||
state.activity.thirdParties.push(payload.result);
|
||||
break;
|
||||
case "user":
|
||||
state.activity.users.push(payload.result);
|
||||
break;
|
||||
}
|
||||
},
|
||||
removePersonInvolved(state, payload) {
|
||||
//console.log('### mutation removePersonInvolved', payload.type);
|
||||
switch (payload.type) {
|
||||
case "person":
|
||||
state.activity.persons = state.activity.persons.filter(
|
||||
(person) => person !== payload,
|
||||
);
|
||||
break;
|
||||
case "thirdparty":
|
||||
state.activity.thirdParties = state.activity.thirdParties.filter(
|
||||
(thirdparty) => thirdparty !== payload,
|
||||
);
|
||||
break;
|
||||
case "user":
|
||||
state.activity.users = state.activity.users.filter(
|
||||
(user) => user !== payload,
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
updateLocation(state, value) {
|
||||
console.log("### mutation: updateLocation", value);
|
||||
state.activity.location = value;
|
||||
},
|
||||
addAvailableLocationGroup(state, group) {
|
||||
state.availableLocations.push(group);
|
||||
},
|
||||
setAccompanyingPeriodWorks(state, works) {
|
||||
state.accompanyingPeriodWorks = works;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
addIssueSelected({ commit }, issue) {
|
||||
let aSocialIssues = document.getElementById(
|
||||
"chill_activitybundle_activity_socialIssues",
|
||||
);
|
||||
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
|
||||
commit("addIssueSelected", issue);
|
||||
},
|
||||
updateIssuesSelected({ commit }, payload) {
|
||||
let aSocialIssues = document.getElementById(
|
||||
"chill_activitybundle_activity_socialIssues",
|
||||
);
|
||||
aSocialIssues.value = "";
|
||||
payload.forEach((item) => {
|
||||
aSocialIssues.value = addIdToValue(aSocialIssues.value, item.id);
|
||||
});
|
||||
commit("updateIssuesSelected", payload);
|
||||
},
|
||||
updateActionsSelected({ commit }, payload) {
|
||||
let aSocialActions = document.getElementById(
|
||||
"chill_activitybundle_activity_socialActions",
|
||||
);
|
||||
aSocialActions.value = "";
|
||||
payload.forEach((item) => {
|
||||
aSocialActions.value = addIdToValue(aSocialActions.value, item.id);
|
||||
});
|
||||
commit("updateActionsSelected", payload);
|
||||
},
|
||||
addAvailableLocationGroup({ commit }, payload) {
|
||||
commit("addAvailableLocationGroup", payload);
|
||||
},
|
||||
addPersonsInvolved({ commit }, payload) {
|
||||
//console.log('### action addPersonsInvolved', payload.result.type);
|
||||
switch (payload.result.type) {
|
||||
case "person":
|
||||
let aPersons = document.getElementById(
|
||||
"chill_activitybundle_activity_persons",
|
||||
);
|
||||
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
|
||||
break;
|
||||
case "thirdparty":
|
||||
let aThirdParties = document.getElementById(
|
||||
"chill_activitybundle_activity_thirdParties",
|
||||
);
|
||||
aThirdParties.value = addIdToValue(
|
||||
aThirdParties.value,
|
||||
payload.result.id,
|
||||
);
|
||||
break;
|
||||
case "user":
|
||||
let aUsers = document.getElementById(
|
||||
"chill_activitybundle_activity_users",
|
||||
);
|
||||
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
|
||||
break;
|
||||
}
|
||||
commit("addPersonsInvolved", payload);
|
||||
},
|
||||
removePersonInvolved({ commit }, payload) {
|
||||
//console.log('### action removePersonInvolved', payload);
|
||||
switch (payload.type) {
|
||||
case "person":
|
||||
let aPersons = document.getElementById(
|
||||
"chill_activitybundle_activity_persons",
|
||||
);
|
||||
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
|
||||
break;
|
||||
case "thirdparty":
|
||||
let aThirdParties = document.getElementById(
|
||||
"chill_activitybundle_activity_thirdParties",
|
||||
);
|
||||
aThirdParties.value = removeIdFromValue(
|
||||
aThirdParties.value,
|
||||
payload.id,
|
||||
);
|
||||
break;
|
||||
case "user":
|
||||
let aUsers = document.getElementById(
|
||||
"chill_activitybundle_activity_users",
|
||||
);
|
||||
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
|
||||
break;
|
||||
}
|
||||
commit("removePersonInvolved", payload);
|
||||
},
|
||||
updateLocation({ commit }, value) {
|
||||
console.log("### action: updateLocation", value);
|
||||
let hiddenLocation = document.getElementById(
|
||||
"chill_activitybundle_activity_location",
|
||||
);
|
||||
if (value.onthefly) {
|
||||
const body = {
|
||||
type: "location",
|
||||
name:
|
||||
value.name === "__AccompanyingCourseLocation__" ? null : value.name,
|
||||
locationType: {
|
||||
id: value.locationType.id,
|
||||
type: "location-type",
|
||||
},
|
||||
};
|
||||
if (value.address.id) {
|
||||
Object.assign(body, {
|
||||
address: {
|
||||
id: value.address.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
postLocation(body)
|
||||
.then((location) => (hiddenLocation.value = location.id))
|
||||
.catch((err) => {
|
||||
console.log(err.message);
|
||||
});
|
||||
} else {
|
||||
hiddenLocation.value = value.id;
|
||||
}
|
||||
commit("updateLocation", value);
|
||||
},
|
||||
actions: {
|
||||
addIssueSelected({ commit }, issue) {
|
||||
let aSocialIssues = document.getElementById(
|
||||
"chill_activitybundle_activity_socialIssues"
|
||||
);
|
||||
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
|
||||
commit("addIssueSelected", issue);
|
||||
},
|
||||
updateIssuesSelected({ commit }, payload) {
|
||||
let aSocialIssues = document.getElementById(
|
||||
"chill_activitybundle_activity_socialIssues"
|
||||
);
|
||||
aSocialIssues.value = "";
|
||||
payload.forEach((item) => {
|
||||
aSocialIssues.value = addIdToValue(
|
||||
aSocialIssues.value,
|
||||
item.id
|
||||
);
|
||||
});
|
||||
commit("updateIssuesSelected", payload);
|
||||
},
|
||||
updateActionsSelected({ commit }, payload) {
|
||||
let aSocialActions = document.getElementById(
|
||||
"chill_activitybundle_activity_socialActions"
|
||||
);
|
||||
aSocialActions.value = "";
|
||||
payload.forEach((item) => {
|
||||
aSocialActions.value = addIdToValue(
|
||||
aSocialActions.value,
|
||||
item.id
|
||||
);
|
||||
});
|
||||
commit("updateActionsSelected", payload);
|
||||
},
|
||||
addAvailableLocationGroup({ commit }, payload) {
|
||||
commit("addAvailableLocationGroup", payload);
|
||||
},
|
||||
addPersonsInvolved({ commit }, payload) {
|
||||
//console.log('### action addPersonsInvolved', payload.result.type);
|
||||
switch (payload.result.type) {
|
||||
case "person":
|
||||
let aPersons = document.getElementById(
|
||||
"chill_activitybundle_activity_persons"
|
||||
);
|
||||
aPersons.value = addIdToValue(
|
||||
aPersons.value,
|
||||
payload.result.id
|
||||
);
|
||||
break;
|
||||
case "thirdparty":
|
||||
let aThirdParties = document.getElementById(
|
||||
"chill_activitybundle_activity_thirdParties"
|
||||
);
|
||||
aThirdParties.value = addIdToValue(
|
||||
aThirdParties.value,
|
||||
payload.result.id
|
||||
);
|
||||
break;
|
||||
case "user":
|
||||
let aUsers = document.getElementById(
|
||||
"chill_activitybundle_activity_users"
|
||||
);
|
||||
aUsers.value = addIdToValue(
|
||||
aUsers.value,
|
||||
payload.result.id
|
||||
);
|
||||
break;
|
||||
}
|
||||
commit("addPersonsInvolved", payload);
|
||||
},
|
||||
removePersonInvolved({ commit }, payload) {
|
||||
//console.log('### action removePersonInvolved', payload);
|
||||
switch (payload.type) {
|
||||
case "person":
|
||||
let aPersons = document.getElementById(
|
||||
"chill_activitybundle_activity_persons"
|
||||
);
|
||||
aPersons.value = removeIdFromValue(
|
||||
aPersons.value,
|
||||
payload.id
|
||||
);
|
||||
break;
|
||||
case "thirdparty":
|
||||
let aThirdParties = document.getElementById(
|
||||
"chill_activitybundle_activity_thirdParties"
|
||||
);
|
||||
aThirdParties.value = removeIdFromValue(
|
||||
aThirdParties.value,
|
||||
payload.id
|
||||
);
|
||||
break;
|
||||
case "user":
|
||||
let aUsers = document.getElementById(
|
||||
"chill_activitybundle_activity_users"
|
||||
);
|
||||
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
|
||||
break;
|
||||
}
|
||||
commit("removePersonInvolved", payload);
|
||||
},
|
||||
updateLocation({ commit }, value) {
|
||||
console.log("### action: updateLocation", value);
|
||||
let hiddenLocation = document.getElementById(
|
||||
"chill_activitybundle_activity_location"
|
||||
);
|
||||
if (value.onthefly) {
|
||||
const body = {
|
||||
"type": "location",
|
||||
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name,
|
||||
"locationType": {
|
||||
"id": value.locationType.id,
|
||||
"type": "location-type"
|
||||
}
|
||||
};
|
||||
if (value.address.id) {
|
||||
Object.assign(body, {
|
||||
"address": {
|
||||
"id": value.address.id
|
||||
},
|
||||
})
|
||||
}
|
||||
postLocation(body)
|
||||
.then(
|
||||
location => hiddenLocation.value = location.id
|
||||
).catch(
|
||||
err => {
|
||||
console.log(err.message);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
hiddenLocation.value = value.id;
|
||||
}
|
||||
commit("updateLocation", value);
|
||||
},
|
||||
async fetchAccompanyingPeriodWorks({ state, commit }) {
|
||||
const accompanyingPeriodId = state.activity.accompanyingPeriod.id;
|
||||
const url = `/api/1.0/person/accompanying-course/${accompanyingPeriodId}/works.json`;
|
||||
try {
|
||||
const works = await makeFetch("GET", url);
|
||||
// console.log("works", works);
|
||||
commit("setAccompanyingPeriodWorks", works);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch accompanying period works:", error);
|
||||
}
|
||||
},
|
||||
getWhoAmI({ commit }) {
|
||||
const url = `/api/1.0/main/whoami.json`;
|
||||
makeFetch("GET", url).then((user) => {
|
||||
commit("setWhoAmI", user);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
store.dispatch("getWhoAmI");
|
||||
|
||||
prepareLocations(store);
|
||||
store.dispatch("fetchAccompanyingPeriodWorks");
|
||||
|
||||
export default store;
|
||||
|
@@ -1,124 +1,132 @@
|
||||
import {getLocations, getLocationTypeByDefaultFor, getUserCurrentLocation} from "./api";
|
||||
import {
|
||||
getLocations,
|
||||
getLocationTypeByDefaultFor,
|
||||
getUserCurrentLocation,
|
||||
} from "./api";
|
||||
|
||||
const makeConcernedPersonsLocation = (locationType, store) => {
|
||||
let locations = [];
|
||||
store.getters.suggestedEntities.forEach(
|
||||
(e) => {
|
||||
if (e.type === 'person' && e.current_household_address !== null){
|
||||
locations.push({
|
||||
type: 'location',
|
||||
id: -store.getters.suggestedEntities.indexOf(e)*10,
|
||||
onthefly: true,
|
||||
name: e.text,
|
||||
address: {
|
||||
id: e.current_household_address.address_id,
|
||||
},
|
||||
locationType: locationType
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
return locations;
|
||||
let locations = [];
|
||||
store.getters.suggestedEntities.forEach((e) => {
|
||||
if (e.type === "person" && e.current_household_address !== null) {
|
||||
locations.push({
|
||||
type: "location",
|
||||
id: -store.getters.suggestedEntities.indexOf(e) * 10,
|
||||
onthefly: true,
|
||||
name: e.text,
|
||||
address: {
|
||||
id: e.current_household_address.address_id,
|
||||
},
|
||||
locationType: locationType,
|
||||
});
|
||||
}
|
||||
});
|
||||
return locations;
|
||||
};
|
||||
const makeConcernedThirdPartiesLocation = (locationType, store) => {
|
||||
let locations = [];
|
||||
store.getters.suggestedEntities.forEach(
|
||||
(e) => {
|
||||
if (e.type === 'thirdparty' && e.address !== null){
|
||||
locations.push({
|
||||
type: 'location',
|
||||
id: -store.getters.suggestedEntities.indexOf(e)*10,
|
||||
onthefly: true,
|
||||
name: e.text,
|
||||
address: { id: e.address.address_id },
|
||||
locationType: locationType
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
return locations;
|
||||
let locations = [];
|
||||
store.getters.suggestedEntities.forEach((e) => {
|
||||
if (e.type === "thirdparty" && e.address !== null) {
|
||||
locations.push({
|
||||
type: "location",
|
||||
id: -store.getters.suggestedEntities.indexOf(e) * 10,
|
||||
onthefly: true,
|
||||
name: e.text,
|
||||
address: { id: e.address.address_id },
|
||||
locationType: locationType,
|
||||
});
|
||||
}
|
||||
});
|
||||
return locations;
|
||||
};
|
||||
const makeAccompanyingPeriodLocation = (locationType, store) => {
|
||||
if (store.state.activity.accompanyingPeriod === null) {
|
||||
return {};
|
||||
}
|
||||
const accPeriodLocation = store.state.activity.accompanyingPeriod.location;
|
||||
return {
|
||||
type: 'location',
|
||||
id: -1,
|
||||
onthefly: true,
|
||||
name: '__AccompanyingCourseLocation__',
|
||||
address: {
|
||||
id: accPeriodLocation.address_id,
|
||||
text: `${accPeriodLocation.text} - ${accPeriodLocation.postcode.code} ${accPeriodLocation.postcode.name}`
|
||||
},
|
||||
locationType: locationType
|
||||
}
|
||||
if (store.state.activity.accompanyingPeriod === null) {
|
||||
return {};
|
||||
}
|
||||
const accPeriodLocation = store.state.activity.accompanyingPeriod.location;
|
||||
return {
|
||||
type: "location",
|
||||
id: -1,
|
||||
onthefly: true,
|
||||
name: "__AccompanyingCourseLocation__",
|
||||
address: {
|
||||
id: accPeriodLocation.address_id,
|
||||
text: `${accPeriodLocation.text} - ${accPeriodLocation.postcode.code} ${accPeriodLocation.postcode.name}`,
|
||||
},
|
||||
locationType: locationType,
|
||||
};
|
||||
};
|
||||
|
||||
export default function prepareLocations(store) {
|
||||
|
||||
// find the locations
|
||||
let allLocations = getLocations().then(
|
||||
(results) => {
|
||||
store.commit('addAvailableLocationGroup', {
|
||||
locationGroup: 'Autres localisations',
|
||||
locations: results
|
||||
let allLocations = getLocations().then((results) => {
|
||||
store.commit("addAvailableLocationGroup", {
|
||||
locationGroup: "Autres localisations",
|
||||
locations: results,
|
||||
});
|
||||
});
|
||||
|
||||
let currentLocation = getUserCurrentLocation().then((userCurrentLocation) => {
|
||||
if (null !== userCurrentLocation) {
|
||||
store.commit("addAvailableLocationGroup", {
|
||||
locationGroup: "Ma localisation",
|
||||
locations: [userCurrentLocation],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let partiesLocations = [],
|
||||
partyPromise;
|
||||
["person", "thirdparty"].forEach((kind) => {
|
||||
partyPromise = getLocationTypeByDefaultFor(kind).then(
|
||||
(kindLocationType) => {
|
||||
if (kindLocationType) {
|
||||
let concernedKindLocations;
|
||||
if (kind === "person") {
|
||||
concernedKindLocations = makeConcernedPersonsLocation(
|
||||
kindLocationType,
|
||||
store,
|
||||
);
|
||||
// add location for the parcours into suggestions
|
||||
const personLocation = makeAccompanyingPeriodLocation(
|
||||
kindLocationType,
|
||||
store,
|
||||
);
|
||||
store.commit("addAvailableLocationGroup", {
|
||||
locationGroup: "Localisation du parcours",
|
||||
locations: [personLocation],
|
||||
});
|
||||
} else {
|
||||
concernedKindLocations = makeConcernedThirdPartiesLocation(
|
||||
kindLocationType,
|
||||
store,
|
||||
);
|
||||
}
|
||||
|
||||
store.commit("addAvailableLocationGroup", {
|
||||
locationGroup:
|
||||
kind === "person" ? "Usagers concernés" : "Tiers concernés",
|
||||
locations: concernedKindLocations,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
partiesLocations.push(partyPromise);
|
||||
});
|
||||
|
||||
let currentLocation = getUserCurrentLocation().then(
|
||||
userCurrentLocation => {
|
||||
if (null !== userCurrentLocation) {
|
||||
store.commit('addAvailableLocationGroup', {
|
||||
locationGroup: 'Ma localisation',
|
||||
locations: [userCurrentLocation]
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let partiesLocations = [], partyPromise;
|
||||
['person', 'thirdparty'].forEach(kind => {
|
||||
partyPromise = getLocationTypeByDefaultFor(kind).then(
|
||||
(kindLocationType) => {
|
||||
if (kindLocationType) {
|
||||
let concernedKindLocations;
|
||||
if (kind === 'person') {
|
||||
concernedKindLocations = makeConcernedPersonsLocation(kindLocationType, store);
|
||||
// add location for the parcours into suggestions
|
||||
const personLocation = makeAccompanyingPeriodLocation(kindLocationType, store);
|
||||
store.commit('addAvailableLocationGroup', {
|
||||
locationGroup: 'Localisation du parcours',
|
||||
locations: [personLocation]
|
||||
});
|
||||
} else {
|
||||
concernedKindLocations = makeConcernedThirdPartiesLocation(kindLocationType, store);
|
||||
}
|
||||
|
||||
store.commit('addAvailableLocationGroup', {
|
||||
locationGroup: kind === 'person' ? 'Usagers concernés' : 'Tiers concernés',
|
||||
locations: concernedKindLocations,
|
||||
});
|
||||
}
|
||||
}
|
||||
// when all location are loaded
|
||||
Promise.all([allLocations, currentLocation, ...partiesLocations]).then(() => {
|
||||
console.log("current location in activity", store.state.activity.location);
|
||||
console.log("default loation id", window.default_location_id);
|
||||
if (window.default_location_id) {
|
||||
for (let group of store.state.availableLocations) {
|
||||
let location = group.locations.find(
|
||||
(l) => l.id === window.default_location_id,
|
||||
);
|
||||
partiesLocations.push(partyPromise);
|
||||
});
|
||||
|
||||
// when all location are loaded
|
||||
Promise.all([allLocations, currentLocation, ...partiesLocations]).then(() => {
|
||||
console.log('current location in activity', store.state.activity.location);
|
||||
console.log('default loation id', window.default_location_id);
|
||||
if (window.default_location_id) {
|
||||
for (let group of store.state.availableLocations) {
|
||||
let location = group.locations.find((l) => l.id === window.default_location_id);
|
||||
if (location !== undefined && store.state.activity.location === null) {
|
||||
store.dispatch('updateLocation', location);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (location !== undefined && store.state.activity.location === null) {
|
||||
store.dispatch("updateLocation", location);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -1,83 +1,3 @@
|
||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||
|
||||
{% set person_id = null %}
|
||||
{% if activity.person %}
|
||||
{% set person_id = activity.person.id %}
|
||||
{% endif %}
|
||||
|
||||
{% set accompanying_course_id = null %}
|
||||
{% if activity.accompanyingPeriod %}
|
||||
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
||||
{% endif %}
|
||||
|
||||
<div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
|
||||
<div class="item-row">
|
||||
<div class="item-col" style="width: unset">
|
||||
{% if document.isPending %}
|
||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||
{% elseif document.isFailure %}
|
||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{% if activity.accompanyingPeriod is not null and context == 'person' %}
|
||||
<span class="badge bg-primary">
|
||||
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="badge-activity-type">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ activity.type.name | localize_translatable_string }}
|
||||
{% if activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="denomination h2">
|
||||
{{ document.title|chill_print_or_message("No title") }}
|
||||
</div>
|
||||
{% if document.hasTemplate %}
|
||||
<div>
|
||||
<p>{{ document.template.name|localize_translatable_string }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<div class="container">
|
||||
<div class="dates row text-end">
|
||||
<span>{{ document.createdAt|format_date('short') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="item-row separator">
|
||||
<div class="item-col item-meta">
|
||||
{{ mmm.createdBy(document) }}
|
||||
</div>
|
||||
<ul class="item-col record_actions flex-shrink-1">
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||
<li>
|
||||
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{{ include('@ChillActivity/GenericDoc/activity_document_row.html.twig') }}
|
||||
</div>
|
||||
|
@@ -0,0 +1,81 @@
|
||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||
|
||||
{% set person_id = null %}
|
||||
{% if activity.person %}
|
||||
{% set person_id = activity.person.id %}
|
||||
{% endif %}
|
||||
|
||||
{% set accompanying_course_id = null %}
|
||||
{% if activity.accompanyingPeriod %}
|
||||
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row">
|
||||
<div class="item-col" style="width: unset">
|
||||
{% if document.isPending %}
|
||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||
{% elseif document.isFailure %}
|
||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{% if activity.accompanyingPeriod is not null and context == 'person' %}
|
||||
<span class="badge bg-primary">
|
||||
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="badge-activity-type">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ activity.type.name | localize_translatable_string }}
|
||||
{% if activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="denomination h2">
|
||||
{{ document.title|chill_print_or_message("No title") }}
|
||||
</div>
|
||||
{% if document.hasTemplate %}
|
||||
<div>
|
||||
<p>{{ document.template.name|localize_translatable_string }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<div class="container">
|
||||
<div class="dates row text-end">
|
||||
<span>{{ document.createdAt|format_date('short') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if show_actions %}
|
||||
<div class="item-row separator">
|
||||
<div class="item-col item-meta">
|
||||
{{ mmm.createdBy(document) }}
|
||||
</div>
|
||||
<ul class="item-col record_actions flex-shrink-1">
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||
<li>
|
||||
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
@@ -143,7 +143,10 @@ class ListActivitiesByAccompanyingPeriodContext implements
|
||||
array_filter(
|
||||
$works,
|
||||
function ($work) use ($user) {
|
||||
$workUsernames = array_map(static fn (User $user) => $user['username'], $work['referrers'] ?? []);
|
||||
$workUsernames = [];
|
||||
foreach ($work['referrers'] as $referrer) {
|
||||
$workUsernames[] = $referrer['username'];
|
||||
}
|
||||
|
||||
return \in_array($user->getUserIdentifier(), $workUsernames, true);
|
||||
}
|
||||
|
@@ -0,0 +1,54 @@
|
||||
<?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\ActivityBundle\Service\GenericDoc\Normalizer;
|
||||
|
||||
use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider;
|
||||
use Chill\ActivityBundle\Service\GenericDoc\Renderers\AccompanyingPeriodActivityGenericDocRenderer;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
final readonly class AccompanyingPeriodActivityGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private StoredObjectRepositoryInterface $storedObjectRepository,
|
||||
private AccompanyingPeriodActivityGenericDocRenderer $renderer,
|
||||
private Environment $twig,
|
||||
private TranslatorInterface $translator,
|
||||
) {}
|
||||
|
||||
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||
{
|
||||
return AccompanyingPeriodActivityGenericDocProvider::KEY === $genericDocDTO->key
|
||||
&& 'json' == $format;
|
||||
}
|
||||
|
||||
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||
{
|
||||
$storedObject = $this->storedObjectRepository->find($genericDocDTO->identifiers['id']);
|
||||
|
||||
if (null === $storedObject) {
|
||||
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||
}
|
||||
|
||||
return [
|
||||
'isPresent' => true,
|
||||
'title' => $storedObject->getTitle(),
|
||||
'html' => $this->twig->render(
|
||||
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
@@ -13,10 +13,12 @@ namespace Chill\ActivityBundle\Service\GenericDoc\Providers;
|
||||
|
||||
use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
|
||||
use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
@@ -34,8 +36,47 @@ final readonly class AccompanyingPeriodActivityGenericDocProvider implements Gen
|
||||
private EntityManagerInterface $em,
|
||||
private Security $security,
|
||||
private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
|
||||
private ActivityRepository $activityRepository,
|
||||
) {}
|
||||
|
||||
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||
{
|
||||
if (null === $activity = $this->getRelatedEntity($genericDocDTO->key, $genericDocDTO->identifiers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $activity->getDocuments()->findFirst(fn (int $key, StoredObject $storedObject) => $storedObject->getId() === $genericDocDTO->identifiers['id']);
|
||||
}
|
||||
|
||||
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||
{
|
||||
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||
}
|
||||
|
||||
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||
{
|
||||
return self::KEY === $key && array_key_exists('activity_id', $identifiers);
|
||||
}
|
||||
|
||||
private function getRelatedEntity(string $key, array $identifiers): ?Activity
|
||||
{
|
||||
return $this->activityRepository->find($identifiers['activity_id']);
|
||||
}
|
||||
|
||||
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||
{
|
||||
if (null === $activity = $this->getRelatedEntity($key, $identifiers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GenericDocDTO(
|
||||
self::KEY,
|
||||
$identifiers,
|
||||
\DateTimeImmutable::createFromInterface($activity->getDate()),
|
||||
$activity->getAccompanyingPeriod(),
|
||||
);
|
||||
}
|
||||
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
|
@@ -18,6 +18,9 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||
|
||||
/**
|
||||
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||
*/
|
||||
final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
|
||||
{
|
||||
public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository) {}
|
||||
@@ -29,7 +32,8 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
|
||||
|
||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||
{
|
||||
return '@ChillActivity/GenericDoc/activity_document.html.twig';
|
||||
return ($options['row-only'] ?? false) ? '@ChillActivity/GenericDoc/activity_document_row.html.twig' :
|
||||
'@ChillActivity/GenericDoc/activity_document.html.twig';
|
||||
}
|
||||
|
||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||
@@ -38,6 +42,7 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
|
||||
'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
|
||||
'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
|
||||
'context' => $genericDocDTO->getContext(),
|
||||
'show_actions' => $options['show-actions'] ?? true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,18 @@
|
||||
// this file loads all assets from the Chill person bundle
|
||||
module.exports = function(encore, entries)
|
||||
{
|
||||
entries.push(__dirname + '/Resources/public/chill/index.js');
|
||||
module.exports = function (encore, entries) {
|
||||
entries.push(__dirname + "/Resources/public/chill/index.js");
|
||||
|
||||
encore.addAliases({
|
||||
ChillActivityAssets: __dirname + '/Resources/public'
|
||||
});
|
||||
encore.addAliases({
|
||||
ChillActivityAssets: __dirname + "/Resources/public",
|
||||
});
|
||||
|
||||
encore.addEntry('page_edit_activity', __dirname + '/Resources/public/page/edit_activity/index.scss');
|
||||
encore.addEntry(
|
||||
"page_edit_activity",
|
||||
__dirname + "/Resources/public/page/edit_activity/index.scss",
|
||||
);
|
||||
|
||||
encore.addEntry('vue_activity', __dirname + '/Resources/public/vuejs/Activity/index.js');
|
||||
encore.addEntry(
|
||||
"vue_activity",
|
||||
__dirname + "/Resources/public/vuejs/Activity/index.js",
|
||||
);
|
||||
};
|
||||
|
@@ -14,3 +14,5 @@ export:
|
||||
describe_action_with_subject: >-
|
||||
Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}, et un de ces sujets choisis: {reasons}
|
||||
|
||||
activity:
|
||||
title: Échange du {date, date, long} - {type}
|
||||
|
@@ -101,6 +101,33 @@ activity:
|
||||
Insert a document: Insérer un document
|
||||
Remove a document: Supprimer le document
|
||||
comment: Commentaire
|
||||
deleted: Échange supprimé
|
||||
|
||||
errors: Le formulaire contient des erreurs
|
||||
social_issues: Problématiques sociales
|
||||
choose_other_social_issue: Ajouter une autre problématique sociale...
|
||||
social_actions: Actions d'accompagnement
|
||||
select_first_a_social_issue: Sélectionnez d'abord une problématique sociale
|
||||
social_action_list_empty: Aucune action sociale disponible
|
||||
add_persons: Ajouter des personnes concernées
|
||||
bloc_persons: Usagers
|
||||
bloc_persons_associated: Usagers du parcours
|
||||
bloc_persons_not_associated: Tiers non-pro.
|
||||
bloc_thirdparty: Tiers professionnels
|
||||
bloc_users: T(M)S
|
||||
location: Localisation
|
||||
choose_location: Choisissez une localisation
|
||||
choose_location_type: Choisissez un type de localisation
|
||||
create_new_location: Créer une nouvelle localisation
|
||||
location_fields:
|
||||
name: Nom
|
||||
type: Type
|
||||
phonenumber1: Téléphone
|
||||
phonenumber2: Autre téléphone
|
||||
email: Adresse courriel
|
||||
create_address: Créer une adresse
|
||||
edit_address: Modifier l'adresse
|
||||
|
||||
No documents: Aucun document
|
||||
|
||||
# activity filter in list page
|
||||
|
@@ -1 +1 @@
|
||||
require('./chillbudget.scss');
|
||||
require("./chillbudget.scss");
|
||||
|
@@ -1,9 +1,8 @@
|
||||
// this file loads all assets from the Chill budget bundle
|
||||
module.exports = function(encore, entries)
|
||||
{
|
||||
encore.addAliases({
|
||||
ChillBudgetAssets: __dirname + '/Resources/public'
|
||||
});
|
||||
module.exports = function (encore, entries) {
|
||||
encore.addAliases({
|
||||
ChillBudgetAssets: __dirname + "/Resources/public",
|
||||
});
|
||||
|
||||
encore.addEntry('page_budget', __dirname + '/Resources/public/page/index.js');
|
||||
encore.addEntry("page_budget", __dirname + "/Resources/public/page/index.js");
|
||||
};
|
||||
|
@@ -21,9 +21,7 @@ namespace Chill\CalendarBundle\Command;
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Service\ShortMessageNotification\ShortMessageForCalendarBuilderInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Chill\MainBundle\Service\ShortMessage\ShortMessageTransporterInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use libphonenumber\PhoneNumber;
|
||||
@@ -36,6 +34,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Notifier\TexterInterface;
|
||||
|
||||
class SendTestShortMessageOnCalendarCommand extends Command
|
||||
{
|
||||
@@ -44,9 +43,8 @@ class SendTestShortMessageOnCalendarCommand extends Command
|
||||
public function __construct(
|
||||
private readonly PersonRepository $personRepository,
|
||||
private readonly PhoneNumberUtil $phoneNumberUtil,
|
||||
private readonly PhoneNumberHelperInterface $phoneNumberHelper,
|
||||
private readonly ShortMessageForCalendarBuilderInterface $messageForCalendarBuilder,
|
||||
private readonly ShortMessageTransporterInterface $transporter,
|
||||
private readonly TexterInterface $transporter,
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
) {
|
||||
parent::__construct('chill:calendar:test-send-short-message');
|
||||
@@ -152,10 +150,6 @@ class SendTestShortMessageOnCalendarCommand extends Command
|
||||
return $phone;
|
||||
});
|
||||
|
||||
$phone = $helper->ask($input, $output, $question);
|
||||
|
||||
$question = new ConfirmationQuestion('really send the message to the phone ?');
|
||||
$reallySend = (bool) $helper->ask($input, $output, $question);
|
||||
|
||||
$messages = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar);
|
||||
|
||||
@@ -165,8 +159,12 @@ class SendTestShortMessageOnCalendarCommand extends Command
|
||||
|
||||
foreach ($messages as $key => $message) {
|
||||
$output->writeln("The short message for SMS {$key} will be: ");
|
||||
$output->writeln($message->getContent());
|
||||
$message->setPhoneNumber($phone);
|
||||
$output->writeln($message->getSubject());
|
||||
$output->writeln('The destination number will be:');
|
||||
$output->writeln($message->getPhone());
|
||||
|
||||
$question = new ConfirmationQuestion('really send the message to the phone ?');
|
||||
$reallySend = (bool) $helper->ask($input, $output, $question);
|
||||
|
||||
if ($reallySend) {
|
||||
$this->transporter->send($message);
|
||||
|
@@ -15,6 +15,7 @@ use Chill\CalendarBundle\Repository\CalendarRepository;
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class CalendarAPIController extends ApiController
|
||||
{
|
||||
public function __construct(private readonly CalendarRepository $calendarRepository) {}
|
||||
public function __construct(private readonly CalendarRepository $calendarRepository, ManagerRegistry $managerRegistry)
|
||||
{
|
||||
parent::__construct($managerRegistry);
|
||||
}
|
||||
|
||||
#[Route(path: '/api/1.0/calendar/calendar/by-user/{id}.{_format}', name: 'chill_api_single_calendar_list_by-user', requirements: ['_format' => 'json'])]
|
||||
public function listByUser(User $user, Request $request, string $_format): JsonResponse
|
||||
|
@@ -15,6 +15,7 @@ use Chill\CalendarBundle\Repository\CalendarRangeRepository;
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class CalendarRangeAPIController extends ApiController
|
||||
{
|
||||
public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository) {}
|
||||
public function __construct(private readonly CalendarRangeRepository $calendarRangeRepository, ManagerRegistry $managerRegistry)
|
||||
{
|
||||
parent::__construct($managerRegistry);
|
||||
}
|
||||
|
||||
#[Route(path: '/api/1.0/calendar/calendar-range-available/{id}.{_format}', name: 'chill_api_single_calendar_range_available', requirements: ['_format' => 'json'])]
|
||||
public function availableRanges(User $user, Request $request, string $_format): JsonResponse
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\CalendarBundle\Repository;
|
||||
|
||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
@@ -49,4 +50,21 @@ class CalendarDocRepository implements ObjectRepository, CalendarDocRepositoryIn
|
||||
{
|
||||
return CalendarDoc::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StoredObject|int $storedObject the StoredObject instance, or the id of the stored object
|
||||
*/
|
||||
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc
|
||||
{
|
||||
$storedObjectId = $storedObject instanceof StoredObject ? $storedObject->getId() : $storedObject;
|
||||
|
||||
$qb = $this->repository->createQueryBuilder('c');
|
||||
$qb->where(
|
||||
$qb->expr()->eq(':storedObject', 'c.storedObject')
|
||||
);
|
||||
|
||||
$qb->setParameter('storedObject', $storedObjectId);
|
||||
|
||||
return $qb->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\CalendarBundle\Repository;
|
||||
|
||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
|
||||
interface CalendarDocRepositoryInterface
|
||||
{
|
||||
@@ -29,5 +30,7 @@ interface CalendarDocRepositoryInterface
|
||||
|
||||
public function findOneBy(array $criteria): ?CalendarDoc;
|
||||
|
||||
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc;
|
||||
|
||||
public function getClassName();
|
||||
}
|
||||
|
@@ -1,2 +1,2 @@
|
||||
import './scss/badge.scss';
|
||||
import './scss/calendar-list.scss';
|
||||
import "./scss/badge.scss";
|
||||
import "./scss/calendar-list.scss";
|
||||
|
@@ -1 +1 @@
|
||||
require('./scss/calendar.scss');
|
||||
require("./scss/calendar.scss");
|
||||
|
@@ -1,14 +1,13 @@
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import Answer from 'ChillCalendarAssets/vuejs/Invite/Answer';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
||||
import { createApp } from "vue";
|
||||
import Answer from "ChillCalendarAssets/vuejs/Invite/Answer";
|
||||
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||
|
||||
const i18n = _createI18n({});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function (e) {
|
||||
console.log('dom loaded answer');
|
||||
document.querySelectorAll('div[invite-answer]').forEach(function (el) {
|
||||
console.log('element found', el);
|
||||
document.addEventListener("DOMContentLoaded", function (e) {
|
||||
console.log("dom loaded answer");
|
||||
document.querySelectorAll("div[invite-answer]").forEach(function (el) {
|
||||
console.log("element found", el);
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
@@ -18,14 +17,15 @@ document.addEventListener('DOMContentLoaded', function (e) {
|
||||
return {
|
||||
status: el.dataset.status,
|
||||
calendarId: Number.parseInt(el.dataset.calendarId),
|
||||
}
|
||||
};
|
||||
},
|
||||
template: '<answer :calendarId="calendarId" :status="status" @statusChanged="onStatusChanged"></answer>',
|
||||
template:
|
||||
'<answer :calendarId="calendarId" :status="status" @statusChanged="onStatusChanged"></answer>',
|
||||
methods: {
|
||||
onStatusChanged: function(newStatus) {
|
||||
onStatusChanged: function (newStatus) {
|
||||
this.$data.status = newStatus;
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
app.use(i18n).mount(el);
|
||||
|
@@ -1,67 +1,76 @@
|
||||
import {EventInput} from '@fullcalendar/core';
|
||||
import {DateTime, Location, User, UserAssociatedInterface} from '../../../ChillMainBundle/Resources/public/types' ;
|
||||
import {Person} from "../../../ChillPersonBundle/Resources/public/types";
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import {
|
||||
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 is EventInputCalendarRange {
|
||||
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
|
||||
export function isEventInputCalendarRange(
|
||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||
): toBeDetermined is EventInputCalendarRange {
|
||||
return (
|
||||
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
|
||||
);
|
||||
}
|
||||
|
||||
export {};
|
||||
|
@@ -1,302 +1,393 @@
|
||||
<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" ></calendar-active>
|
||||
<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>
|
||||
</div>
|
||||
<pick-entity
|
||||
:multiple="false"
|
||||
:types="['user']"
|
||||
:uniqid="'main_user_calendar'"
|
||||
:picked="null !== this.$store.getters.getMainUser ? [this.$store.getters.getMainUser] : []"
|
||||
:removableIfSet="false"
|
||||
:displayPicked="false"
|
||||
:suggested="this.suggestedUsers"
|
||||
:label="'main_user'"
|
||||
@addNewEntity="setMainUser"
|
||||
></pick-entity>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</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></location>
|
||||
<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 class="" v-for="u in getActiveUsers" :key="u.id">
|
||||
<calendar-active :user="u" :invite="this.$store.getters.getInviteForUser(u)"></calendar-active>
|
||||
</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>
|
||||
</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 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="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
|
||||
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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||
<template v-slot: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>
|
||||
<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>
|
||||
import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue';
|
||||
import Location from 'ChillActivityAssets/vuejs/Activity/components/Location.vue';
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
import listPlugin from '@fullcalendar/list';
|
||||
import CalendarActive from './Components/CalendarActive';
|
||||
import PickEntity from 'ChillMainAssets/vuejs/PickEntity/PickEntity.vue';
|
||||
import {mapGetters, mapState} from "vuex";
|
||||
import ConcernedGroups from "ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue";
|
||||
import Location from "ChillActivityAssets/vuejs/Activity/components/Location.vue";
|
||||
import frLocale from "@fullcalendar/core/locales/fr";
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import listPlugin from "@fullcalendar/list";
|
||||
import CalendarActive from "./Components/CalendarActive";
|
||||
import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
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;
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
Location,
|
||||
FullCalendar,
|
||||
CalendarActive,
|
||||
PickEntity,
|
||||
},
|
||||
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',
|
||||
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;
|
||||
},
|
||||
views: {
|
||||
timeGrid: {
|
||||
slotEventOverlap: false,
|
||||
slotDuration: this.slotDuration,
|
||||
},
|
||||
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);
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
},
|
||||
suggestedUsers() {
|
||||
const suggested = [];
|
||||
|
||||
this.$data.previousUser.forEach(u => {
|
||||
if (u.id !== this.$store.getters.getMainUser.id) {
|
||||
suggested.push(u)
|
||||
}
|
||||
});
|
||||
|
||||
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,80 +1,119 @@
|
||||
<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>
|
||||
<i v-else-if="invite.status === 'declined'" class="fa fa-times"></i>
|
||||
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o"></i>
|
||||
<i v-else-if="invite.status === 'tentative'" class="fa fa-question"></i>
|
||||
<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"></i></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"></i></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';
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "CalendarActive",
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true
|
||||
name: "CalendarActive",
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
invite: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
invite: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
|
||||
};
|
||||
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,
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
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>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import {fetchResults} from '../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods';
|
||||
import {datetimeToISO} from '../../../../../ChillMainBundle/Resources/public/chill/js/date';
|
||||
import {User} from '../../../../../ChillMainBundle/Resources/public/types';
|
||||
import {CalendarLight, CalendarRange, CalendarRemote} from '../../types';
|
||||
import { fetchResults } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import { datetimeToISO } from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import { User } from "../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { CalendarLight, CalendarRange, CalendarRemote } from "../../types";
|
||||
|
||||
// re-export whoami
|
||||
export {whoami} from "../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||
export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -13,26 +13,38 @@ export {whoami} from "../../../../../ChillMainBundle/Resources/public/lib/api/us
|
||||
* @param Date end
|
||||
* @return Promise
|
||||
*/
|
||||
export const fetchCalendarRangeForUser = (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);
|
||||
export const fetchCalendarRangeForUser = (
|
||||
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);
|
||||
|
||||
return fetchResults<CalendarRange>(uri, {dateFrom, dateTo});
|
||||
}
|
||||
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
export const fetchCalendarRemoteForUser = (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);
|
||||
export const fetchCalendarRemoteForUser = (
|
||||
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);
|
||||
|
||||
return fetchResults<CalendarRemote>(uri, {dateFrom, dateTo});
|
||||
}
|
||||
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
||||
export const fetchCalendarLocalForUser = (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);
|
||||
export const fetchCalendarLocalForUser = (
|
||||
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);
|
||||
|
||||
return fetchResults<CalendarLight>(uri, {dateFrom, dateTo});
|
||||
}
|
||||
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||
};
|
||||
|
@@ -1,19 +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'
|
||||
const COLORS = [
|
||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||
"#8dd3c7",
|
||||
"#ffffb3",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#b3de69",
|
||||
"#fccde5",
|
||||
"#d9d9d9",
|
||||
"#bc80bd",
|
||||
"#ccebc5",
|
||||
"#ffed6f",
|
||||
];
|
||||
|
||||
export {
|
||||
COLORS,
|
||||
};
|
||||
export { COLORS };
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {personMessages} from 'ChillPersonAssets/vuejs/_js/i18n'
|
||||
import {calendarUserSelectorMessages} from '../_components/CalendarUserSelector/js/i18n';
|
||||
import {activityMessages} from 'ChillActivityAssets/vuejs/Activity/i18n';
|
||||
import { personMessages } from "ChillPersonAssets/vuejs/_js/i18n";
|
||||
import { calendarUserSelectorMessages } from "../_components/CalendarUserSelector/js/i18n";
|
||||
import { activityMessages } from "ChillActivityAssets/vuejs/Activity/i18n";
|
||||
|
||||
const appMessages = {
|
||||
fr: {
|
||||
@@ -13,20 +13,22 @@ const appMessages = {
|
||||
bloc_thirdparty: "Tiers professionnels",
|
||||
bloc_users: "T(M)S",
|
||||
},
|
||||
this_calendar_range_will_change_main_user: "Cette plage de disponibilité n'est pas celle de l'utilisateur principal. Si vous continuez, l'utilisateur principal sera adapté. Êtes-vous sûr·e ?",
|
||||
will_change_main_user_for_me: "Vous ne pouvez pas écrire dans le calendrier d'un autre utilisateur. Voulez-vous être l'utilisateur principal de ce rendez-vous ?",
|
||||
main_user_is_mandatory: "L'utilisateur principal est requis. Vous pouvez le modifier, mais pas le supprimer",
|
||||
change_main_user_will_reset_event_data: "Modifier l'utilisateur principal nécessite de choisir une autre plage de disponibilité ou un autre horaire. Ces informations seront perdues. Êtes-vous sûr·e de vouloir continuer ?",
|
||||
list_three_days: 'Liste 3 jours',
|
||||
current_selected: 'Rendez-vous fixé',
|
||||
this_calendar_range_will_change_main_user:
|
||||
"Cette plage de disponibilité n'est pas celle de l'utilisateur principal. Si vous continuez, l'utilisateur principal sera adapté. Êtes-vous sûr·e ?",
|
||||
will_change_main_user_for_me:
|
||||
"Vous ne pouvez pas écrire dans le calendrier d'un autre utilisateur. Voulez-vous être l'utilisateur principal de ce rendez-vous ?",
|
||||
main_user_is_mandatory:
|
||||
"L'utilisateur principal est requis. Vous pouvez le modifier, mais pas le supprimer",
|
||||
change_main_user_will_reset_event_data:
|
||||
"Modifier l'utilisateur principal nécessite de choisir une autre plage de disponibilité ou un autre horaire. Ces informations seront perdues. Êtes-vous sûr·e de vouloir continuer ?",
|
||||
list_three_days: "Liste 3 jours",
|
||||
current_selected: "Rendez-vous fixé",
|
||||
main_user: "Utilisateur principal",
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(appMessages.fr, personMessages.fr);
|
||||
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
|
||||
Object.assign(appMessages.fr, activityMessages.fr);
|
||||
|
||||
export {
|
||||
appMessages
|
||||
};
|
||||
export { appMessages };
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import { createApp } from 'vue';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||
import { appMessages } from './i18n'
|
||||
import store from './store'
|
||||
import { createApp } from "vue";
|
||||
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||
import { appMessages } from "./i18n";
|
||||
import store from "./store";
|
||||
|
||||
import App from './App.vue';
|
||||
import App from "./App.vue";
|
||||
|
||||
const i18n = _createI18n(appMessages);
|
||||
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
template: `<app></app>`,
|
||||
})
|
||||
.use(store)
|
||||
.use(i18n)
|
||||
.component('app', App)
|
||||
.mount('#calendar');
|
||||
.use(store)
|
||||
.use(i18n)
|
||||
.component("app", App)
|
||||
.mount("#calendar");
|
||||
|
@@ -1,14 +1,11 @@
|
||||
import {
|
||||
addIdToValue,
|
||||
removeIdFromValue,
|
||||
} from './utils';
|
||||
import { addIdToValue, removeIdFromValue } from "./utils";
|
||||
import {
|
||||
fetchCalendarRangeForUser,
|
||||
fetchCalendarRemoteForUser,
|
||||
fetchCalendarLocalForUser,
|
||||
} from './../api';
|
||||
import {datetimeToISO} from 'ChillMainAssets/chill/js/date';
|
||||
import {postLocation} from 'ChillActivityAssets/vuejs/Activity/api';
|
||||
} from "./../api";
|
||||
import { datetimeToISO } from "ChillMainAssets/chill/js/date";
|
||||
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
|
||||
|
||||
/**
|
||||
* This will store a unique key for each value, and prevent to launch the same
|
||||
@@ -24,12 +21,12 @@ import {postLocation} from 'ChillActivityAssets/vuejs/Activity/api';
|
||||
const fetchings = new Set();
|
||||
|
||||
export default {
|
||||
setCurrentDatesView({commit, dispatch}, {start, end}) {
|
||||
commit('setCurrentDatesView', {start, end});
|
||||
setCurrentDatesView({ commit, dispatch }, { start, end }) {
|
||||
commit("setCurrentDatesView", { start, end });
|
||||
|
||||
return dispatch('fetchCalendarEvents');
|
||||
return dispatch("fetchCalendarEvents");
|
||||
},
|
||||
fetchCalendarEvents({state, getters, dispatch}) {
|
||||
fetchCalendarEvents({ state, getters, dispatch }) {
|
||||
if (state.currentView.start === null && state.currentView.end === null) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -39,101 +36,124 @@ export default {
|
||||
let unique = `${uid}, ${state.currentView.start.toISOString()}, ${state.currentView.end.toISOString()}`;
|
||||
|
||||
if (fetchings.has(unique)) {
|
||||
console.log('prevent from fetching for a user', unique);
|
||||
console.log("prevent from fetching for a user", unique);
|
||||
continue;
|
||||
}
|
||||
|
||||
fetchings.add(unique);
|
||||
|
||||
promises.push(
|
||||
dispatch(
|
||||
'fetchCalendarRangeForUser',
|
||||
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end}
|
||||
)
|
||||
dispatch("fetchCalendarRangeForUser", {
|
||||
user: state.usersData.get(uid).user,
|
||||
start: state.currentView.start,
|
||||
end: state.currentView.end,
|
||||
}),
|
||||
);
|
||||
promises.push(
|
||||
dispatch(
|
||||
'fetchCalendarRemotesForUser',
|
||||
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end}
|
||||
)
|
||||
dispatch("fetchCalendarRemotesForUser", {
|
||||
user: state.usersData.get(uid).user,
|
||||
start: state.currentView.start,
|
||||
end: state.currentView.end,
|
||||
}),
|
||||
);
|
||||
promises.push(
|
||||
dispatch(
|
||||
'fetchCalendarLocalsForUser',
|
||||
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end}
|
||||
)
|
||||
dispatch("fetchCalendarLocalsForUser", {
|
||||
user: state.usersData.get(uid).user,
|
||||
start: state.currentView.start,
|
||||
end: state.currentView.end,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
},
|
||||
fetchCalendarRangeForUser({commit, getters}, {user, start, end}) {
|
||||
if (!getters.isCalendarRangeLoadedForUser({user, start, end})) {
|
||||
fetchCalendarRangeForUser({ commit, getters }, { user, start, end }) {
|
||||
if (!getters.isCalendarRangeLoadedForUser({ user, start, end })) {
|
||||
return fetchCalendarRangeForUser(user, start, end).then((ranges) => {
|
||||
commit('addCalendarRangesForUser', {user, ranges, start, end});
|
||||
commit("addCalendarRangesForUser", { user, ranges, start, end });
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
},
|
||||
fetchCalendarRemotesForUser({commit, getters}, {user, start, end}) {
|
||||
if (!getters.isCalendarRemoteLoadedForUser({user, start, end})) {
|
||||
fetchCalendarRemotesForUser({ commit, getters }, { user, start, end }) {
|
||||
if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) {
|
||||
return fetchCalendarRemoteForUser(user, start, end).then((remotes) => {
|
||||
commit('addCalendarRemotesForUser', {user, remotes, start, end});
|
||||
commit("addCalendarRemotesForUser", { user, remotes, start, end });
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
},
|
||||
fetchCalendarLocalsForUser({commit, getters}, {user, start, end}) {
|
||||
if (!getters.isCalendarRemoteLoadedForUser({user, start, end})) {
|
||||
fetchCalendarLocalsForUser({ commit, getters }, { user, start, end }) {
|
||||
if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) {
|
||||
return fetchCalendarLocalForUser(user, start, end).then((locals) => {
|
||||
commit('addCalendarLocalsForUser', {user, locals, start, end});
|
||||
commit("addCalendarLocalsForUser", { user, locals, start, end });
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
},
|
||||
addPersonsInvolved({commit, dispatch}, payload) {
|
||||
console.log('### action addPersonsInvolved', payload.result.type);
|
||||
console.log('### action addPersonsInvolved payload result', payload.result);
|
||||
addPersonsInvolved({ commit, dispatch }, payload) {
|
||||
console.log("### action addPersonsInvolved", payload.result.type);
|
||||
console.log("### action addPersonsInvolved payload result", payload.result);
|
||||
switch (payload.result.type) {
|
||||
case 'person':
|
||||
let aPersons = document.getElementById("chill_activitybundle_activity_persons");
|
||||
case "person":
|
||||
let aPersons = document.getElementById(
|
||||
"chill_activitybundle_activity_persons",
|
||||
);
|
||||
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
|
||||
break;
|
||||
case 'thirdparty':
|
||||
let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals");
|
||||
aThirdParties.value = addIdToValue(aThirdParties.value, payload.result.id);
|
||||
case "thirdparty":
|
||||
let aThirdParties = document.getElementById(
|
||||
"chill_activitybundle_activity_professionals",
|
||||
);
|
||||
aThirdParties.value = addIdToValue(
|
||||
aThirdParties.value,
|
||||
payload.result.id,
|
||||
);
|
||||
break;
|
||||
case 'user':
|
||||
let aUsers = document.getElementById("chill_activitybundle_activity_users");
|
||||
case "user":
|
||||
let aUsers = document.getElementById(
|
||||
"chill_activitybundle_activity_users",
|
||||
);
|
||||
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
|
||||
commit('showUserOnCalendar', {user: payload.result, ranges: false, remotes: true});
|
||||
dispatch('fetchCalendarEvents');
|
||||
commit("showUserOnCalendar", {
|
||||
user: payload.result,
|
||||
ranges: false,
|
||||
remotes: true,
|
||||
});
|
||||
dispatch("fetchCalendarEvents");
|
||||
break;
|
||||
}
|
||||
;
|
||||
commit('addPersonsInvolved', payload);
|
||||
commit("addPersonsInvolved", payload);
|
||||
},
|
||||
removePersonInvolved({commit}, payload) {
|
||||
removePersonInvolved({ commit }, payload) {
|
||||
//console.log('### action removePersonInvolved', payload);
|
||||
switch (payload.type) {
|
||||
case 'person':
|
||||
let aPersons = document.getElementById("chill_activitybundle_activity_persons");
|
||||
case "person":
|
||||
let aPersons = document.getElementById(
|
||||
"chill_activitybundle_activity_persons",
|
||||
);
|
||||
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
|
||||
break;
|
||||
case 'thirdparty':
|
||||
let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals");
|
||||
aThirdParties.value = removeIdFromValue(aThirdParties.value, payload.id);
|
||||
case "thirdparty":
|
||||
let aThirdParties = document.getElementById(
|
||||
"chill_activitybundle_activity_professionals",
|
||||
);
|
||||
aThirdParties.value = removeIdFromValue(
|
||||
aThirdParties.value,
|
||||
payload.id,
|
||||
);
|
||||
break;
|
||||
case 'user':
|
||||
let aUsers = document.getElementById("chill_activitybundle_activity_users");
|
||||
case "user":
|
||||
let aUsers = document.getElementById(
|
||||
"chill_activitybundle_activity_users",
|
||||
);
|
||||
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
|
||||
break;
|
||||
}
|
||||
;
|
||||
commit('removePersonInvolved', payload);
|
||||
commit("removePersonInvolved", payload);
|
||||
},
|
||||
|
||||
// Calendar
|
||||
@@ -148,31 +168,49 @@ export default {
|
||||
* @param start
|
||||
* @param end
|
||||
*/
|
||||
setEventTimes({commit, state, getters}, {start, end}) {
|
||||
console.log('### action createEvent', {start, end});
|
||||
let startDateInput = document.getElementById("chill_activitybundle_activity_startDate");
|
||||
startDateInput.value = null !== start ? datetimeToISO(start) : '';
|
||||
let endDateInput = document.getElementById("chill_activitybundle_activity_endDate");
|
||||
endDateInput.value = null !== end ? datetimeToISO(end) : '';
|
||||
let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange");
|
||||
setEventTimes({ commit, state, getters }, { start, end }) {
|
||||
console.log("### action createEvent", { start, end });
|
||||
let startDateInput = document.getElementById(
|
||||
"chill_activitybundle_activity_startDate",
|
||||
);
|
||||
startDateInput.value = null !== start ? datetimeToISO(start) : "";
|
||||
let endDateInput = document.getElementById(
|
||||
"chill_activitybundle_activity_endDate",
|
||||
);
|
||||
endDateInput.value = null !== end ? datetimeToISO(end) : "";
|
||||
let calendarRangeInput = document.getElementById(
|
||||
"chill_activitybundle_activity_calendarRange",
|
||||
);
|
||||
calendarRangeInput.value = "";
|
||||
|
||||
if (getters.getMainUser === null || getters.getMainUser.id !== state.me.id) {
|
||||
let mainUserInput = document.getElementById("chill_activitybundle_activity_mainUser");
|
||||
if (
|
||||
getters.getMainUser === null ||
|
||||
getters.getMainUser.id !== state.me.id
|
||||
) {
|
||||
let mainUserInput = document.getElementById(
|
||||
"chill_activitybundle_activity_mainUser",
|
||||
);
|
||||
mainUserInput.value = state.me.id;
|
||||
commit('setMainUser', state.me);
|
||||
commit("setMainUser", state.me);
|
||||
}
|
||||
|
||||
commit('setEventTimes', {start, end});
|
||||
commit("setEventTimes", { start, end });
|
||||
},
|
||||
associateCalendarToRange({state, commit, dispatch, getters}, {range}) {
|
||||
console.log('### action associateCAlendarToRange', range);
|
||||
let startDateInput = document.getElementById("chill_activitybundle_activity_startDate");
|
||||
associateCalendarToRange({ state, commit, dispatch, getters }, { range }) {
|
||||
console.log("### action associateCAlendarToRange", range);
|
||||
let startDateInput = document.getElementById(
|
||||
"chill_activitybundle_activity_startDate",
|
||||
);
|
||||
startDateInput.value = null !== range ? datetimeToISO(range.start) : "";
|
||||
let endDateInput = document.getElementById("chill_activitybundle_activity_endDate");
|
||||
let endDateInput = document.getElementById(
|
||||
"chill_activitybundle_activity_endDate",
|
||||
);
|
||||
endDateInput.value = null !== range ? datetimeToISO(range.end) : "";
|
||||
let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange");
|
||||
calendarRangeInput.value = null !== range ? Number(range.extendedProps.calendarRangeId) : "";
|
||||
let calendarRangeInput = document.getElementById(
|
||||
"chill_activitybundle_activity_calendarRange",
|
||||
);
|
||||
calendarRangeInput.value =
|
||||
null !== range ? Number(range.extendedProps.calendarRangeId) : "";
|
||||
|
||||
if (null !== range) {
|
||||
let location = getters.getLocationById(range.extendedProps.locationId);
|
||||
@@ -181,63 +219,68 @@ export default {
|
||||
console.error("location not found!", range.extendedProps.locationId);
|
||||
}
|
||||
|
||||
dispatch('updateLocation', location);
|
||||
dispatch("updateLocation", location);
|
||||
|
||||
const userId = range.extendedProps.userId;
|
||||
if (state.activity.mainUser !== null && state.activity.mainUser.id !== userId) {
|
||||
dispatch('setMainUser', state.usersData.get(userId).user);
|
||||
if (
|
||||
state.activity.mainUser !== null &&
|
||||
state.activity.mainUser.id !== userId
|
||||
) {
|
||||
dispatch("setMainUser", state.usersData.get(userId).user);
|
||||
|
||||
// TODO: remove persons involved with this user
|
||||
}
|
||||
}
|
||||
|
||||
commit('associateCalendarToRange', {range});
|
||||
commit("associateCalendarToRange", { range });
|
||||
return Promise.resolve();
|
||||
},
|
||||
setMainUser({commit, dispatch, state}, mainUser) {
|
||||
console.log('setMainUser', mainUser);
|
||||
setMainUser({ commit, dispatch, state }, mainUser) {
|
||||
console.log("setMainUser", mainUser);
|
||||
|
||||
let mainUserInput = document.getElementById("chill_activitybundle_activity_mainUser");
|
||||
let mainUserInput = document.getElementById(
|
||||
"chill_activitybundle_activity_mainUser",
|
||||
);
|
||||
mainUserInput.value = Number(mainUser.id);
|
||||
|
||||
return dispatch('associateCalendarToRange', { range: null }).then(() => {
|
||||
commit('setMainUser', mainUser);
|
||||
return dispatch("associateCalendarToRange", { range: null }).then(() => {
|
||||
commit("setMainUser", mainUser);
|
||||
|
||||
return dispatch('fetchCalendarEvents');
|
||||
return dispatch("fetchCalendarEvents");
|
||||
});
|
||||
},
|
||||
|
||||
// Location
|
||||
updateLocation({commit}, value) {
|
||||
console.log('### action: updateLocation', value);
|
||||
let hiddenLocation = document.getElementById("chill_activitybundle_activity_location");
|
||||
updateLocation({ commit }, value) {
|
||||
console.log("### action: updateLocation", value);
|
||||
let hiddenLocation = document.getElementById(
|
||||
"chill_activitybundle_activity_location",
|
||||
);
|
||||
if (value.onthefly) {
|
||||
const body = {
|
||||
"type": "location",
|
||||
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name,
|
||||
"locationType": {
|
||||
"id": value.locationType.id,
|
||||
"type": "location-type"
|
||||
}
|
||||
type: "location",
|
||||
name:
|
||||
value.name === "__AccompanyingCourseLocation__" ? null : value.name,
|
||||
locationType: {
|
||||
id: value.locationType.id,
|
||||
type: "location-type",
|
||||
},
|
||||
};
|
||||
if (value.address.id) {
|
||||
Object.assign(body, {
|
||||
"address": {
|
||||
"id": value.address.id
|
||||
address: {
|
||||
id: value.address.id,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
postLocation(body)
|
||||
.then(
|
||||
location => hiddenLocation.value = location.id
|
||||
).catch(
|
||||
err => {
|
||||
.then((location) => (hiddenLocation.value = location.id))
|
||||
.catch((err) => {
|
||||
console.log(err.message);
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
hiddenLocation.value = value.id;
|
||||
}
|
||||
commit("updateLocation", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -18,7 +18,7 @@ export default {
|
||||
if (null === state.activity.start) {
|
||||
return new Date();
|
||||
}
|
||||
throw 'transform date to object ?';
|
||||
throw "transform date to object ?";
|
||||
},
|
||||
/**
|
||||
* Compute the event sources to show on the FullCalendar
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
// current calendar
|
||||
if (state.activity.startDate !== null && state.activity.endDate !== null) {
|
||||
const s = {
|
||||
id: 'current',
|
||||
id: "current",
|
||||
events: [
|
||||
{
|
||||
title: "Rendez-vous",
|
||||
@@ -41,8 +41,8 @@ export default {
|
||||
end: state.activity.endDate,
|
||||
allDay: false,
|
||||
is: "current",
|
||||
classNames: ['iscurrent'],
|
||||
}
|
||||
classNames: ["iscurrent"],
|
||||
},
|
||||
],
|
||||
editable: state.activity.calendarRange === null,
|
||||
};
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
|
||||
for (const [userId, kinds] of state.currentView.users.entries()) {
|
||||
if (!state.usersData.has(userId)) {
|
||||
console.log('try to get events on a user which not exists', userId);
|
||||
console.log("try to get events on a user which not exists", userId);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -61,11 +61,16 @@ export default {
|
||||
if (kinds.ranges && userData.calendarRanges.length > 0) {
|
||||
const s = {
|
||||
id: `ranges_${userId}`,
|
||||
events: userData.calendarRanges.filter(r => state.activity.calendarRange === null || r.calendarRangeId !== state.activity.calendarRange.calendarRangeId),
|
||||
events: userData.calendarRanges.filter(
|
||||
(r) =>
|
||||
state.activity.calendarRange === null ||
|
||||
r.calendarRangeId !==
|
||||
state.activity.calendarRange.calendarRangeId,
|
||||
),
|
||||
color: userData.mainColor,
|
||||
classNames: ['isrange'],
|
||||
backgroundColor: 'white',
|
||||
textColor: 'black',
|
||||
classNames: ["isrange"],
|
||||
backgroundColor: "white",
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
};
|
||||
|
||||
@@ -74,10 +79,10 @@ export default {
|
||||
|
||||
if (kinds.remotes && userData.remotes.length > 0) {
|
||||
const s = {
|
||||
'id': `remote_${userId}`,
|
||||
id: `remote_${userId}`,
|
||||
events: userData.remotes,
|
||||
color: userData.mainColor,
|
||||
textColor: 'black',
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
};
|
||||
|
||||
@@ -87,10 +92,12 @@ export default {
|
||||
// if remotes is checked, we display also the locals calendars
|
||||
if (kinds.remotes && userData.locals.length > 0) {
|
||||
const s = {
|
||||
'id': `local_${userId}`,
|
||||
events: userData.locals.filter(l => l.originId !== state.activity.id),
|
||||
id: `local_${userId}`,
|
||||
events: userData.locals.filter(
|
||||
(l) => l.originId !== state.activity.id,
|
||||
),
|
||||
color: userData.mainColor,
|
||||
textColor: 'black',
|
||||
textColor: "black",
|
||||
editable: false,
|
||||
};
|
||||
|
||||
@@ -104,7 +111,7 @@ export default {
|
||||
return state.activity.startDate;
|
||||
},
|
||||
getInviteForUser: (state) => (user) => {
|
||||
return state.activity.invites.find(i => i.user.id === user.id);
|
||||
return state.activity.invites.find((i) => i.user.id === user.id);
|
||||
},
|
||||
/**
|
||||
* get the user data for a specific user
|
||||
@@ -138,19 +145,21 @@ export default {
|
||||
* @param getters
|
||||
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
|
||||
*/
|
||||
isCalendarRangeLoadedForUser: (state, getters) => ({user, start, end}) => {
|
||||
if (!getters.hasUserData(user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let interval of getters.getUserData(user).calendarRangesLoaded) {
|
||||
if (start >= interval.start && end <= interval.end) {
|
||||
return true;
|
||||
isCalendarRangeLoadedForUser:
|
||||
(state, getters) =>
|
||||
({ user, start, end }) => {
|
||||
if (!getters.hasUserData(user)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
for (let interval of getters.getUserData(user).calendarRangesLoaded) {
|
||||
if (start >= interval.start && end <= interval.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* return true if there was a fetch query for event between this date (start and end),
|
||||
* those date are included.
|
||||
@@ -159,19 +168,21 @@ export default {
|
||||
* @param getters
|
||||
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
|
||||
*/
|
||||
isCalendarRemoteLoadedForUser: (state, getters) => ({user, start, end}) => {
|
||||
if (!getters.hasUserData(user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let interval of getters.getUserData(user).remotesLoaded) {
|
||||
if (start >= interval.start && end <= interval.end) {
|
||||
return true;
|
||||
isCalendarRemoteLoadedForUser:
|
||||
(state, getters) =>
|
||||
({ user, start, end }) => {
|
||||
if (!getters.hasUserData(user)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
for (let interval of getters.getUserData(user).remotesLoaded) {
|
||||
if (start >= interval.start && end <= interval.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* return true if the user ranges are shown on calendar
|
||||
*
|
||||
@@ -180,8 +191,10 @@ export default {
|
||||
*/
|
||||
isRangeShownOnCalendarForUser: (state) => (user) => {
|
||||
const k = state.currentView.users.get(user.id);
|
||||
if (typeof k === 'undefined') {
|
||||
console.error('try to determinate if calendar range is shown and user is not in currentView');
|
||||
if (typeof k === "undefined") {
|
||||
console.error(
|
||||
"try to determinate if calendar range is shown and user is not in currentView",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -195,8 +208,10 @@ export default {
|
||||
*/
|
||||
isRemoteShownOnCalendarForUser: (state) => (user) => {
|
||||
const k = state.currentView.users.get(user.id);
|
||||
if (typeof k === 'undefined') {
|
||||
console.error('try to determinate if calendar range is shown and user is not in currentView');
|
||||
if (typeof k === "undefined") {
|
||||
console.error(
|
||||
"try to determinate if calendar range is shown and user is not in currentView",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -205,8 +220,8 @@ export default {
|
||||
|
||||
getLocationById: (state) => (id) => {
|
||||
for (let group of state.availableLocations) {
|
||||
console.log('group', group);
|
||||
const found = group.locations.find(l => l.id === id);
|
||||
console.log("group", group);
|
||||
const found = group.locations.find((l) => l.id === id);
|
||||
if (typeof found !== "undefined") {
|
||||
return found;
|
||||
}
|
||||
@@ -216,57 +231,60 @@ export default {
|
||||
},
|
||||
|
||||
suggestedEntities(state, getters) {
|
||||
if (typeof (state.activity.accompanyingPeriod) === 'undefined') {
|
||||
if (typeof state.activity.accompanyingPeriod === "undefined") {
|
||||
return [];
|
||||
}
|
||||
const allEntities = [
|
||||
...getters.suggestedPersons,
|
||||
...getters.suggestedRequestor,
|
||||
...getters.suggestedUser,
|
||||
...getters.suggestedResources
|
||||
...getters.suggestedResources,
|
||||
];
|
||||
const uniqueIds = [...new Set(allEntities.map(i => `${i.type}-${i.id}`))];
|
||||
return Array.from(uniqueIds, id => allEntities.filter(r => `${r.type}-${r.id}` === id)[0]);
|
||||
const uniqueIds = [...new Set(allEntities.map((i) => `${i.type}-${i.id}`))];
|
||||
return Array.from(
|
||||
uniqueIds,
|
||||
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0],
|
||||
);
|
||||
},
|
||||
suggestedPersons(state) {
|
||||
const existingPersonIds = state.activity.persons.map(p => p.id);
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
return state.activity.accompanyingPeriod.participations
|
||||
.filter(p => p.endDate === null)
|
||||
.map(p => p.person)
|
||||
.filter(p => !existingPersonIds.includes(p.id))
|
||||
.filter((p) => p.endDate === null)
|
||||
.map((p) => p.person)
|
||||
.filter((p) => !existingPersonIds.includes(p.id));
|
||||
},
|
||||
suggestedRequestor(state) {
|
||||
if (state.activity.accompanyingPeriod.requestor === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const existingPersonIds = state.activity.persons.map(p => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id);
|
||||
return [state.activity.accompanyingPeriod.requestor]
|
||||
.filter(r =>
|
||||
(r.type === 'person' && !existingPersonIds.includes(r.id)) ||
|
||||
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id))
|
||||
);
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
|
||||
return [state.activity.accompanyingPeriod.requestor].filter(
|
||||
(r) =>
|
||||
(r.type === "person" && !existingPersonIds.includes(r.id)) ||
|
||||
(r.type === "thirdparty" && !existingThirdPartyIds.includes(r.id)),
|
||||
);
|
||||
},
|
||||
suggestedUser(state) {
|
||||
if (null === state.activity.users) {
|
||||
return [];
|
||||
}
|
||||
const existingUserIds = state.activity.users.map(p => p.id);
|
||||
return [state.activity.accompanyingPeriod.user]
|
||||
.filter(
|
||||
u => u !== null && !existingUserIds.includes(u.id)
|
||||
);
|
||||
const existingUserIds = state.activity.users.map((p) => p.id);
|
||||
return [state.activity.accompanyingPeriod.user].filter(
|
||||
(u) => u !== null && !existingUserIds.includes(u.id),
|
||||
);
|
||||
},
|
||||
suggestedResources(state) {
|
||||
const resources = state.activity.accompanyingPeriod.resources;
|
||||
const existingPersonIds = state.activity.persons.map(p => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id);
|
||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||
const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
|
||||
return state.activity.accompanyingPeriod.resources
|
||||
.map(r => r.resource)
|
||||
.filter(r =>
|
||||
(r.type === 'person' && !existingPersonIds.includes(r.id)) ||
|
||||
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id))
|
||||
.map((r) => r.resource)
|
||||
.filter(
|
||||
(r) =>
|
||||
(r.type === "person" && !existingPersonIds.includes(r.id)) ||
|
||||
(r.type === "thirdparty" && !existingThirdPartyIds.includes(r.id)),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -1,57 +1,65 @@
|
||||
import 'es6-promise/auto';
|
||||
import { createStore } from 'vuex';
|
||||
import { postLocation } from 'ChillActivityAssets/vuejs/Activity/api';
|
||||
import getters from './getters';
|
||||
import actions from './actions';
|
||||
import mutations from './mutations';
|
||||
import { mapEntity } from './utils';
|
||||
import { whoami } from '../api';
|
||||
import "es6-promise/auto";
|
||||
import { createStore } from "vuex";
|
||||
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
|
||||
import getters from "./getters";
|
||||
import actions from "./actions";
|
||||
import mutations from "./mutations";
|
||||
import { mapEntity } from "./utils";
|
||||
import { whoami } from "../api";
|
||||
import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations";
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
const debug = process.env.NODE_ENV !== "production";
|
||||
|
||||
const store = createStore({
|
||||
strict: debug,
|
||||
state: {
|
||||
activity: mapEntity(window.entity), // activity is the calendar entity actually
|
||||
currentEvent: null,
|
||||
availableLocations: [],
|
||||
/**
|
||||
* the current user
|
||||
*/
|
||||
me: null,
|
||||
/**
|
||||
* store information about current view
|
||||
*/
|
||||
currentView: {
|
||||
start: null,
|
||||
end: null,
|
||||
users: new Map(),
|
||||
},
|
||||
/**
|
||||
* store a list of existing event, to avoid storing them twice
|
||||
*/
|
||||
existingEvents: new Set(),
|
||||
/**
|
||||
* store user data
|
||||
*/
|
||||
usersData: new Map(),
|
||||
},
|
||||
getters,
|
||||
mutations,
|
||||
actions,
|
||||
strict: debug,
|
||||
state: {
|
||||
activity: mapEntity(window.entity), // activity is the calendar entity actually
|
||||
currentEvent: null,
|
||||
availableLocations: [],
|
||||
/**
|
||||
* the current user
|
||||
*/
|
||||
me: null,
|
||||
/**
|
||||
* store information about current view
|
||||
*/
|
||||
currentView: {
|
||||
start: null,
|
||||
end: null,
|
||||
users: new Map(),
|
||||
},
|
||||
/**
|
||||
* store a list of existing event, to avoid storing them twice
|
||||
*/
|
||||
existingEvents: new Set(),
|
||||
/**
|
||||
* store user data
|
||||
*/
|
||||
usersData: new Map(),
|
||||
},
|
||||
getters,
|
||||
mutations,
|
||||
actions,
|
||||
});
|
||||
|
||||
whoami().then(me => {
|
||||
store.commit('setWhoAmiI', me);
|
||||
whoami().then((me) => {
|
||||
store.commit("setWhoAmiI", me);
|
||||
});
|
||||
|
||||
if (null !== store.getters.getMainUser) {
|
||||
store.commit('showUserOnCalendar', {ranges: true, remotes: true, user: store.getters.getMainUser});
|
||||
store.commit("showUserOnCalendar", {
|
||||
ranges: true,
|
||||
remotes: true,
|
||||
user: store.getters.getMainUser,
|
||||
});
|
||||
}
|
||||
|
||||
for (let u of store.state.activity.users) {
|
||||
store.commit('showUserOnCalendar', {ranges: false, remotes: false, user: u});
|
||||
store.commit("showUserOnCalendar", {
|
||||
ranges: false,
|
||||
remotes: false,
|
||||
user: u,
|
||||
});
|
||||
}
|
||||
|
||||
prepareLocations(store);
|
||||
|
@@ -3,30 +3,27 @@ import {
|
||||
calendarRangeToFullCalendarEvent,
|
||||
remoteToFullCalendarEvent,
|
||||
localsToFullCalendarEvent,
|
||||
} from './utils';
|
||||
} from "./utils";
|
||||
|
||||
export default {
|
||||
setWhoAmiI(state, me) {
|
||||
state.me = me;
|
||||
},
|
||||
setCurrentDatesView(state, {start, end}) {
|
||||
setCurrentDatesView(state, { start, end }) {
|
||||
state.currentView.start = start;
|
||||
state.currentView.end = end;
|
||||
},
|
||||
showUserOnCalendar(state, {user, ranges, remotes}) {
|
||||
showUserOnCalendar(state, { user, ranges, remotes }) {
|
||||
if (!state.usersData.has(user.id)) {
|
||||
state.usersData.set(user.id, createUserData(user, state.usersData.size));
|
||||
}
|
||||
|
||||
const cur = state.currentView.users.get(user.id);
|
||||
|
||||
state.currentView.users.set(
|
||||
user.id,
|
||||
{
|
||||
ranges: typeof ranges !== 'undefined' ? ranges : cur.ranges,
|
||||
remotes: typeof remotes !== 'undefined' ? remotes : cur.remotes,
|
||||
}
|
||||
);
|
||||
state.currentView.users.set(user.id, {
|
||||
ranges: typeof ranges !== "undefined" ? ranges : cur.ranges,
|
||||
remotes: typeof remotes !== "undefined" ? remotes : cur.remotes,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Set the event start and end to the given start and end,
|
||||
@@ -36,7 +33,7 @@ export default {
|
||||
* @param Date start
|
||||
* @param Date end
|
||||
*/
|
||||
setEventTimes(state, {start, end}) {
|
||||
setEventTimes(state, { start, end }) {
|
||||
state.activity.startDate = start;
|
||||
state.activity.endDate = end;
|
||||
state.activity.calendarRange = null;
|
||||
@@ -48,8 +45,8 @@ export default {
|
||||
* @param state
|
||||
* @param range
|
||||
*/
|
||||
associateCalendarToRange(state, {range}) {
|
||||
console.log('associateCalendarToRange', range);
|
||||
associateCalendarToRange(state, { range }) {
|
||||
console.log("associateCalendarToRange", range);
|
||||
|
||||
if (null === range) {
|
||||
state.activity.calendarRange = null;
|
||||
@@ -59,22 +56,25 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('userId', range.extendedProps.userId);
|
||||
console.log("userId", range.extendedProps.userId);
|
||||
|
||||
const r = state.usersData.get(range.extendedProps.userId).calendarRanges
|
||||
.find(r => r.calendarRangeId === range.extendedProps.calendarRangeId);
|
||||
const r = state.usersData
|
||||
.get(range.extendedProps.userId)
|
||||
.calendarRanges.find(
|
||||
(r) => r.calendarRangeId === range.extendedProps.calendarRangeId,
|
||||
);
|
||||
|
||||
if (typeof r === 'undefined') {
|
||||
throw Error('Could not find managed calendar range');
|
||||
if (typeof r === "undefined") {
|
||||
throw Error("Could not find managed calendar range");
|
||||
}
|
||||
|
||||
console.log('range found', r);
|
||||
console.log("range found", r);
|
||||
|
||||
state.activity.startDate = range.start;
|
||||
state.activity.endDate = range.end;
|
||||
state.activity.calendarRange = r;
|
||||
|
||||
console.log('activity', state.activity);
|
||||
console.log("activity", state.activity);
|
||||
},
|
||||
|
||||
setMainUser(state, user) {
|
||||
@@ -85,32 +85,36 @@ export default {
|
||||
addPersonsInvolved(state, payload) {
|
||||
//console.log('### mutation addPersonsInvolved', payload.result.type);
|
||||
switch (payload.result.type) {
|
||||
case 'person':
|
||||
case "person":
|
||||
state.activity.persons.push(payload.result);
|
||||
break;
|
||||
case 'thirdparty':
|
||||
case "thirdparty":
|
||||
state.activity.thirdParties.push(payload.result);
|
||||
break;
|
||||
case 'user':
|
||||
case "user":
|
||||
state.activity.users.push(payload.result);
|
||||
break;
|
||||
}
|
||||
;
|
||||
},
|
||||
removePersonInvolved(state, payload) {
|
||||
//console.log('### mutation removePersonInvolved', payload.type);
|
||||
switch (payload.type) {
|
||||
case 'person':
|
||||
state.activity.persons = state.activity.persons.filter(person => person !== payload);
|
||||
case "person":
|
||||
state.activity.persons = state.activity.persons.filter(
|
||||
(person) => person !== payload,
|
||||
);
|
||||
break;
|
||||
case 'thirdparty':
|
||||
state.activity.thirdParties = state.activity.thirdParties.filter(thirdparty => thirdparty !== payload);
|
||||
case "thirdparty":
|
||||
state.activity.thirdParties = state.activity.thirdParties.filter(
|
||||
(thirdparty) => thirdparty !== payload,
|
||||
);
|
||||
break;
|
||||
case 'user':
|
||||
state.activity.users = state.activity.users.filter(user => user !== payload);
|
||||
case "user":
|
||||
state.activity.users = state.activity.users.filter(
|
||||
(user) => user !== payload,
|
||||
);
|
||||
break;
|
||||
}
|
||||
;
|
||||
},
|
||||
/**
|
||||
* Add CalendarRange object for an user
|
||||
@@ -121,7 +125,7 @@ export default {
|
||||
* @param start
|
||||
* @param end
|
||||
*/
|
||||
addCalendarRangesForUser(state, {user, ranges, start, end}) {
|
||||
addCalendarRangesForUser(state, { user, ranges, start, end }) {
|
||||
let userData;
|
||||
if (state.usersData.has(user.id)) {
|
||||
userData = state.usersData.get(user.id);
|
||||
@@ -131,18 +135,18 @@ export default {
|
||||
}
|
||||
|
||||
const eventRanges = ranges
|
||||
.filter(r => !state.existingEvents.has(`range_${r.id}`))
|
||||
.map(r => {
|
||||
.filter((r) => !state.existingEvents.has(`range_${r.id}`))
|
||||
.map((r) => {
|
||||
// add to existing ids
|
||||
state.existingEvents.add(`range_${r.id}`);
|
||||
return r;
|
||||
})
|
||||
.map(r => calendarRangeToFullCalendarEvent(r));
|
||||
.map((r) => calendarRangeToFullCalendarEvent(r));
|
||||
|
||||
userData.calendarRanges = userData.calendarRanges.concat(eventRanges);
|
||||
userData.calendarRangesLoaded.push({start, end});
|
||||
userData.calendarRangesLoaded.push({ start, end });
|
||||
},
|
||||
addCalendarRemotesForUser(state, {user, remotes, start, end}) {
|
||||
addCalendarRemotesForUser(state, { user, remotes, start, end }) {
|
||||
let userData;
|
||||
if (state.usersData.has(user.id)) {
|
||||
userData = state.usersData.get(user.id);
|
||||
@@ -152,18 +156,18 @@ export default {
|
||||
}
|
||||
|
||||
const eventRemotes = remotes
|
||||
.filter(r => !state.existingEvents.has(`remote_${r.id}`))
|
||||
.map(r => {
|
||||
.filter((r) => !state.existingEvents.has(`remote_${r.id}`))
|
||||
.map((r) => {
|
||||
// add to existing ids
|
||||
state.existingEvents.add(`remote_${r.id}`);
|
||||
return r;
|
||||
})
|
||||
.map(r => remoteToFullCalendarEvent(r));
|
||||
.map((r) => remoteToFullCalendarEvent(r));
|
||||
|
||||
userData.remotes = userData.remotes.concat(eventRemotes);
|
||||
userData.remotesLoaded.push({start, end});
|
||||
userData.remotesLoaded.push({ start, end });
|
||||
},
|
||||
addCalendarLocalsForUser(state, {user, locals, start, end}) {
|
||||
addCalendarLocalsForUser(state, { user, locals, start, end }) {
|
||||
let userData;
|
||||
if (state.usersData.has(user.id)) {
|
||||
userData = state.usersData.get(user.id);
|
||||
@@ -173,20 +177,20 @@ export default {
|
||||
}
|
||||
|
||||
const eventRemotes = locals
|
||||
.filter(r => !state.existingEvents.has(`locals_${r.id}`))
|
||||
.map(r => {
|
||||
.filter((r) => !state.existingEvents.has(`locals_${r.id}`))
|
||||
.map((r) => {
|
||||
// add to existing ids
|
||||
state.existingEvents.add(`locals_${r.id}`);
|
||||
return r;
|
||||
})
|
||||
.map(r => localsToFullCalendarEvent(r));
|
||||
.map((r) => localsToFullCalendarEvent(r));
|
||||
|
||||
userData.locals = userData.locals.concat(eventRemotes);
|
||||
userData.localsLoaded.push({start, end});
|
||||
userData.localsLoaded.push({ start, end });
|
||||
},
|
||||
// Location
|
||||
updateLocation(state, value) {
|
||||
console.log('### mutation: updateLocation', value);
|
||||
console.log("### mutation: updateLocation", value);
|
||||
state.activity.location = value;
|
||||
},
|
||||
addAvailableLocationGroup(state, group) {
|
||||
|
@@ -1,108 +1,117 @@
|
||||
import {COLORS} from '../const';
|
||||
import {ISOToDatetime} from '../../../../../../ChillMainBundle/Resources/public/chill/js/date';
|
||||
import {DateTime, User} from '../../../../../../ChillMainBundle/Resources/public/types';
|
||||
import {CalendarLight, CalendarRange, CalendarRemote} from '../../../types';
|
||||
import type {EventInputCalendarRange} from '../../../types';
|
||||
import {EventInput} from '@fullcalendar/core';
|
||||
import { COLORS } from "../const";
|
||||
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import {
|
||||
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 => {
|
||||
let array = string ? string.split(',') : [];
|
||||
array.push(id.toString());
|
||||
let 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());
|
||||
let 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
|
||||
*/
|
||||
* Assign missing keys for the ConcernedGroups component
|
||||
*/
|
||||
export const mapEntity = (entity: EventInput): EventInput => {
|
||||
let 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;
|
||||
export const createUserData = (user: User, colorIndex: number): UserData => {
|
||||
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): 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',
|
||||
};
|
||||
}
|
||||
export const calendarRangeToFullCalendarEvent = (
|
||||
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",
|
||||
};
|
||||
};
|
||||
|
||||
export const remoteToFullCalendarEvent = (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',
|
||||
};
|
||||
}
|
||||
export const remoteToFullCalendarEvent = (
|
||||
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",
|
||||
};
|
||||
};
|
||||
|
||||
export const localsToFullCalendarEvent = (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',
|
||||
};
|
||||
}
|
||||
export const localsToFullCalendarEvent = (
|
||||
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",
|
||||
};
|
||||
};
|
||||
|
@@ -1,95 +1,133 @@
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<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
|
||||
>
|
||||
</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';
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
const ACCEPTED = 'accepted';
|
||||
const DECLINED = 'declined';
|
||||
const PENDING = 'pending';
|
||||
const TENTATIVELY_ACCEPTED = 'tentative';
|
||||
const ACCEPTED = "accepted";
|
||||
const DECLINED = "declined";
|
||||
const PENDING = "pending";
|
||||
const TENTATIVELY_ACCEPTED = "tentative";
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
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",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
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;
|
||||
name: "Answer",
|
||||
i18n,
|
||||
props: {
|
||||
calendarId: { type: Number, required: true },
|
||||
status: {
|
||||
type: String as PropType<
|
||||
"accepted" | "declined" | "pending" | "tentative"
|
||||
>,
|
||||
required: 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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<style scoped></style>
|
||||
|
@@ -1,180 +1,225 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
||||
<vue-multiselect
|
||||
v-model="pickedLocation"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
||||
:selectedLabel="'Choisir'"
|
||||
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
||||
:placeholder="'Choisir'"
|
||||
></vue-multiselect>
|
||||
</div>
|
||||
</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>
|
||||
</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-xs-12 col-sm-3">
|
||||
<div class="float-end">
|
||||
<div class="form-check input-group">
|
||||
<span class="input-group-text">
|
||||
<input
|
||||
id="showHideWE"
|
||||
class="mt-0"
|
||||
type="checkbox"
|
||||
v-model="showWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label for="showHideWE" class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
||||
<vue-multiselect
|
||||
v-model="pickedLocation"
|
||||
:options="locations"
|
||||
:label="'name'"
|
||||
:track-by="'id'"
|
||||
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
||||
:selectedLabel="'Choisir'"
|
||||
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
||||
:placeholder="'Choisir'"
|
||||
></vue-multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
||||
<template v-slot:eventContent="arg: EventApi">
|
||||
<span :class="eventClasses(arg.event)">
|
||||
<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 }}</b
|
||||
>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<b v-else>no 'is'</b>
|
||||
<a
|
||||
v-if="arg.event.extendedProps.is === 'range'"
|
||||
class="fa fa-fw fa-times delete"
|
||||
@click.prevent="onClickDelete(arg.event)"
|
||||
>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
|
||||
<div id="copy-widget">
|
||||
<div class="container mt-2 mb-2">
|
||||
|
||||
<div class="row justify-content-between align-items-center mb-4">
|
||||
<div class="col-xs-12 col-sm-3 col-md-2">
|
||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
|
||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||
<option value="week">{{ $t("from_week_to_week") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<template v-if="dayOrWeek === 'day'">
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input class="form-control" type="date" v-model="copyFrom" />
|
||||
<div
|
||||
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>
|
||||
</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 class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input class="form-control" type="date" v-model="copyTo" />
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button class="btn btn-action float-end" @click="copyDay">
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<select
|
||||
v-model="copyFromWeek"
|
||||
id="copyFromWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
|
||||
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button class="btn btn-action float-end" @click="copyWeek">
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3">
|
||||
<div class="float-end">
|
||||
<div class="form-check input-group">
|
||||
<span class="input-group-text">
|
||||
<input
|
||||
id="showHideWE"
|
||||
class="mt-0"
|
||||
type="checkbox"
|
||||
v-model="showWeekends"
|
||||
/>
|
||||
</span>
|
||||
<label
|
||||
for="showHideWE"
|
||||
class="form-check-label input-group-text"
|
||||
>Week-ends</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
||||
<template v-slot:eventContent="arg: EventApi">
|
||||
<span :class="eventClasses(arg.event)">
|
||||
<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 }}</b
|
||||
>
|
||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||
arg.event.title
|
||||
}}</b>
|
||||
<b v-else>no 'is'</b>
|
||||
<a
|
||||
v-if="arg.event.extendedProps.is === 'range'"
|
||||
class="fa fa-fw fa-times delete"
|
||||
@click.prevent="onClickDelete(arg.event)"
|
||||
>
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
|
||||
</div>
|
||||
<div id="copy-widget">
|
||||
<div class="container mt-2 mb-2">
|
||||
<div class="row justify-content-between align-items-center mb-4">
|
||||
<div class="col-xs-12 col-sm-3 col-md-2">
|
||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||
<select
|
||||
v-model="dayOrWeek"
|
||||
id="dayOrWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||
<option value="week">
|
||||
{{ $t("from_week_to_week") }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<template v-if="dayOrWeek === 'day'">
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input
|
||||
class="form-control"
|
||||
type="date"
|
||||
v-model="copyFrom"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<input
|
||||
class="form-control"
|
||||
type="date"
|
||||
v-model="copyTo"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button
|
||||
class="btn btn-action float-end"
|
||||
@click="copyDay"
|
||||
>
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<select
|
||||
v-model="copyFromWeek"
|
||||
id="copyFromWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="w in lastWeeks"
|
||||
:value="w.value"
|
||||
:key="w.value"
|
||||
>
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||
<select
|
||||
v-model="copyToWeek"
|
||||
id="copyToWeek"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="w in nextWeeks"
|
||||
:value="w.value"
|
||||
:key="w.value"
|
||||
>
|
||||
{{ w.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||
<button
|
||||
class="btn btn-action float-end"
|
||||
@click="copyWeek"
|
||||
>
|
||||
{{ $t("copy_range") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- not directly seen, but include in a modal -->
|
||||
<edit-location ref="editLocation"></edit-location>
|
||||
<!-- not directly seen, but include in a modal -->
|
||||
<edit-location ref="editLocation"></edit-location>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
CalendarOptions,
|
||||
DatesSetArg,
|
||||
EventInput,
|
||||
CalendarOptions,
|
||||
DatesSetArg,
|
||||
EventInput,
|
||||
} from "@fullcalendar/core";
|
||||
import { reactive, computed, ref, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
@@ -182,19 +227,19 @@ import { key } from "./store";
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import frLocale from "@fullcalendar/core/locales/fr";
|
||||
import interactionPlugin, {
|
||||
DropArg,
|
||||
EventResizeDoneArg,
|
||||
DropArg,
|
||||
EventResizeDoneArg,
|
||||
} from "@fullcalendar/interaction";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import {
|
||||
EventApi,
|
||||
DateSelectArg,
|
||||
EventDropArg,
|
||||
EventClickArg,
|
||||
EventApi,
|
||||
DateSelectArg,
|
||||
EventDropArg,
|
||||
EventClickArg,
|
||||
} from "@fullcalendar/core";
|
||||
import {
|
||||
dateToISO,
|
||||
ISOToDate,
|
||||
dateToISO,
|
||||
ISOToDate,
|
||||
} from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { Location } from "../../../../../ChillMainBundle/Resources/public/types";
|
||||
@@ -217,89 +262,91 @@ const copyFromWeek = ref<string | null>(null);
|
||||
const copyToWeek = ref<string | null>(null);
|
||||
|
||||
interface Weeks {
|
||||
value: string | null;
|
||||
text: string;
|
||||
value: string | null;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const getMonday = (week: number): Date => {
|
||||
const lastMonday = new Date();
|
||||
lastMonday.setDate(
|
||||
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7
|
||||
);
|
||||
return lastMonday;
|
||||
const lastMonday = new Date();
|
||||
lastMonday.setDate(
|
||||
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
||||
);
|
||||
return lastMonday;
|
||||
};
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
};
|
||||
|
||||
const lastWeeks = computed((): Weeks[] =>
|
||||
Array.from(Array(30).keys()).map((w) => {
|
||||
const lastMonday = getMonday(15-w);
|
||||
return {
|
||||
value: dateToISO(lastMonday),
|
||||
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
})
|
||||
Array.from(Array(30).keys()).map((w) => {
|
||||
const lastMonday = getMonday(15 - w);
|
||||
return {
|
||||
value: dateToISO(lastMonday),
|
||||
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const nextWeeks = computed((): Weeks[] =>
|
||||
Array.from(Array(52).keys()).map((w) => {
|
||||
const nextMonday = getMonday(w + 1);
|
||||
return {
|
||||
value: dateToISO(nextMonday),
|
||||
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
})
|
||||
Array.from(Array(52).keys()).map((w) => {
|
||||
const nextMonday = getMonday(w + 1);
|
||||
return {
|
||||
value: dateToISO(nextMonday),
|
||||
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const baseOptions = ref<CalendarOptions>({
|
||||
locale: frLocale,
|
||||
plugins: [interactionPlugin, timeGridPlugin],
|
||||
initialView: "timeGridWeek",
|
||||
initialDate: new Date(),
|
||||
scrollTimeReset: false,
|
||||
selectable: true,
|
||||
// when the dates are changes in the fullcalendar view OR when new events are added
|
||||
datesSet: onDatesSet,
|
||||
// when a date is selected
|
||||
select: onDateSelect,
|
||||
// when a event is resized
|
||||
eventResize: onEventDropOrResize,
|
||||
// when an event is moved
|
||||
eventDrop: onEventDropOrResize,
|
||||
// when an event si clicked
|
||||
eventClick: onEventClick,
|
||||
selectMirror: false,
|
||||
editable: true,
|
||||
headerToolbar: {
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
right: "timeGridWeek,timeGridDay",
|
||||
},
|
||||
locale: frLocale,
|
||||
plugins: [interactionPlugin, timeGridPlugin],
|
||||
initialView: "timeGridWeek",
|
||||
initialDate: new Date(),
|
||||
scrollTimeReset: false,
|
||||
selectable: true,
|
||||
// when the dates are changes in the fullcalendar view OR when new events are added
|
||||
datesSet: onDatesSet,
|
||||
// when a date is selected
|
||||
select: onDateSelect,
|
||||
// when a event is resized
|
||||
eventResize: onEventDropOrResize,
|
||||
// when an event is moved
|
||||
eventDrop: onEventDropOrResize,
|
||||
// when an event si clicked
|
||||
eventClick: onEventClick,
|
||||
selectMirror: false,
|
||||
editable: true,
|
||||
headerToolbar: {
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
right: "timeGridWeek,timeGridDay",
|
||||
},
|
||||
});
|
||||
|
||||
const ranges = computed<EventInput[]>(() => {
|
||||
return store.state.calendarRanges.ranges;
|
||||
return store.state.calendarRanges.ranges;
|
||||
});
|
||||
|
||||
const locations = computed<Location[]>(() => {
|
||||
return store.state.locations.locations;
|
||||
return store.state.locations.locations;
|
||||
});
|
||||
|
||||
const pickedLocation = computed<Location | null>({
|
||||
get(): Location | null {
|
||||
return (
|
||||
store.state.locations.locationPicked ||
|
||||
store.state.locations.currentLocation
|
||||
);
|
||||
},
|
||||
set(newLocation: Location | null): void {
|
||||
store.commit("locations/setLocationPicked", newLocation, { root: true });
|
||||
},
|
||||
get(): Location | null {
|
||||
return (
|
||||
store.state.locations.locationPicked ||
|
||||
store.state.locations.currentLocation
|
||||
);
|
||||
},
|
||||
set(newLocation: Location | null): void {
|
||||
store.commit("locations/setLocationPicked", newLocation, {
|
||||
root: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -307,7 +354,7 @@ const pickedLocation = computed<Location | null>({
|
||||
* @param arg
|
||||
*/
|
||||
const eventClasses = function (arg: EventApi): object {
|
||||
return { calendarRangeItems: true };
|
||||
return { calendarRangeItems: true };
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -331,117 +378,117 @@ const sources = computed<EventSourceInput[]>(() => {
|
||||
*/
|
||||
|
||||
const calendarOptions = computed((): CalendarOptions => {
|
||||
return {
|
||||
...baseOptions.value,
|
||||
weekends: showWeekends.value,
|
||||
slotDuration: slotDuration.value,
|
||||
events: ranges.value,
|
||||
slotMinTime: slotMinTime.value,
|
||||
slotMaxTime: slotMaxTime.value,
|
||||
};
|
||||
return {
|
||||
...baseOptions.value,
|
||||
weekends: showWeekends.value,
|
||||
slotDuration: slotDuration.value,
|
||||
events: ranges.value,
|
||||
slotMinTime: slotMinTime.value,
|
||||
slotMaxTime: slotMaxTime.value,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* launched when the calendar range date change
|
||||
*/
|
||||
function onDatesSet(event: DatesSetArg): void {
|
||||
store.dispatch("fullCalendar/setCurrentDatesView", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
});
|
||||
store.dispatch("fullCalendar/setCurrentDatesView", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
});
|
||||
}
|
||||
|
||||
function onDateSelect(event: DateSelectArg): void {
|
||||
if (null === pickedLocation.value) {
|
||||
window.alert(
|
||||
"Indiquez une localisation avant de créer une période de disponibilité."
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (null === pickedLocation.value) {
|
||||
window.alert(
|
||||
"Indiquez une localisation avant de créer une période de disponibilité.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch("calendarRanges/createRange", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
location: pickedLocation.value,
|
||||
});
|
||||
store.dispatch("calendarRanges/createRange", {
|
||||
start: event.start,
|
||||
end: event.end,
|
||||
location: pickedLocation.value,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When a calendar range is deleted
|
||||
*/
|
||||
function onClickDelete(event: EventApi): void {
|
||||
if (event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
if (event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
"calendarRanges/deleteRange",
|
||||
event.extendedProps.calendarRangeId
|
||||
);
|
||||
store.dispatch(
|
||||
"calendarRanges/deleteRange",
|
||||
event.extendedProps.calendarRangeId,
|
||||
);
|
||||
}
|
||||
|
||||
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
const changedEvent = payload.event;
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
const changedEvent = payload.event;
|
||||
|
||||
store.dispatch("calendarRanges/patchRangeTime", {
|
||||
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
||||
start: payload.event.start,
|
||||
end: payload.event.end,
|
||||
});
|
||||
store.dispatch("calendarRanges/patchRangeTime", {
|
||||
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
||||
start: payload.event.start,
|
||||
end: payload.event.end,
|
||||
});
|
||||
}
|
||||
|
||||
function onEventClick(payload: EventClickArg): void {
|
||||
// @ts-ignore TS does not recognize the target. But it does exists.
|
||||
if (payload.jsEvent.target.classList.contains("delete")) {
|
||||
return;
|
||||
}
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore TS does not recognize the target. But it does exists.
|
||||
if (payload.jsEvent.target.classList.contains("delete")) {
|
||||
return;
|
||||
}
|
||||
if (payload.event.extendedProps.is !== "range") {
|
||||
return;
|
||||
}
|
||||
|
||||
editLocation.value?.startEdit(payload.event);
|
||||
editLocation.value?.startEdit(payload.event);
|
||||
}
|
||||
|
||||
function copyDay() {
|
||||
if (null === copyFrom.value || null === copyTo.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
||||
from: ISOToDate(copyFrom.value),
|
||||
to: ISOToDate(copyTo.value),
|
||||
});
|
||||
if (null === copyFrom.value || null === copyTo.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
||||
from: ISOToDate(copyFrom.value),
|
||||
to: ISOToDate(copyTo.value),
|
||||
});
|
||||
}
|
||||
|
||||
function copyWeek() {
|
||||
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
||||
fromMonday: ISOToDate(copyFromWeek.value),
|
||||
toMonday: ISOToDate(copyToWeek.value),
|
||||
});
|
||||
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
||||
return;
|
||||
}
|
||||
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
||||
fromMonday: ISOToDate(copyFromWeek.value),
|
||||
toMonday: ISOToDate(copyToWeek.value),
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
copyFromWeek.value = dateToISO(getMonday(0));
|
||||
copyToWeek.value = dateToISO(getMonday(1));
|
||||
copyFromWeek.value = dateToISO(getMonday(0));
|
||||
copyToWeek.value = dateToISO(getMonday(1));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#copy-widget {
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
background-color: white;
|
||||
z-index: 9999999999;
|
||||
padding: 0.25rem 0 0.25rem;
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
background-color: white;
|
||||
z-index: 9999999999;
|
||||
padding: 0.25rem 0 0.25rem;
|
||||
}
|
||||
div.copy-chevron {
|
||||
text-align: center;
|
||||
font-size: x-large;
|
||||
width: 2rem;
|
||||
text-align: center;
|
||||
font-size: x-large;
|
||||
width: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,51 +1,46 @@
|
||||
<template>
|
||||
<component :is="Teleport" to="body">
|
||||
<modal v-if="showModal"
|
||||
@close="closeModal">
|
||||
<component :is="Teleport" to="body">
|
||||
<modal v-if="showModal" @close="closeModal">
|
||||
<template v-slot:header>
|
||||
<h3>{{ "Modifier le lieu" }}</h3>
|
||||
</template>
|
||||
|
||||
<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">
|
||||
import Modal from "../../../../../../ChillMainBundle/Resources/public/vuejs/_components/Modal.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import {EventApi} from "@fullcalendar/core";
|
||||
import {useStore} from "vuex";
|
||||
import {key} from "../store";
|
||||
import {Location} from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { computed, ref } from "vue";
|
||||
import { EventApi } from "@fullcalendar/core";
|
||||
import { useStore } from "vuex";
|
||||
import { key } from "../store";
|
||||
import { Location } from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
//import type {Teleport} from "vue";
|
||||
|
||||
// see https://github.com/vuejs/core/issues/2855
|
||||
import {
|
||||
Teleport as teleport_,
|
||||
TeleportProps,
|
||||
VNodeProps
|
||||
} from 'vue'
|
||||
|
||||
const Teleport = teleport_ as {
|
||||
new (): {
|
||||
$props: VNodeProps & TeleportProps
|
||||
}
|
||||
}
|
||||
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
||||
|
||||
const Teleport = teleport_ as new () => {
|
||||
$props: VNodeProps & TeleportProps;
|
||||
};
|
||||
|
||||
const store = useStore(key);
|
||||
|
||||
@@ -55,34 +50,40 @@ 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;
|
||||
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('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);
|
||||
const saveAndClose = function (e: Event): void {
|
||||
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;
|
||||
}
|
||||
|
||||
defineExpose({startEdit});
|
||||
const closeModal = function (_: any): void {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
defineExpose({ startEdit });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
@@ -1,28 +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",
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
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",
|
||||
},
|
||||
};
|
||||
|
||||
export { appMessages };
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { createApp } from 'vue';
|
||||
import { _createI18n } from '../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n'
|
||||
import { appMessages } from './i18n'
|
||||
import futureStore, {key} from './store/index'
|
||||
import { createApp } from "vue";
|
||||
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||
import { appMessages } from "./i18n";
|
||||
import futureStore, { key } from "./store/index";
|
||||
|
||||
import App2 from './App2.vue';
|
||||
import {useI18n} from "vue-i18n";
|
||||
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");
|
||||
});
|
||||
|
@@ -1,50 +1,56 @@
|
||||
import 'es6-promise/auto';
|
||||
import {Store, createStore} from 'vuex';
|
||||
import {InjectionKey} from "vue";
|
||||
import me, {MeState} from './modules/me';
|
||||
import fullCalendar, {FullCalendarState} from './modules/fullcalendar';
|
||||
import calendarRanges, {CalendarRangesState} from './modules/calendarRanges';
|
||||
import calendarRemotes, {CalendarRemotesState} from './modules/calendarRemotes';
|
||||
import {whoami} from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||
import {User} from '../../../../../../ChillMainBundle/Resources/public/types';
|
||||
import locations, {LocationState} from "./modules/location";
|
||||
import calendarLocals, {CalendarLocalsState} from "./modules/calendarLocals";
|
||||
import "es6-promise/auto";
|
||||
import { Store, createStore } from "vuex";
|
||||
import { InjectionKey } from "vue";
|
||||
import me, { MeState } from "./modules/me";
|
||||
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
||||
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
||||
import calendarRemotes, {
|
||||
CalendarRemotesState,
|
||||
} from "./modules/calendarRemotes";
|
||||
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import locations, { LocationState } from "./modules/location";
|
||||
import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production';
|
||||
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: {}
|
||||
});
|
||||
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});
|
||||
});
|
||||
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 Promise.resolve(store);
|
||||
});
|
||||
};
|
||||
|
||||
export default futureStore;
|
||||
|
@@ -1,95 +1,116 @@
|
||||
import {State} from './../index';
|
||||
import {ActionContext, Module} from 'vuex';
|
||||
import {CalendarLight} from '../../../../types';
|
||||
import {fetchCalendarLocalForUser} from '../../../Calendar/api';
|
||||
import {EventInput} from '@fullcalendar/core';
|
||||
import {localsToFullCalendarEvent} from "../../../Calendar/store/utils";
|
||||
import {TransportExceptionInterface} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import {COLORS} from "../../../Calendar/const";
|
||||
import { State } from "./../index";
|
||||
import { ActionContext, Module } from "vuex";
|
||||
import { CalendarLight } from "../../../../types";
|
||||
import { fetchCalendarLocalForUser } from "../../../Calendar/api";
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import { localsToFullCalendarEvent } from "../../../Calendar/store/utils";
|
||||
import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
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 <Module<CalendarLocalsState, State>> {
|
||||
namespaced: true,
|
||||
state: (): CalendarLocalsState => ({
|
||||
locals: [],
|
||||
localsLoaded: [],
|
||||
localsIndex: new Set<string>(),
|
||||
key: 0
|
||||
}),
|
||||
getters: {
|
||||
isLocalsLoaded: (state: CalendarLocalsState) => ({start, end}: {start: Date, end: Date}): boolean => {
|
||||
for (let range of state.localsLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
||||
console.log('addLocals', ranges);
|
||||
mutations: {
|
||||
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
||||
console.log("addLocals", ranges);
|
||||
|
||||
const toAdd = ranges
|
||||
.map(cr => localsToFullCalendarEvent(cr))
|
||||
.filter(r => !state.localsIndex.has(r.id));
|
||||
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;
|
||||
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;
|
||||
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
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 Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
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.getters.isLocalsLoaded({start, end})) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
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 Promise.resolve(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
} as Module<CalendarLocalsState, State>;
|
||||
|
@@ -1,286 +1,380 @@
|
||||
import {State} from './../index';
|
||||
import {ActionContext, Module} from 'vuex';
|
||||
import {CalendarRange, CalendarRangeCreate, CalendarRangeEdit, isEventInputCalendarRange} from "../../../../types";
|
||||
import {Location} from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import {fetchCalendarRangeForUser} from '../../../Calendar/api';
|
||||
import {calendarRangeToFullCalendarEvent} from '../../../Calendar/store/utils';
|
||||
import {EventInput} from '@fullcalendar/core';
|
||||
import {makeFetch} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import { State } from "./../index";
|
||||
import { ActionContext, Module } from "vuex";
|
||||
import {
|
||||
datetimeToISO,
|
||||
dateToISO,
|
||||
ISOToDatetime
|
||||
CalendarRange,
|
||||
CalendarRangeCreate,
|
||||
CalendarRangeEdit,
|
||||
isEventInputCalendarRange,
|
||||
} from "../../../../types";
|
||||
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
|
||||
import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils";
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import {
|
||||
datetimeToISO,
|
||||
dateToISO,
|
||||
ISOToDatetime,
|
||||
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import type {EventInputCalendarRange} from "../../../../types";
|
||||
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 <Module<CalendarRangesState, State>>{
|
||||
namespaced: true,
|
||||
state: (): CalendarRangesState => ({
|
||||
ranges: [],
|
||||
rangesLoaded: [],
|
||||
rangesIndex: new Set<string>(),
|
||||
key: 0
|
||||
}),
|
||||
getters: {
|
||||
isRangeLoaded: (state: CalendarRangesState) => ({start, end}: { start: Date, end: Date }): boolean => {
|
||||
for (let range of state.rangesLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
},
|
||||
getRangesOnDate: (state: CalendarRangesState) => (date: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
const dateStr = <string>dateToISO(date);
|
||||
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));
|
||||
|
||||
for (let range of state.ranges) {
|
||||
if (isEventInputCalendarRange(range)
|
||||
&& range.start.startsWith(dateStr)
|
||||
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 })[],
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
const toAdd = externalEvents.filter(
|
||||
(r) => !state.rangesIndex.has(r.id),
|
||||
);
|
||||
|
||||
return founds;
|
||||
},
|
||||
getRangesOnWeek: (state: CalendarRangesState) => (mondayDate: Date): EventInputCalendarRange[] => {
|
||||
const founds = [];
|
||||
for (let d of Array.from(Array(7).keys())) {
|
||||
const dateOfWeek = new Date(mondayDate);
|
||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||
const dateStr = <string>dateToISO(dateOfWeek);
|
||||
for (let range of state.ranges) {
|
||||
if (isEventInputCalendarRange(range)
|
||||
&& range.start.startsWith(dateStr)
|
||||
) {
|
||||
founds.push(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
addExternals(state, 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, 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, {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"
|
||||
toAdd.forEach((r) => {
|
||||
state.rangesIndex.add(r.id);
|
||||
state.ranges.push(r);
|
||||
});
|
||||
state.key = state.key + toAdd.length;
|
||||
},
|
||||
startDate: {
|
||||
datetime: datetimeToISO(start),
|
||||
addLoaded(
|
||||
state: CalendarRangesState,
|
||||
payload: { start: Date; end: Date },
|
||||
) {
|
||||
state.rangesLoaded.push({
|
||||
start: payload.start.getTime(),
|
||||
end: payload.end.getTime(),
|
||||
});
|
||||
},
|
||||
endDate: {
|
||||
datetime: datetimeToISO(end)
|
||||
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;
|
||||
},
|
||||
location: {
|
||||
id: location.id,
|
||||
type: "location"
|
||||
}
|
||||
} as CalendarRangeCreate;
|
||||
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
||||
const found = state.ranges.find(
|
||||
(r) =>
|
||||
r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||
);
|
||||
|
||||
return makeFetch<CalendarRangeCreate, CalendarRange>('POST', url, body)
|
||||
.then((newRange) => {
|
||||
if (found !== undefined) {
|
||||
state.ranges = state.ranges.filter(
|
||||
(r) =>
|
||||
!(
|
||||
r.calendarRangeId === calendarRangeId &&
|
||||
r.is === "range"
|
||||
),
|
||||
);
|
||||
|
||||
ctx.commit('addRange', newRange);
|
||||
if (typeof found.id === "string") {
|
||||
// should always be true
|
||||
state.rangesIndex.delete(found.id);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
|
||||
throw error;
|
||||
})
|
||||
},
|
||||
deleteRange(ctx, 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)
|
||||
state.key = state.key + 1;
|
||||
}
|
||||
},
|
||||
endDate: {
|
||||
datetime: datetimeToISO(end)
|
||||
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;
|
||||
},
|
||||
} 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"
|
||||
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);
|
||||
});
|
||||
},
|
||||
} as CalendarRangeEdit;
|
||||
createRange(
|
||||
ctx: Context,
|
||||
{
|
||||
start,
|
||||
end,
|
||||
location,
|
||||
}: { start: Date; end: Date; location: Location },
|
||||
): Promise<null> {
|
||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||
|
||||
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);
|
||||
})
|
||||
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));
|
||||
},
|
||||
},
|
||||
copyFromDayToAnotherDay(ctx, {from, to}: {from: Date, to: Date}): Promise<null> {
|
||||
const rangesToCopy: EventInputCalendarRange[] = ctx.getters['getRangesOnDate'](from);
|
||||
const promises = [];
|
||||
|
||||
for (let r of rangesToCopy) {
|
||||
let start = new Date(<Date>ISOToDatetime(r.start));
|
||||
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||
let end = new Date(<Date>ISOToDatetime(r.end));
|
||||
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||
let 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, {fromMonday, toMonday}: {fromMonday: Date, toMonday: Date}): Promise<null> {
|
||||
|
||||
const rangesToCopy: EventInputCalendarRange[] = ctx.getters['getRangesOnWeek'](fromMonday);
|
||||
const promises = [];
|
||||
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
||||
for (let r of rangesToCopy) {
|
||||
let start = new Date(<Date>ISOToDatetime(r.start));
|
||||
let end = new Date(<Date>ISOToDatetime(r.end));
|
||||
start.setTime(start.getTime() + diffTime);
|
||||
end.setTime(end.getTime() + diffTime);
|
||||
let 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>;
|
||||
|
@@ -1,95 +1,116 @@
|
||||
import {State} from './../index';
|
||||
import {ActionContext, Module} from 'vuex';
|
||||
import {CalendarRemote} from '../../../../types';
|
||||
import {fetchCalendarRemoteForUser} from '../../../Calendar/api';
|
||||
import {EventInput} from '@fullcalendar/core';
|
||||
import {remoteToFullCalendarEvent} from "../../../Calendar/store/utils";
|
||||
import {TransportExceptionInterface} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import {COLORS} from "../../../Calendar/const";
|
||||
import { State } from "./../index";
|
||||
import { ActionContext, Module } from "vuex";
|
||||
import { CalendarRemote } from "../../../../types";
|
||||
import { fetchCalendarRemoteForUser } from "../../../Calendar/api";
|
||||
import { EventInput } from "@fullcalendar/core";
|
||||
import { remoteToFullCalendarEvent } from "../../../Calendar/store/utils";
|
||||
import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
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 <Module<CalendarRemotesState, State>> {
|
||||
namespaced: true,
|
||||
state: (): CalendarRemotesState => ({
|
||||
remotes: [],
|
||||
remotesLoaded: [],
|
||||
remotesIndex: new Set<string>(),
|
||||
key: 0
|
||||
}),
|
||||
getters: {
|
||||
isRemotesLoaded: (state: CalendarRemotesState) => ({start, end}: {start: Date, end: Date}): boolean => {
|
||||
for (let range of state.remotesLoaded) {
|
||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
||||
console.log('addRemotes', ranges);
|
||||
mutations: {
|
||||
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
||||
console.log("addRemotes", ranges);
|
||||
|
||||
const toAdd = ranges
|
||||
.map(cr => remoteToFullCalendarEvent(cr))
|
||||
.filter(r => !state.remotesIndex.has(r.id));
|
||||
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;
|
||||
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;
|
||||
|
||||
if (ctx.rootGetters["me/getMe"] === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
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 Promise.resolve(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
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.getters.isRemotesLoaded({start, end})) {
|
||||
return Promise.resolve(ctx.getters.getRangeSource);
|
||||
}
|
||||
|
||||
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 Promise.resolve(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
} as Module<CalendarRemotesState, State>;
|
||||
|
@@ -1,56 +1,78 @@
|
||||
import {State} from './../index';
|
||||
import {ActionContext} from 'vuex';
|
||||
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,
|
||||
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;
|
||||
},
|
||||
},
|
||||
key: 0,
|
||||
}),
|
||||
mutations: {
|
||||
setCurrentDatesView: function(state: FullCalendarState, payload: {start: Date, end: Date}): void {
|
||||
state.currentView.start = payload.start;
|
||||
state.currentView.end = payload.end;
|
||||
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);
|
||||
}
|
||||
},
|
||||
},
|
||||
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);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user