mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-10 16:55:00 +00:00
Compare commits
102 Commits
v3.4.2
...
user_edit_
Author | SHA1 | Date | |
---|---|---|---|
527285bb13 | |||
19fa308c06 | |||
1b831bc424 | |||
573118e514 | |||
0cabf5654a | |||
cfb547d55f | |||
a915c35026 | |||
018f8aef5c | |||
de6385ba21 | |||
edb51dd3cd | |||
c379bccad4 | |||
bd9ad8a569 | |||
cb5fd2b69d | |||
feebcf6662
|
|||
6de4861b98
|
|||
b4a1e824ac
|
|||
d87cf925e2
|
|||
ce3cce7b95 | |||
6c97654e5e
|
|||
0787e61c22
|
|||
73bcfb82b7
|
|||
812e4047d0
|
|||
999ac3af2b
|
|||
0c628c39db
|
|||
c65f1d495d
|
|||
83f7086bb0
|
|||
c1e449f48e
|
|||
1f6de3cb11
|
|||
3a2548ed89
|
|||
d7652658f2
|
|||
67b5bc6dba
|
|||
e25c1e1816
|
|||
282b7f7fbb | |||
ab311eaecb
|
|||
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 | |||
559901e528 | |||
05bc69fd33 | |||
49da62d364 | |||
3af7824d01
|
|||
033053c437
|
|||
633bb00154
|
|||
f5c1b5cf8a | |||
ccd71da4e4
|
|||
1eadb3bbdb
|
|||
|
0bb5a79cae
|
||
|
bd3198e42b
|
||
|
96dfddc55f
|
||
|
da37a3db5f
|
||
|
c2882b1079
|
||
b9e515f4e6 | |||
|
df2ea7e1ba
|
||
|
d59cda9cc4
|
||
54e10cacd3 | |||
3ca126804b | |||
40d733c290 | |||
7a98bb5a06
|
|||
2ce8f540fe
|
|||
6c4d8990cc | |||
351e9c3fcc
|
|||
1b65cac1df
|
|||
aa0785fc71 | |||
610227815a | |||
9d9f062417 | |||
0454e5d758 | |||
2d6d2a1f58 | |||
c971e34675 | |||
be2a119163 | |||
22ecb11227 | |||
dd854ea339 | |||
f05c25853c | |||
90798b12e5 | |||
f91f5ce27e | |||
96f73b419d | |||
273f91fd00 | |||
27ecae4486 | |||
f90f1c7ef8 |
4
.changes/v3.4.3.md
Normal file
4
.changes/v3.4.3.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## v3.4.3 - 2024-12-05
|
||||||
|
### Fixed
|
||||||
|
* Remove the "not null" constraint on person supplementary phones
|
||||||
|
* Remove doctrine annotation that prevent from adding documents to activities
|
6
.changes/v3.5.0.md
Normal file
6
.changes/v3.5.0.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## 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
|
||||||
|
### Fixed
|
||||||
|
* Wrap the signature's change state into a transaction, to avoid race conditions
|
||||||
|
* Fix display of gender label
|
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.
|
2386
.eslint-baseline.json
Normal file
2386
.eslint-baseline.json
Normal file
File diff suppressed because it is too large
Load Diff
5
.gitignore
vendored
5
.gitignore
vendored
@@ -51,3 +51,8 @@ phpstan.neon
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
###< symfony/webpack-encore-bundle ###
|
###< 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:
|
paths:
|
||||||
- /vendor/
|
- /vendor/
|
||||||
- .cache
|
- .cache
|
||||||
|
- node_modules/
|
||||||
|
|
||||||
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
|
# 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.
|
# See http://docs.gitlab.com/ee/ci/services/README.html for examples.
|
||||||
@@ -103,6 +104,32 @@ rector_tests:
|
|||||||
paths:
|
paths:
|
||||||
- vendor/
|
- 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 "**/*.{js,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:
|
unit_tests:
|
||||||
stage: Tests
|
stage: Tests
|
||||||
image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82
|
image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82
|
||||||
|
35
CHANGELOG.md
35
CHANGELOG.md
@@ -6,6 +6,41 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
|||||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
### Fixed
|
||||||
|
* Wrap the signature's change state into a transaction, to avoid race conditions
|
||||||
|
* Fix display of gender label
|
||||||
|
|
||||||
|
## v3.4.3 - 2024-12-05
|
||||||
|
### Fixed
|
||||||
|
* Remove the "not null" constraint on person supplementary phones
|
||||||
|
* Remove doctrine annotation that prevent from adding documents to activities
|
||||||
|
|
||||||
## v3.4.2 - 2024-12-05
|
## v3.4.2 - 2024-12-05
|
||||||
### Fixed
|
### Fixed
|
||||||
* ([#329](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/329)) Fix the serialization of gender for the generation of documents
|
* ([#329](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/329)) Fix the serialization of gender for the generation of documents
|
||||||
|
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
|
## Translations
|
||||||
|
|
||||||
|
|
||||||
Par bundle, toutes les traductions des pages twig se trouvent dans un seul fichier `translations/messages.fr.yaml`.
|
Par bundle, toutes les traductions des pages twig se trouvent dans un seul fichier `translations/messages.fr.yaml`.
|
||||||
|
|
||||||
## Emplacement des fichiers
|
## Emplacement des fichiers
|
||||||
@@ -142,7 +141,6 @@ ul.record_actions {
|
|||||||
|
|
||||||
## Render box
|
## Render box
|
||||||
|
|
||||||
|
|
||||||
## URL
|
## URL
|
||||||
|
|
||||||
### Nommage des routes
|
### 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`
|
`/{_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
|
## 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]
|
> 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
|
#### En vue
|
||||||
|
|
||||||
Il existe systématiquement une "box" équivalente en vue.
|
Il existe systématiquement une "box" équivalente en vue.
|
@@ -13,9 +13,11 @@
|
|||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-openssl": "*",
|
"ext-openssl": "*",
|
||||||
"ext-redis": "*",
|
"ext-redis": "*",
|
||||||
|
"ext-zlib": "*",
|
||||||
"champs-libres/wopi-bundle": "dev-master@dev",
|
"champs-libres/wopi-bundle": "dev-master@dev",
|
||||||
"champs-libres/wopi-lib": "dev-master@dev",
|
"champs-libres/wopi-lib": "dev-master@dev",
|
||||||
"doctrine/doctrine-bundle": "^2.1",
|
"doctrine/doctrine-bundle": "^2.1",
|
||||||
|
"doctrine/data-fixtures": "^1.8",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||||
"doctrine/orm": "^2.13.0",
|
"doctrine/orm": "^2.13.0",
|
||||||
"erusev/parsedown": "^1.7",
|
"erusev/parsedown": "^1.7",
|
||||||
@@ -85,6 +87,7 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/doctrine-fixtures-bundle": "^3.3",
|
"doctrine/doctrine-fixtures-bundle": "^3.3",
|
||||||
"fakerphp/faker": "^1.13",
|
"fakerphp/faker": "^1.13",
|
||||||
|
"friendsofphp/php-cs-fixer": "3.65.0",
|
||||||
"jangregor/phpstan-prophecy": "^1.0",
|
"jangregor/phpstan-prophecy": "^1.0",
|
||||||
"nelmio/alice": "^3.8",
|
"nelmio/alice": "^3.8",
|
||||||
"nikic/php-parser": "^4.15",
|
"nikic/php-parser": "^4.15",
|
||||||
@@ -95,6 +98,7 @@
|
|||||||
"phpstan/phpstan-strict-rules": "^1.0",
|
"phpstan/phpstan-strict-rules": "^1.0",
|
||||||
"phpunit/phpunit": "^10.5.24",
|
"phpunit/phpunit": "^10.5.24",
|
||||||
"rector/rector": "^1.1.0",
|
"rector/rector": "^1.1.0",
|
||||||
|
"symfony/amqp-messenger": "^5.4.45",
|
||||||
"symfony/debug-bundle": "^5.4",
|
"symfony/debug-bundle": "^5.4",
|
||||||
"symfony/dotenv": "^5.4",
|
"symfony/dotenv": "^5.4",
|
||||||
"symfony/flex": "^2.4",
|
"symfony/flex": "^2.4",
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
chill_doc_store:
|
chill_doc_store:
|
||||||
|
use_driver: openstack
|
||||||
|
local_storage:
|
||||||
|
storage_path: '%kernel.project_dir%/var/storage'
|
||||||
openstack:
|
openstack:
|
||||||
temp_url:
|
temp_url:
|
||||||
temp_url_key: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_KEY)%' # Required
|
temp_url_key: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_KEY)%' # Required
|
||||||
|
11
config/packages/chill_workflow_signature_documents.yaml
Normal file
11
config/packages/chill_workflow_signature_documents.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
chill_main:
|
||||||
|
workflow_signature:
|
||||||
|
base_signer:
|
||||||
|
document_kinds:
|
||||||
|
- { key: id_card, labels: [ { lang: fr, label: "Carte d'identité" } ] }
|
||||||
|
- { key: passport, labels: [ { lang: fr, label: "Passeport" } ] }
|
||||||
|
- { key: drivers_license, labels: [ { lang: fr, label: "Permis de conduire" } ] }
|
||||||
|
- { key: visa_long_stay, labels: [ { lang: fr, label: "Visa de long séjour" } ] }
|
||||||
|
- { key: resident_permit, labels: [ { lang: fr, label: "Carte de séjour" } ] }
|
||||||
|
- { key: residency_card, labels: [ { lang: fr, label: "Carte de résident" } ] }
|
||||||
|
- { key: provisionary_residency_permit, labels: [ { lang: fr, label: "Autorisation provisoire de séjour" } ] }
|
69
docs/source/development/es-lint.rst
Normal file
69
docs/source/development/es-lint.rst
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
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>
|
Exports <exports.rst>
|
||||||
Embeddable comments <embeddable-comments.rst>
|
Embeddable comments <embeddable-comments.rst>
|
||||||
Run tests <run-tests.rst>
|
Run tests <run-tests.rst>
|
||||||
|
ESLint <es-lint.rst>
|
||||||
Useful snippets <useful-snippets.rst>
|
Useful snippets <useful-snippets.rst>
|
||||||
manual/index.rst
|
manual/index.rst
|
||||||
Assets <assets.rst>
|
Assets <assets.rst>
|
||||||
|
@@ -1,19 +1,18 @@
|
|||||||
import { ShowHide } from 'ShowHide/show_hide.js';
|
import { ShowHide } from "ShowHide/show_hide.js";
|
||||||
|
|
||||||
var
|
var div_accompagnement = document.getElementById("form_accompagnement"),
|
||||||
div_accompagnement = document.getElementById("form_accompagnement"),
|
div_accompagnement_comment = document.getElementById(
|
||||||
div_accompagnement_comment = document.getElementById("form_accompagnement_comment"),
|
"form_accompagnement_comment",
|
||||||
|
),
|
||||||
div_caf_id = document.getElementById("cafId"),
|
div_caf_id = document.getElementById("cafId"),
|
||||||
div_caf_inscription_date = document.getElementById("cafInscriptionDate"),
|
div_caf_inscription_date = document.getElementById("cafInscriptionDate");
|
||||||
;
|
|
||||||
|
|
||||||
// let show/hide the div_accompagnement_comment if the input with value `'autre'` is checked
|
// let show/hide the div_accompagnement_comment if the input with value `'autre'` is checked
|
||||||
new ShowHide({
|
new ShowHide({
|
||||||
"froms": [div_accompagnement],
|
froms: [div_accompagnement],
|
||||||
"test": function(froms, event) {
|
test: function (froms, event) {
|
||||||
for (let el of froms.values()) {
|
for (let el of froms.values()) {
|
||||||
for (let input of el.querySelectorAll('input').values()) {
|
for (let input of el.querySelectorAll("input").values()) {
|
||||||
if (input.value === 'autre') {
|
if (input.value === "autre") {
|
||||||
return input.checked;
|
return input.checked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,19 +20,18 @@ new ShowHide({
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
"container": [div_accompagnement_comment]
|
container: [div_accompagnement_comment],
|
||||||
});
|
});
|
||||||
|
|
||||||
// let show the date input only if the the id is filled
|
// let show the date input only if the the id is filled
|
||||||
new ShowHide({
|
new ShowHide({
|
||||||
froms: [ div_caf_id ],
|
froms: [div_caf_id],
|
||||||
test: function(froms, event) {
|
test: function (froms, event) {
|
||||||
for (let el of froms.values()) {
|
for (let el of froms.values()) {
|
||||||
return el.querySelector("input").value !== "";
|
return el.querySelector("input").value !== "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
container: [ div_caf_inscription_date ],
|
container: [div_caf_inscription_date],
|
||||||
// using this option, we use the event `input` instead of `change`
|
// using this option, we use the event `input` instead of `change`
|
||||||
event_name: 'input'
|
event_name: "input",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@@ -323,6 +323,7 @@ Going further
|
|||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
prod.rst
|
prod.rst
|
||||||
|
document-storage.rst
|
||||||
load-addresses.rst
|
load-addresses.rst
|
||||||
prod-calendar-sms-sending.rst
|
prod-calendar-sms-sending.rst
|
||||||
msgraph-configure.rst
|
msgraph-configure.rst
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
15
package.json
15
package.json
@@ -13,26 +13,36 @@
|
|||||||
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
|
"@ckeditor/ckeditor5-markdown-gfm": "^41.4.2",
|
||||||
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
|
"@ckeditor/ckeditor5-theme-lark": "^41.4.2",
|
||||||
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
"@ckeditor/ckeditor5-vue": "^5.1.0",
|
||||||
|
"@eslint/js": "^9.14.0",
|
||||||
|
"@luminateone/eslint-baseline": "^1.0.9",
|
||||||
"@symfony/webpack-encore": "^4.1.0",
|
"@symfony/webpack-encore": "^4.1.0",
|
||||||
"@tsconfig/node14": "^1.0.1",
|
"@tsconfig/node14": "^1.0.1",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@types/eslint__js": "^8.42.3",
|
||||||
|
"@typescript-eslint/parser": "^8.12.2",
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
"bootstrap": "5.2.3",
|
"bootstrap": "5.2.3",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
"dompurify": "^3.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",
|
"fork-awesome": "^1.1.7",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"marked": "^12.0.1",
|
"marked": "^12.0.1",
|
||||||
"node-sass": "^8.0.0",
|
"node-sass": "^8.0.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"postcss-loader": "^7.0.2",
|
"postcss-loader": "^7.0.2",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"sass-loader": "^14.0.0",
|
"sass-loader": "^14.0.0",
|
||||||
"select2": "^4.0.13",
|
"select2": "^4.0.13",
|
||||||
"select2-bootstrap-theme": "0.1.0-beta.10",
|
"select2-bootstrap-theme": "0.1.0-beta.10",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"ts-loader": "^9.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",
|
"vue-loader": "^17.0.0",
|
||||||
"webpack": "^5.75.0",
|
"webpack": "^5.75.0",
|
||||||
"webpack-cli": "^5.0.1"
|
"webpack-cli": "^5.0.1"
|
||||||
@@ -69,7 +79,8 @@
|
|||||||
"dev-server": "encore dev-server",
|
"dev-server": "encore dev-server",
|
||||||
"dev": "encore dev",
|
"dev": "encore dev",
|
||||||
"watch": "encore dev --watch",
|
"watch": "encore dev --watch",
|
||||||
"build": "encore production --progress"
|
"build": "encore production --progress",
|
||||||
|
"eslint": "npx eslint-baseline \"**/*.{js,ts,vue}\""
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
@@ -84,11 +84,6 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
|
|||||||
*/
|
*/
|
||||||
#[Assert\Valid(traverse: true)]
|
#[Assert\Valid(traverse: true)]
|
||||||
#[ORM\ManyToMany(targetEntity: StoredObject::class, cascade: ['persist'])]
|
#[ORM\ManyToMany(targetEntity: StoredObject::class, cascade: ['persist'])]
|
||||||
#[ORM\JoinTable(
|
|
||||||
name: 'activity_storedobject',
|
|
||||||
joinColumns: new ORM\JoinColumn(name: 'activity_id', referencedColumnName: 'id', nullable: false),
|
|
||||||
inverseJoinColumns: new ORM\InverseJoinColumn(name: 'storedobject_id', referencedColumnName: 'id', unique: true, nullable: false)
|
|
||||||
)]
|
|
||||||
private Collection $documents;
|
private Collection $documents;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TIME_MUTABLE, nullable: true)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TIME_MUTABLE, nullable: true)]
|
||||||
|
@@ -55,6 +55,10 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
|||||||
.' AND '
|
.' AND '
|
||||||
.'(person_person_having_activity.id = person.id OR person MEMBER OF activity_person_having_activity.persons)');
|
.'(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']) {
|
if (isset($data['reasons']) && [] !== $data['reasons']) {
|
||||||
// add clause activity reason
|
// add clause activity reason
|
||||||
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
|
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
|
||||||
|
@@ -1 +1 @@
|
|||||||
require('./chillactivity.scss');
|
require("./chillactivity.scss");
|
||||||
|
@@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<concerned-groups v-if="hasPerson"></concerned-groups>
|
<concerned-groups v-if="hasPerson" />
|
||||||
<social-issues-acc v-if="hasSocialIssues"></social-issues-acc>
|
<social-issues-acc v-if="hasSocialIssues" />
|
||||||
<location v-if="hasLocation"></location>
|
<location v-if="hasLocation" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ConcernedGroups from './components/ConcernedGroups.vue';
|
import ConcernedGroups from "./components/ConcernedGroups.vue";
|
||||||
import SocialIssuesAcc from './components/SocialIssuesAcc.vue';
|
import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
|
||||||
import Location from './components/Location.vue';
|
import Location from "./components/Location.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
props: ['hasSocialIssues', 'hasLocation', 'hasPerson'],
|
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
||||||
components: {
|
components: {
|
||||||
ConcernedGroups,
|
ConcernedGroups,
|
||||||
SocialIssuesAcc,
|
SocialIssuesAcc,
|
||||||
Location
|
Location,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,37 +1,38 @@
|
|||||||
import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.js';
|
import { getSocialIssues } from "ChillPersonAssets/vuejs/AccompanyingCourse/api.js";
|
||||||
import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods';
|
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load socialActions by socialIssue (id)
|
* Load socialActions by socialIssue (id)
|
||||||
*/
|
*/
|
||||||
const getSocialActionByIssue = (id) => {
|
const getSocialActionByIssue = (id) => {
|
||||||
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
|
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
|
||||||
return fetch(url)
|
return fetch(url).then((response) => {
|
||||||
.then(response => {
|
if (response.ok) {
|
||||||
if (response.ok) { return response.json(); }
|
return response.json();
|
||||||
throw Error('Error with request resource response');
|
}
|
||||||
|
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 getLocationTypes = () => fetchResults("/api/1.0/main/location-type.json");
|
||||||
|
|
||||||
const getUserCurrentLocation =
|
const getUserCurrentLocation = () =>
|
||||||
() => fetch('/api/1.0/main/user-current-location.json')
|
fetch("/api/1.0/main/user-current-location.json").then((response) => {
|
||||||
.then(response => {
|
if (response.ok) {
|
||||||
if (response.ok) { return response.json(); }
|
return response.json();
|
||||||
throw Error('Error with request resource response');
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load Location Type by defaultFor
|
* Load Location Type by defaultFor
|
||||||
* @param {string} entity - can be "person" or "thirdparty"
|
* @param {string} entity - can be "person" or "thirdparty"
|
||||||
*/
|
*/
|
||||||
const getLocationTypeByDefaultFor = (entity) => {
|
const getLocationTypeByDefaultFor = (entity) => {
|
||||||
return getLocationTypes().then(results =>
|
return getLocationTypes().then(
|
||||||
results.filter(t => t.defaultFor === entity)[0]
|
(results) => results.filter((t) => t.defaultFor === entity)[0],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,15 +46,16 @@ const getLocationTypeByDefaultFor = (entity) => {
|
|||||||
const postLocation = (body) => {
|
const postLocation = (body) => {
|
||||||
const url = `/api/1.0/main/location.json`;
|
const url = `/api/1.0/main/location.json`;
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json;charset=utf-8'
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body),
|
||||||
})
|
}).then((response) => {
|
||||||
.then(response => {
|
if (response.ok) {
|
||||||
if (response.ok) { return response.json(); }
|
return response.json();
|
||||||
throw Error('Error with request resource response');
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,5 +66,5 @@ export {
|
|||||||
getLocationTypes,
|
getLocationTypes,
|
||||||
getLocationTypeByDefaultFor,
|
getLocationTypeByDefaultFor,
|
||||||
postLocation,
|
postLocation,
|
||||||
getUserCurrentLocation
|
getUserCurrentLocation,
|
||||||
};
|
};
|
||||||
|
@@ -1,19 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||||
|
|
||||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||||
<persons-bloc
|
<persons-bloc
|
||||||
v-for="bloc in contextPersonsBlocs"
|
v-for="bloc in contextPersonsBlocs"
|
||||||
v-bind:key="bloc.key"
|
:key="bloc.key"
|
||||||
v-bind:bloc="bloc"
|
:bloc="bloc"
|
||||||
v-bind:blocWidth="getBlocWidth"
|
:bloc-width="getBlocWidth"
|
||||||
v-bind:setPersonsInBloc="setPersonsInBloc">
|
:set-persons-in-bloc="setPersonsInBloc"
|
||||||
</persons-bloc>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0">
|
<div
|
||||||
|
v-if="
|
||||||
|
getContext === 'accompanyingCourse' &&
|
||||||
|
suggestedEntities.length > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
<ul class="list-suggest add-items inline">
|
<ul class="list-suggest add-items inline">
|
||||||
<li v-for="(p, i) in suggestedEntities" @click="addSuggestedEntity(p)" :key="`suggestedEntities-${i}`">
|
<li
|
||||||
<person-text v-if="p.type === 'person'" :person="p"></person-text>
|
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>
|
<span v-else>{{ p.text }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -22,114 +30,126 @@
|
|||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li class="add-persons">
|
<li class="add-persons">
|
||||||
<add-persons
|
<add-persons
|
||||||
buttonTitle="activity.add_persons"
|
button-title="activity.add_persons"
|
||||||
modalTitle="activity.add_persons"
|
modal-title="activity.add_persons"
|
||||||
v-bind:key="addPersons.key"
|
:key="addPersons.key"
|
||||||
v-bind:options="addPersonsOptions"
|
:options="addPersonsOptions"
|
||||||
@addNewPersons="addNewPersons"
|
@add-new-persons="addNewPersons"
|
||||||
ref="addPersons">
|
ref="addPersons"
|
||||||
</add-persons>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState, mapGetters } from "vuex";
|
||||||
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
||||||
import PersonsBloc from './ConcernedGroups/PersonsBloc.vue';
|
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
||||||
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
|
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ConcernedGroups",
|
name: "ConcernedGroups",
|
||||||
components: {
|
components: {
|
||||||
AddPersons,
|
AddPersons,
|
||||||
PersonsBloc,
|
PersonsBloc,
|
||||||
PersonText
|
PersonText,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
personsBlocs: [
|
personsBlocs: [
|
||||||
{ key: 'persons',
|
{
|
||||||
title: 'activity.bloc_persons',
|
key: "persons",
|
||||||
|
title: "activity.bloc_persons",
|
||||||
persons: [],
|
persons: [],
|
||||||
included: false
|
included: false,
|
||||||
},
|
},
|
||||||
{ key: 'personsAssociated',
|
{
|
||||||
title: 'activity.bloc_persons_associated',
|
key: "personsAssociated",
|
||||||
|
title: "activity.bloc_persons_associated",
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
|
included: window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0
|
||||||
|
: true,
|
||||||
},
|
},
|
||||||
{ key: 'personsNotAssociated',
|
{
|
||||||
title: 'activity.bloc_persons_not_associated',
|
key: "personsNotAssociated",
|
||||||
|
title: "activity.bloc_persons_not_associated",
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity ? window.activity.activityType.personsVisible !== 0 : true
|
included: window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0
|
||||||
|
: true,
|
||||||
},
|
},
|
||||||
{ key: 'thirdparty',
|
{
|
||||||
title: 'activity.bloc_thirdparty',
|
key: "thirdparty",
|
||||||
|
title: "activity.bloc_thirdparty",
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity ? window.activity.activityType.thirdPartiesVisible !== 0 : true
|
included: window.activity
|
||||||
|
? window.activity.activityType.thirdPartiesVisible !== 0
|
||||||
|
: true,
|
||||||
},
|
},
|
||||||
{ key: 'users',
|
{
|
||||||
title: 'activity.bloc_users',
|
key: "users",
|
||||||
|
title: "activity.bloc_users",
|
||||||
persons: [],
|
persons: [],
|
||||||
included: window.activity ? window.activity.activityType.usersVisible !== 0 : true
|
included: window.activity
|
||||||
|
? window.activity.activityType.usersVisible !== 0
|
||||||
|
: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
addPersons: {
|
addPersons: {
|
||||||
key: 'activity'
|
key: "activity",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isComponentVisible() {
|
isComponentVisible() {
|
||||||
return window.activity
|
return window.activity
|
||||||
? (window.activity.activityType.personsVisible !== 0 || window.activity.activityType.thirdPartiesVisible !== 0 || window.activity.activityType.usersVisible !== 0)
|
? window.activity.activityType.personsVisible !== 0 ||
|
||||||
: true
|
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
||||||
|
window.activity.activityType.usersVisible !== 0
|
||||||
|
: true;
|
||||||
},
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
persons: state => state.activity.persons,
|
persons: (state) => state.activity.persons,
|
||||||
thirdParties: state => state.activity.thirdParties,
|
thirdParties: (state) => state.activity.thirdParties,
|
||||||
users: state => state.activity.users,
|
users: (state) => state.activity.users,
|
||||||
accompanyingCourse: state => state.activity.accompanyingPeriod
|
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
||||||
}),
|
}),
|
||||||
...mapGetters([
|
...mapGetters(["suggestedEntities"]),
|
||||||
'suggestedEntities'
|
|
||||||
]),
|
|
||||||
getContext() {
|
getContext() {
|
||||||
return (this.accompanyingCourse) ? "accompanyingCourse" : "person";
|
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
||||||
},
|
},
|
||||||
contextPersonsBlocs() {
|
contextPersonsBlocs() {
|
||||||
return this.personsBlocs.filter(bloc => bloc.included !== false);
|
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
||||||
},
|
},
|
||||||
addPersonsOptions() {
|
addPersonsOptions() {
|
||||||
let optionsType = [];
|
let optionsType = [];
|
||||||
if (window.activity) {
|
if (window.activity) {
|
||||||
if (window.activity.activityType.personsVisible !== 0) {
|
if (window.activity.activityType.personsVisible !== 0) {
|
||||||
optionsType.push('person')
|
optionsType.push("person");
|
||||||
}
|
}
|
||||||
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
||||||
optionsType.push('thirdparty')
|
optionsType.push("thirdparty");
|
||||||
}
|
}
|
||||||
if (window.activity.activityType.usersVisible !== 0) {
|
if (window.activity.activityType.usersVisible !== 0) {
|
||||||
optionsType.push('user')
|
optionsType.push("user");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
optionsType = ['person', 'thirdparty', 'user'];
|
optionsType = ["person", "thirdparty", "user"];
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: optionsType,
|
type: optionsType,
|
||||||
priority: null,
|
priority: null,
|
||||||
uniq: false,
|
uniq: false,
|
||||||
button: {
|
button: {
|
||||||
size: 'btn-sm'
|
size: "btn-sm",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
getBlocWidth() {
|
getBlocWidth() {
|
||||||
return Math.round(100/(this.contextPersonsBlocs.length)) + '%';
|
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.setPersonsInBloc();
|
this.setPersonsInBloc();
|
||||||
@@ -140,31 +160,31 @@ export default {
|
|||||||
if (this.accompanyingCourse) {
|
if (this.accompanyingCourse) {
|
||||||
groups = this.splitPersonsInGroups();
|
groups = this.splitPersonsInGroups();
|
||||||
}
|
}
|
||||||
this.personsBlocs.forEach(bloc => {
|
this.personsBlocs.forEach((bloc) => {
|
||||||
if (this.accompanyingCourse) {
|
if (this.accompanyingCourse) {
|
||||||
switch (bloc.key) {
|
switch (bloc.key) {
|
||||||
case 'personsAssociated':
|
case "personsAssociated":
|
||||||
bloc.persons = groups.personsAssociated;
|
bloc.persons = groups.personsAssociated;
|
||||||
bloc.included = true;
|
bloc.included = true;
|
||||||
break;
|
break;
|
||||||
case 'personsNotAssociated':
|
case "personsNotAssociated":
|
||||||
bloc.persons = groups.personsNotAssociated;
|
bloc.persons = groups.personsNotAssociated;
|
||||||
bloc.included = true;
|
bloc.included = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (bloc.key) {
|
switch (bloc.key) {
|
||||||
case 'persons':
|
case "persons":
|
||||||
bloc.persons = this.persons;
|
bloc.persons = this.persons;
|
||||||
bloc.included = true;
|
bloc.included = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (bloc.key) {
|
switch (bloc.key) {
|
||||||
case 'thirdparty':
|
case "thirdparty":
|
||||||
bloc.persons = this.thirdParties;
|
bloc.persons = this.thirdParties;
|
||||||
break;
|
break;
|
||||||
case 'users':
|
case "users":
|
||||||
bloc.persons = this.users;
|
bloc.persons = this.users;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -174,23 +194,25 @@ export default {
|
|||||||
let personsAssociated = [];
|
let personsAssociated = [];
|
||||||
let personsNotAssociated = this.persons;
|
let personsNotAssociated = this.persons;
|
||||||
let participations = this.getCourseParticipations();
|
let participations = this.getCourseParticipations();
|
||||||
this.persons.forEach(person => {
|
this.persons.forEach((person) => {
|
||||||
participations.forEach(participation => {
|
participations.forEach((participation) => {
|
||||||
if (person.id === participation.id) {
|
if (person.id === participation.id) {
|
||||||
//console.log(person.id);
|
//console.log(person.id);
|
||||||
personsAssociated.push(person);
|
personsAssociated.push(person);
|
||||||
personsNotAssociated = personsNotAssociated.filter(p => p !== person);
|
personsNotAssociated = personsNotAssociated.filter(
|
||||||
|
(p) => p !== person,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
'personsAssociated': personsAssociated,
|
personsAssociated: personsAssociated,
|
||||||
'personsNotAssociated': personsNotAssociated
|
personsNotAssociated: personsNotAssociated,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getCourseParticipations() {
|
getCourseParticipations() {
|
||||||
let participations = [];
|
let participations = [];
|
||||||
this.accompanyingCourse.participations.forEach(participation => {
|
this.accompanyingCourse.participations.forEach((participation) => {
|
||||||
if (!participation.endDate) {
|
if (!participation.endDate) {
|
||||||
participations.push(participation.person);
|
participations.push(participation.person);
|
||||||
}
|
}
|
||||||
@@ -198,22 +220,23 @@ export default {
|
|||||||
return participations;
|
return participations;
|
||||||
},
|
},
|
||||||
addNewPersons({ selected, modal }) {
|
addNewPersons({ selected, modal }) {
|
||||||
console.log('@@@ CLICK button addNewPersons', selected);
|
console.log("@@@ CLICK button addNewPersons", selected);
|
||||||
selected.forEach((item) => {
|
selected.forEach((item) => {
|
||||||
this.$store.dispatch('addPersonsInvolved', item);
|
this.$store.dispatch("addPersonsInvolved", item);
|
||||||
}, this
|
}, this);
|
||||||
);
|
|
||||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||||
modal.showModal = false;
|
modal.showModal = false;
|
||||||
this.setPersonsInBloc();
|
this.setPersonsInBloc();
|
||||||
},
|
},
|
||||||
addSuggestedEntity(person) {
|
addSuggestedEntity(person) {
|
||||||
this.$store.dispatch('addPersonsInvolved', { result: person, type: 'person' });
|
this.$store.dispatch("addPersonsInvolved", {
|
||||||
|
result: person,
|
||||||
|
type: "person",
|
||||||
|
});
|
||||||
this.setPersonsInBloc();
|
this.setPersonsInBloc();
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped></style>
|
||||||
</style>
|
|
||||||
|
@@ -2,21 +2,20 @@
|
|||||||
<li>
|
<li>
|
||||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||||
<span class="chill_denomination">
|
<span class="chill_denomination">
|
||||||
<person-text :person="person" :isCut="true"></person-text>
|
<person-text :person="person" :is-cut="true" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
|
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PersonBadge",
|
name: "PersonBadge",
|
||||||
props: ['person'],
|
props: ["person"],
|
||||||
components: {
|
components: {
|
||||||
PersonText
|
PersonText,
|
||||||
},
|
},
|
||||||
// computed: {
|
// computed: {
|
||||||
// textCutted() {
|
// textCutted() {
|
||||||
@@ -24,9 +23,8 @@ export default {
|
|||||||
// return this.person.text.slice(0,15) + more;
|
// return this.person.text.slice(0,15) + more;
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
emits: ['remove'],
|
emits: ["remove"],
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped></style>
|
||||||
</style>
|
|
||||||
|
@@ -8,10 +8,10 @@
|
|||||||
<ul class="list-suggest remove-items">
|
<ul class="list-suggest remove-items">
|
||||||
<person-badge
|
<person-badge
|
||||||
v-for="person in bloc.persons"
|
v-for="person in bloc.persons"
|
||||||
v-bind:key="person.id"
|
:key="person.id"
|
||||||
v-bind:person="person"
|
:person="person"
|
||||||
@remove="removePerson">
|
@remove="removePerson"
|
||||||
</person-badge>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,23 +19,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PersonBadge from './PersonBadge.vue';
|
import PersonBadge from "./PersonBadge.vue";
|
||||||
export default {
|
export default {
|
||||||
name:"PersonsBloc",
|
name: "PersonsBloc",
|
||||||
components: {
|
components: {
|
||||||
PersonBadge
|
PersonBadge,
|
||||||
},
|
},
|
||||||
props: ['bloc', 'setPersonsInBloc', 'blocWidth'],
|
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
||||||
methods: {
|
methods: {
|
||||||
removePerson(item) {
|
removePerson(item) {
|
||||||
console.log('@@ CLICK remove person: item', item);
|
console.log("@@ CLICK remove person: item", item);
|
||||||
this.$store.dispatch('removePersonInvolved', item);
|
this.$store.dispatch("removePersonInvolved", item);
|
||||||
this.setPersonsInBloc();
|
this.setPersonsInBloc();
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss"></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@@ -22,9 +22,8 @@
|
|||||||
group-values="locations"
|
group-values="locations"
|
||||||
group-label="locationGroup"
|
group-label="locationGroup"
|
||||||
v-model="location"
|
v-model="location"
|
||||||
>
|
/>
|
||||||
</VueMultiselect>
|
<new-location :available-locations="availableLocations" />
|
||||||
<new-location v-bind:availableLocations="availableLocations"></new-location>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
@@ -43,9 +42,8 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
locationClassList:
|
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
||||||
`col-form-label col-sm-4 ${document.querySelector('input#chill_activitybundle_activity_location').getAttribute('required') ? 'required' : ''}`,
|
};
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["activity", "availableLocations"]),
|
...mapState(["activity", "availableLocations"]),
|
||||||
@@ -61,16 +59,16 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
labelAccompanyingCourseLocation(value) {
|
labelAccompanyingCourseLocation(value) {
|
||||||
return `${value.address.text} (${value.locationType.title.fr})`
|
return `${value.address.text} (${value.locationType.title.fr})`;
|
||||||
},
|
},
|
||||||
customLabel(value) {
|
customLabel(value) {
|
||||||
return value.locationType
|
return value.locationType
|
||||||
? value.name
|
? value.name
|
||||||
? value.name === '__AccompanyingCourseLocation__'
|
? value.name === "__AccompanyingCourseLocation__"
|
||||||
? this.labelAccompanyingCourseLocation(value)
|
? this.labelAccompanyingCourseLocation(value)
|
||||||
: `${value.name} (${value.locationType.title.fr})`
|
: `${value.name} (${value.locationType.title.fr})`
|
||||||
: value.locationType.title.fr
|
: value.locationType.title.fr
|
||||||
: '';
|
: "";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -3,84 +3,129 @@
|
|||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-sm btn-create" @click="openModal">
|
<a class="btn btn-sm btn-create" @click="openModal">
|
||||||
{{ $t('activity.create_new_location') }}
|
{{ $t("activity.create_new_location") }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<modal v-if="modal.showModal"
|
<modal
|
||||||
:modalDialogClass="modal.modalDialogClass"
|
v-if="modal.showModal"
|
||||||
@close="modal.showModal = false">
|
:modal-dialog-class="modal.modalDialogClass"
|
||||||
|
@close="modal.showModal = false"
|
||||||
<template v-slot:header>
|
>
|
||||||
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
|
<template #header>
|
||||||
|
<h3 class="modal-title">
|
||||||
|
{{ $t("activity.create_new_location") }}
|
||||||
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:body>
|
<template #body>
|
||||||
<form>
|
<form>
|
||||||
<div class="alert alert-warning" v-if="errors.length">
|
<div class="alert alert-warning" v-if="errors.length">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
|
<li v-for="(e, i) in errors" :key="i">
|
||||||
|
{{ e }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<select class="form-select form-select-lg" id="type" required v-model="selectType">
|
<select
|
||||||
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
|
class="form-select form-select-lg"
|
||||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
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"
|
||||||
|
>
|
||||||
{{ t.title.fr }}
|
{{ t.title.fr }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<label>{{ $t('activity.location_fields.type') }}</label>
|
<label>{{
|
||||||
|
$t("activity.location_fields.type")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
|
<input
|
||||||
<label for="name">{{ $t('activity.location_fields.name') }}</label>
|
class="form-control form-control-lg"
|
||||||
|
id="name"
|
||||||
|
v-model="inputName"
|
||||||
|
placeholder
|
||||||
|
/>
|
||||||
|
<label for="name">{{
|
||||||
|
$t("activity.location_fields.name")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<add-address
|
<add-address
|
||||||
:context="addAddress.context"
|
:context="addAddress.context"
|
||||||
:options="addAddress.options"
|
:options="addAddress.options"
|
||||||
:addressChangedCallback="submitNewAddress"
|
:address-changed-callback="submitNewAddress"
|
||||||
v-if="showAddAddress"
|
v-if="showAddAddress"
|
||||||
ref="addAddress">
|
ref="addAddress"
|
||||||
</add-address>
|
/>
|
||||||
|
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
|
<input
|
||||||
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
|
class="form-control form-control-lg"
|
||||||
|
id="phonenumber1"
|
||||||
|
v-model="inputPhonenumber1"
|
||||||
|
placeholder
|
||||||
|
/>
|
||||||
|
<label for="phonenumber1">{{
|
||||||
|
$t("activity.location_fields.phonenumber1")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||||
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
|
<input
|
||||||
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
|
class="form-control form-control-lg"
|
||||||
|
id="phonenumber2"
|
||||||
|
v-model="inputPhonenumber2"
|
||||||
|
placeholder
|
||||||
|
/>
|
||||||
|
<label for="phonenumber2">{{
|
||||||
|
$t("activity.location_fields.phonenumber2")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
|
<input
|
||||||
<label for="email">{{ $t('activity.location_fields.email') }}</label>
|
class="form-control form-control-lg"
|
||||||
|
id="email"
|
||||||
|
v-model="inputEmail"
|
||||||
|
placeholder
|
||||||
|
/>
|
||||||
|
<label for="email">{{
|
||||||
|
$t("activity.location_fields.email")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:footer>
|
<template #footer>
|
||||||
<button class="btn btn-save"
|
<button
|
||||||
|
class="btn btn-save"
|
||||||
@click.prevent="saveNewLocation"
|
@click.prevent="saveNewLocation"
|
||||||
>
|
>
|
||||||
{{ $t('action.save') }}
|
{{ $t("action.save") }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</modal>
|
</modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { getLocationTypes } from "../../api";
|
import { getLocationTypes } from "../../api";
|
||||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NewLocation",
|
name: "NewLocation",
|
||||||
@@ -88,7 +133,7 @@ export default {
|
|||||||
Modal,
|
Modal,
|
||||||
AddAddress,
|
AddAddress,
|
||||||
},
|
},
|
||||||
props: ['availableLocations'],
|
props: ["availableLocations"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
errors: [],
|
errors: [],
|
||||||
@@ -103,35 +148,42 @@ export default {
|
|||||||
locationTypes: [],
|
locationTypes: [],
|
||||||
modal: {
|
modal: {
|
||||||
showModal: false,
|
showModal: false,
|
||||||
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||||
},
|
},
|
||||||
addAddress: {
|
addAddress: {
|
||||||
options: {
|
options: {
|
||||||
button: {
|
button: {
|
||||||
text: { create: 'activity.create_address', edit: 'activity.edit_address' },
|
text: {
|
||||||
size: 'btn-sm'
|
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: {
|
context: {
|
||||||
target: { //name, id
|
target: {
|
||||||
|
//name, id
|
||||||
},
|
},
|
||||||
edit: false,
|
edit: false,
|
||||||
addressId: null,
|
addressId: null,
|
||||||
defaults: window.addaddress
|
defaults: window.addaddress,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['activity']),
|
...mapState(["activity"]),
|
||||||
selectType: {
|
selectType: {
|
||||||
get() {
|
get() {
|
||||||
return this.selected.type;
|
return this.selected.type;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.selected.type = value;
|
this.selected.type = value;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
inputName: {
|
inputName: {
|
||||||
get() {
|
get() {
|
||||||
@@ -139,7 +191,7 @@ export default {
|
|||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.selected.name = value;
|
this.selected.name = value;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
inputEmail: {
|
inputEmail: {
|
||||||
get() {
|
get() {
|
||||||
@@ -147,7 +199,7 @@ export default {
|
|||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.selected.email = value;
|
this.selected.email = value;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
inputPhonenumber1: {
|
inputPhonenumber1: {
|
||||||
get() {
|
get() {
|
||||||
@@ -155,7 +207,7 @@ export default {
|
|||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.selected.phonenumber1 = value;
|
this.selected.phonenumber1 = value;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
inputPhonenumber2: {
|
inputPhonenumber2: {
|
||||||
get() {
|
get() {
|
||||||
@@ -163,15 +215,18 @@ export default {
|
|||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
this.selected.phonenumber2 = value;
|
this.selected.phonenumber2 = value;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
hasPhonenumber1() {
|
hasPhonenumber1() {
|
||||||
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== "";
|
return (
|
||||||
|
this.selected.phonenumber1 !== null &&
|
||||||
|
this.selected.phonenumber1 !== ""
|
||||||
|
);
|
||||||
},
|
},
|
||||||
showAddAddress() {
|
showAddAddress() {
|
||||||
let cond = false;
|
let cond = false;
|
||||||
if (this.selected.type) {
|
if (this.selected.type) {
|
||||||
if (this.selected.type.addressRequired !== 'never') {
|
if (this.selected.type.addressRequired !== "never") {
|
||||||
cond = true;
|
cond = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +235,7 @@ export default {
|
|||||||
showContactData() {
|
showContactData() {
|
||||||
let cond = false;
|
let cond = false;
|
||||||
if (this.selected.type) {
|
if (this.selected.type) {
|
||||||
if (this.selected.type.contactData !== 'never') {
|
if (this.selected.type.contactData !== "never") {
|
||||||
cond = true;
|
cond = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,28 +250,39 @@ export default {
|
|||||||
let cond = true;
|
let cond = true;
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
if (!this.selected.type) {
|
if (!this.selected.type) {
|
||||||
this.errors.push('Type de localisation requis');
|
this.errors.push("Type de localisation requis");
|
||||||
cond = false;
|
cond = false;
|
||||||
} else {
|
} else {
|
||||||
if (this.selected.type.addressRequired === 'required' && !this.selected.addressId) {
|
if (
|
||||||
this.errors.push('Adresse requise');
|
this.selected.type.addressRequired === "required" &&
|
||||||
|
!this.selected.addressId
|
||||||
|
) {
|
||||||
|
this.errors.push("Adresse requise");
|
||||||
cond = false;
|
cond = false;
|
||||||
}
|
}
|
||||||
if (this.selected.type.contactData === 'required' && !this.selected.phonenumber1) {
|
if (
|
||||||
this.errors.push('Numéro de téléphone requis');
|
this.selected.type.contactData === "required" &&
|
||||||
|
!this.selected.phonenumber1
|
||||||
|
) {
|
||||||
|
this.errors.push("Numéro de téléphone requis");
|
||||||
cond = false;
|
cond = false;
|
||||||
}
|
}
|
||||||
if (this.selected.type.contactData === 'required' && !this.selected.email) {
|
if (
|
||||||
this.errors.push('Adresse email requise');
|
this.selected.type.contactData === "required" &&
|
||||||
|
!this.selected.email
|
||||||
|
) {
|
||||||
|
this.errors.push("Adresse email requise");
|
||||||
cond = false;
|
cond = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cond;
|
return cond;
|
||||||
},
|
},
|
||||||
getLocationTypesList() {
|
getLocationTypesList() {
|
||||||
getLocationTypes().then(results => {
|
getLocationTypes().then((results) => {
|
||||||
this.locationTypes = results.filter(t => t.availableForUsers === true);
|
this.locationTypes = results.filter(
|
||||||
})
|
(t) => t.availableForUsers === true,
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
openModal() {
|
openModal() {
|
||||||
this.modal.showModal = true;
|
this.modal.showModal = true;
|
||||||
@@ -224,11 +290,11 @@ export default {
|
|||||||
saveNewLocation() {
|
saveNewLocation() {
|
||||||
if (this.checkForm()) {
|
if (this.checkForm()) {
|
||||||
let body = {
|
let body = {
|
||||||
type: 'location',
|
type: "location",
|
||||||
name: this.selected.name,
|
name: this.selected.name,
|
||||||
locationType: {
|
locationType: {
|
||||||
id: this.selected.type.id,
|
id: this.selected.type.id,
|
||||||
type: 'location-type'
|
type: "location-type",
|
||||||
},
|
},
|
||||||
phonenumber1: this.selected.phonenumber1,
|
phonenumber1: this.selected.phonenumber1,
|
||||||
phonenumber2: this.selected.phonenumber2,
|
phonenumber2: this.selected.phonenumber2,
|
||||||
@@ -237,36 +303,36 @@ export default {
|
|||||||
if (this.selected.addressId) {
|
if (this.selected.addressId) {
|
||||||
body = Object.assign(body, {
|
body = Object.assign(body, {
|
||||||
address: {
|
address: {
|
||||||
id: this.selected.addressId
|
id: this.selected.addressId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
makeFetch('POST', '/api/1.0/main/location.json', body)
|
makeFetch("POST", "/api/1.0/main/location.json", body)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
this.$store.dispatch('addAvailableLocationGroup', {
|
this.$store.dispatch("addAvailableLocationGroup", {
|
||||||
locationGroup: 'Localisations nouvellement créées',
|
locationGroup: "Localisations nouvellement créées",
|
||||||
locations: [response]
|
locations: [response],
|
||||||
});
|
});
|
||||||
this.$store.dispatch('updateLocation', response);
|
this.$store.dispatch("updateLocation", response);
|
||||||
this.modal.showModal = false;
|
this.modal.showModal = false;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.name === 'ValidationException') {
|
if (error.name === "ValidationException") {
|
||||||
for (let v of error.violations) {
|
for (let v of error.violations) {
|
||||||
this.errors.push(v);
|
this.errors.push(v);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.errors.push('An error occurred');
|
this.errors.push("An error occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
submitNewAddress(payload) {
|
submitNewAddress(payload) {
|
||||||
this.selected.addressId = payload.addressId;
|
this.selected.addressId = payload.addressId;
|
||||||
this.addAddress.context.addressId = payload.addressId;
|
this.addAddress.context.addressId = payload.addressId;
|
||||||
this.addAddress.context.edit = true;
|
this.addAddress.context.edit = true;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#social-issues-acc">
|
<teleport to="#social-issues-acc">
|
||||||
|
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialIssuesClassList">{{ $t('activity.social_issues') }}</label>
|
<label :class="socialIssuesClassList">{{
|
||||||
|
$t("activity.social_issues")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
|
|
||||||
<check-social-issue
|
<check-social-issue
|
||||||
v-for="issue in socialIssuesList"
|
v-for="issue in socialIssuesList"
|
||||||
:key="issue.id"
|
:key="issue.id"
|
||||||
:issue="issue"
|
:issue="issue"
|
||||||
:selection="socialIssuesSelected"
|
:selection="socialIssuesSelected"
|
||||||
@updateSelected="updateIssuesSelected">
|
@update-selected="updateIssuesSelected"
|
||||||
</check-social-issue>
|
/>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
@@ -33,72 +33,81 @@
|
|||||||
:loading="issueIsLoading"
|
:loading="issueIsLoading"
|
||||||
:placeholder="$t('activity.choose_other_social_issue')"
|
:placeholder="$t('activity.choose_other_social_issue')"
|
||||||
:options="socialIssuesOther"
|
:options="socialIssuesOther"
|
||||||
@select="addIssueInList">
|
@select="addIssueInList"
|
||||||
</VueMultiselect>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialActionsClassList">{{ $t('activity.social_actions') }}</label>
|
<label :class="socialActionsClassList">{{
|
||||||
|
$t("activity.social_actions")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
|
|
||||||
<div v-if="actionIsLoading === true">
|
<div v-if="actionIsLoading === true">
|
||||||
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
|
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span v-else-if="socialIssuesSelected.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
<span
|
||||||
{{ $t('activity.select_first_a_social_issue') }}
|
v-else-if="socialIssuesSelected.length === 0"
|
||||||
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
|
>
|
||||||
|
{{ $t("activity.select_first_a_social_issue") }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<template v-else-if="socialActionsList.length > 0">
|
<template v-else-if="socialActionsList.length > 0">
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
socialIssuesSelected.length ||
|
||||||
|
socialActionsSelected.length
|
||||||
|
"
|
||||||
|
>
|
||||||
<check-social-action
|
<check-social-action
|
||||||
v-if="socialIssuesSelected.length || socialActionsSelected.length"
|
|
||||||
v-for="action in socialActionsList"
|
v-for="action in socialActionsList"
|
||||||
:key="action.id"
|
:key="action.id"
|
||||||
:action="action"
|
:action="action"
|
||||||
:selection="socialActionsSelected"
|
:selection="socialActionsSelected"
|
||||||
@updateSelected="updateActionsSelected">
|
@update-selected="updateActionsSelected"
|
||||||
</check-social-action>
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<span v-else-if="actionAreLoaded && socialActionsList.length === 0" class="inline-choice chill-no-data-statement mt-3">
|
<span
|
||||||
{{ $t('activity.social_action_list_empty') }}
|
v-else-if="
|
||||||
|
actionAreLoaded && socialActionsList.length === 0
|
||||||
|
"
|
||||||
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
|
>
|
||||||
|
{{ $t("activity.social_action_list_empty") }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VueMultiselect from 'vue-multiselect';
|
import VueMultiselect from "vue-multiselect";
|
||||||
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue';
|
import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
|
||||||
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue';
|
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
||||||
import { getSocialIssues, getSocialActionByIssue } from '../api.js';
|
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SocialIssuesAcc",
|
name: "SocialIssuesAcc",
|
||||||
components: {
|
components: {
|
||||||
CheckSocialIssue,
|
CheckSocialIssue,
|
||||||
CheckSocialAction,
|
CheckSocialAction,
|
||||||
VueMultiselect
|
VueMultiselect,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
issueIsLoading: false,
|
issueIsLoading: false,
|
||||||
actionIsLoading: false,
|
actionIsLoading: false,
|
||||||
actionAreLoaded: false,
|
actionAreLoaded: false,
|
||||||
socialIssuesClassList:
|
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
||||||
`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" : ""}`,
|
||||||
socialActionsClassList:
|
};
|
||||||
`col-form-label ${document.querySelector('input#chill_activitybundle_activity_socialActions').getAttribute('required') ? 'required' : ''}`,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
socialIssuesList() {
|
socialIssuesList() {
|
||||||
@@ -115,114 +124,120 @@ export default {
|
|||||||
},
|
},
|
||||||
socialActionsSelected() {
|
socialActionsSelected() {
|
||||||
return this.$store.state.activity.socialActions;
|
return this.$store.state.activity.socialActions;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
/* Load others issues in multiselect
|
/* Load others issues in multiselect
|
||||||
*/
|
*/
|
||||||
this.issueIsLoading = true;
|
this.issueIsLoading = true;
|
||||||
this.actionAreLoaded = false;
|
this.actionAreLoaded = false;
|
||||||
getSocialIssues().then(response => new Promise((resolve, reject) => {
|
getSocialIssues().then(
|
||||||
this.$store.commit('updateIssuesOther', response.results);
|
(response) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
this.$store.commit("updateIssuesOther", response.results);
|
||||||
|
|
||||||
/* Add in list the issues already associated (if not yet listed)
|
/* Add in list the issues already associated (if not yet listed)
|
||||||
*/
|
*/
|
||||||
this.socialIssuesSelected.forEach(issue => {
|
this.socialIssuesSelected.forEach((issue) => {
|
||||||
if (this.socialIssuesList.filter(i => i.id === issue.id).length !== 1) {
|
if (
|
||||||
this.$store.commit('addIssueInList', issue);
|
this.socialIssuesList.filter(
|
||||||
|
(i) => i.id === issue.id,
|
||||||
|
).length !== 1
|
||||||
|
) {
|
||||||
|
this.$store.commit("addIssueInList", issue);
|
||||||
}
|
}
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
/* Remove from multiselect the issues that are not yet in checkbox list
|
/* Remove from multiselect the issues that are not yet in checkbox list
|
||||||
*/
|
*/
|
||||||
this.socialIssuesList.forEach(issue => {
|
this.socialIssuesList.forEach((issue) => {
|
||||||
this.$store.commit('removeIssueInOther', issue);
|
this.$store.commit("removeIssueInOther", issue);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
/* Filter issues
|
/* Filter issues
|
||||||
*/
|
*/
|
||||||
this.$store.commit('filterList', 'issues');
|
this.$store.commit("filterList", "issues");
|
||||||
|
|
||||||
/* Add in list the actions already associated (if not yet listed)
|
/* Add in list the actions already associated (if not yet listed)
|
||||||
*/
|
*/
|
||||||
this.socialActionsSelected.forEach(action => {
|
this.socialActionsSelected.forEach((action) => {
|
||||||
this.$store.commit('addActionInList', action);
|
this.$store.commit("addActionInList", action);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
/* Filter issues
|
/* Filter issues
|
||||||
*/
|
*/
|
||||||
this.$store.commit('filterList', 'actions');
|
this.$store.commit("filterList", "actions");
|
||||||
|
|
||||||
this.issueIsLoading = false;
|
this.issueIsLoading = false;
|
||||||
this.actionAreLoaded = true;
|
this.actionAreLoaded = true;
|
||||||
this.updateActionsList();
|
this.updateActionsList();
|
||||||
resolve();
|
resolve();
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||||
remove it from multiselect, and add socialActions concerned
|
remove it from multiselect, and add socialActions concerned
|
||||||
*/
|
*/
|
||||||
addIssueInList(value) {
|
addIssueInList(value) {
|
||||||
//console.log('addIssueInList', value);
|
//console.log('addIssueInList', value);
|
||||||
this.$store.commit('addIssueInList', value);
|
this.$store.commit("addIssueInList", value);
|
||||||
this.$store.commit('removeIssueInOther', value);
|
this.$store.commit("removeIssueInOther", value);
|
||||||
this.$store.dispatch('addIssueSelected', value);
|
this.$store.dispatch("addIssueSelected", value);
|
||||||
this.updateActionsList();
|
this.updateActionsList();
|
||||||
},
|
},
|
||||||
/* Update value for selected issues checkboxes
|
/* Update value for selected issues checkboxes
|
||||||
*/
|
*/
|
||||||
updateIssuesSelected(issues) {
|
updateIssuesSelected(issues) {
|
||||||
//console.log('updateIssuesSelected', issues);
|
//console.log('updateIssuesSelected', issues);
|
||||||
this.$store.dispatch('updateIssuesSelected', issues);
|
this.$store.dispatch("updateIssuesSelected", issues);
|
||||||
this.updateActionsList();
|
this.updateActionsList();
|
||||||
},
|
},
|
||||||
/* Update value for selected actions checkboxes
|
/* Update value for selected actions checkboxes
|
||||||
*/
|
*/
|
||||||
updateActionsSelected(actions) {
|
updateActionsSelected(actions) {
|
||||||
//console.log('updateActionsSelected', actions);
|
//console.log('updateActionsSelected', actions);
|
||||||
this.$store.dispatch('updateActionsSelected', actions);
|
this.$store.dispatch("updateActionsSelected", actions);
|
||||||
},
|
},
|
||||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||||
to get social actions concerned
|
to get social actions concerned
|
||||||
*/
|
*/
|
||||||
updateActionsList() {
|
updateActionsList() {
|
||||||
this.resetActionsList();
|
this.resetActionsList();
|
||||||
this.socialIssuesSelected.forEach(item => {
|
this.socialIssuesSelected.forEach((item) => {
|
||||||
|
|
||||||
this.actionIsLoading = true;
|
this.actionIsLoading = true;
|
||||||
getSocialActionByIssue(item.id)
|
getSocialActionByIssue(item.id).then(
|
||||||
.then(actions => new Promise((resolve, reject) => {
|
(actions) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
actions.results.forEach(action => {
|
actions.results.forEach((action) => {
|
||||||
this.$store.commit('addActionInList', action);
|
this.$store.commit("addActionInList", action);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.$store.commit('filterList', 'actions');
|
this.$store.commit("filterList", "actions");
|
||||||
|
|
||||||
this.actionIsLoading = false;
|
this.actionIsLoading = false;
|
||||||
this.actionAreLoaded = true;
|
this.actionAreLoaded = true;
|
||||||
resolve();
|
resolve();
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
}, this);
|
}, this);
|
||||||
},
|
},
|
||||||
/* Reset socialActions List: flush list and restore selected actions
|
/* Reset socialActions List: flush list and restore selected actions
|
||||||
*/
|
*/
|
||||||
resetActionsList() {
|
resetActionsList() {
|
||||||
this.$store.commit('resetActionsList');
|
this.$store.commit("resetActionsList");
|
||||||
this.actionAreLoaded = false;
|
this.actionAreLoaded = false;
|
||||||
this.socialActionsSelected.forEach(item => {
|
this.socialActionsSelected.forEach((item) => {
|
||||||
this.$store.commit('addActionInList', item);
|
this.$store.commit("addActionInList", item);
|
||||||
}, this);
|
}, this);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
span.multiselect__single {
|
span.multiselect__single {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,18 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="inline-choice">
|
<span class="inline-choice">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
|
<input
|
||||||
<input class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
name="action"
|
name="action"
|
||||||
v-bind:id="action.id"
|
:id="action.id"
|
||||||
v-bind:value="action"
|
:value="action"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" v-bind:for="action.id">
|
<label class="form-check-label" :for="action.id">
|
||||||
<span class="badge bg-light text-dark">{{ action.text }}</span>
|
<span class="badge bg-light text-dark">{{ action.text }}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,25 +19,25 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "CheckSocialAction",
|
name: "CheckSocialAction",
|
||||||
props: [ 'action', 'selection' ],
|
props: ["action", "selection"],
|
||||||
emits: [ 'updateSelected' ],
|
emits: ["updateSelected"],
|
||||||
computed: {
|
computed: {
|
||||||
selected: {
|
selected: {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit('updateSelected', value);
|
this.$emit("updateSelected", value);
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.selection;
|
return this.selection;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import 'ChillMainAssets/module/bootstrap/shared';
|
@import "ChillMainAssets/module/bootstrap/shared";
|
||||||
@import 'ChillPersonAssets/chill/scss/mixins';
|
@import "ChillPersonAssets/chill/scss/mixins";
|
||||||
@import 'ChillMainAssets/chill/scss/chill_variables';
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
span.badge {
|
span.badge {
|
||||||
@include badge_social($social-action-color);
|
@include badge_social($social-action-color);
|
||||||
font-size: 95%;
|
font-size: 95%;
|
||||||
|
@@ -1,18 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="inline-choice">
|
<span class="inline-choice">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
|
<input
|
||||||
<input class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
name="issue"
|
name="issue"
|
||||||
v-bind:id="issue.id"
|
:id="issue.id"
|
||||||
v-bind:value="issue"
|
:value="issue"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" v-bind:for="issue.id">
|
<label class="form-check-label" :for="issue.id">
|
||||||
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
|
<span class="badge bg-chill-l-gray text-dark">{{
|
||||||
|
issue.text
|
||||||
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,25 +21,25 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "CheckSocialIssue",
|
name: "CheckSocialIssue",
|
||||||
props: [ 'issue', 'selection' ],
|
props: ["issue", "selection"],
|
||||||
emits: [ 'updateSelected' ],
|
emits: ["updateSelected"],
|
||||||
computed: {
|
computed: {
|
||||||
selected: {
|
selected: {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit('updateSelected', value);
|
this.$emit("updateSelected", value);
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.selection;
|
return this.selection;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import 'ChillMainAssets/module/bootstrap/shared';
|
@import "ChillMainAssets/module/bootstrap/shared";
|
||||||
@import 'ChillPersonAssets/chill/scss/mixins';
|
@import "ChillPersonAssets/chill/scss/mixins";
|
||||||
@import 'ChillMainAssets/chill/scss/chill_variables';
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
span.badge {
|
span.badge {
|
||||||
@include badge_social($social-issue-color);
|
@include badge_social($social-issue-color);
|
||||||
font-size: 95%;
|
font-size: 95%;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
|
import { personMessages } from "ChillPersonAssets/vuejs/_js/i18n";
|
||||||
import { multiSelectMessages } from 'ChillMainAssets/vuejs/_js/i18n'
|
import { multiSelectMessages } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
|
|
||||||
const activityMessages = {
|
const activityMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
@@ -9,7 +9,8 @@ const activityMessages = {
|
|||||||
social_issues: "Problématiques sociales",
|
social_issues: "Problématiques sociales",
|
||||||
choose_other_social_issue: "Ajouter une autre problématique sociale...",
|
choose_other_social_issue: "Ajouter une autre problématique sociale...",
|
||||||
social_actions: "Actions d'accompagnement",
|
social_actions: "Actions d'accompagnement",
|
||||||
select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale",
|
select_first_a_social_issue:
|
||||||
|
"Sélectionnez d'abord une problématique sociale",
|
||||||
social_action_list_empty: "Aucune action sociale disponible",
|
social_action_list_empty: "Aucune action sociale disponible",
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -32,14 +33,12 @@ const activityMessages = {
|
|||||||
phonenumber2: "Autre téléphone",
|
phonenumber2: "Autre téléphone",
|
||||||
email: "Adresse courriel",
|
email: "Adresse courriel",
|
||||||
},
|
},
|
||||||
create_address: 'Créer une adresse',
|
create_address: "Créer une adresse",
|
||||||
edit_address: "Modifier l'adresse"
|
edit_address: "Modifier l'adresse",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
Object.assign(activityMessages.fr, personMessages.fr, multiSelectMessages.fr);
|
Object.assign(activityMessages.fr, personMessages.fr, multiSelectMessages.fr);
|
||||||
|
|
||||||
export {
|
export { activityMessages };
|
||||||
activityMessages
|
|
||||||
};
|
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from "vue";
|
||||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
import { activityMessages } from './i18n'
|
import { activityMessages } from "./i18n";
|
||||||
import store from './store'
|
import store from "./store";
|
||||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
import PickTemplate from "ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue";
|
||||||
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
|
import { fetchTemplates } from "ChillDocGeneratorAssets/api/pickTemplate.js";
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from "./App.vue";
|
||||||
|
|
||||||
const i18n = _createI18n(activityMessages);
|
const i18n = _createI18n(activityMessages);
|
||||||
|
|
||||||
// app for activity
|
// app for activity
|
||||||
|
|
||||||
const hasSocialIssues = document.querySelector('#social-issues-acc') !== null;
|
const hasSocialIssues = document.querySelector("#social-issues-acc") !== null;
|
||||||
const hasLocation = document.querySelector('#location') !== null;
|
const hasLocation = document.querySelector("#location") !== null;
|
||||||
const hasPerson = document.querySelector('#add-persons') !== null;
|
const hasPerson = document.querySelector("#add-persons") !== null;
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app
|
template: `<app
|
||||||
@@ -27,20 +27,19 @@ const app = createApp({
|
|||||||
hasLocation,
|
hasLocation,
|
||||||
hasPerson,
|
hasPerson,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.use(store)
|
.use(store)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component('app', App)
|
.component("app", App)
|
||||||
.mount('#activity');
|
.mount("#activity");
|
||||||
|
|
||||||
|
|
||||||
// app for picking template
|
// app for picking template
|
||||||
|
|
||||||
const i18nGendoc = _createI18n({});
|
const i18nGendoc = _createI18n({});
|
||||||
|
|
||||||
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
|
document.querySelectorAll("div[data-docgen-template-picker]").forEach((el) => {
|
||||||
fetchTemplates(el.dataset.entityClass).then(templates => {
|
fetchTemplates(el.dataset.entityClass).then((templates) => {
|
||||||
const picker = {
|
const picker = {
|
||||||
template:
|
template:
|
||||||
'<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' +
|
'<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' +
|
||||||
@@ -52,35 +51,36 @@ document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
|
|||||||
return {
|
return {
|
||||||
templates: templates,
|
templates: templates,
|
||||||
entityId: el.dataset.entityId,
|
entityId: el.dataset.entityId,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
generateDoc({event, link, template}) {
|
generateDoc({ event, link, template }) {
|
||||||
console.log('generateDoc');
|
console.log("generateDoc");
|
||||||
console.log('link', link);
|
console.log("link", link);
|
||||||
console.log('template', template);
|
console.log("template", template);
|
||||||
|
|
||||||
let hiddenInput = document.querySelector("input[data-template-id]");
|
let hiddenInput = document.querySelector("input[data-template-id]");
|
||||||
|
|
||||||
if (hiddenInput === null) {
|
if (hiddenInput === null) {
|
||||||
console.error('hidden input not found');
|
console.error("hidden input not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenInput.value = template;
|
hiddenInput.value = template;
|
||||||
|
|
||||||
let form = document.querySelector('form[name="chill_activitybundle_activity"');
|
let form = document.querySelector(
|
||||||
|
'form[name="chill_activitybundle_activity"',
|
||||||
|
);
|
||||||
|
|
||||||
if (form === null) {
|
if (form === null) {
|
||||||
console.error('form not found');
|
console.error("form not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.submit();
|
form.submit();
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
createApp(picker).use(i18nGendoc).mount(el);
|
createApp(picker).use(i18nGendoc).mount(el);
|
||||||
})
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,21 +1,21 @@
|
|||||||
import 'es6-promise/auto';
|
import "es6-promise/auto";
|
||||||
import { createStore } from 'vuex';
|
import { createStore } from "vuex";
|
||||||
import { postLocation } from './api';
|
import { postLocation } from "./api";
|
||||||
import prepareLocations from './store.locations.js';
|
import prepareLocations from "./store.locations.js";
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== 'production';
|
const debug = process.env.NODE_ENV !== "production";
|
||||||
//console.log('window.activity', window.activity);
|
//console.log('window.activity', window.activity);
|
||||||
|
|
||||||
const addIdToValue = (string, id) => {
|
const addIdToValue = (string, id) => {
|
||||||
let array = string ? string.split(',') : [];
|
let array = string ? string.split(",") : [];
|
||||||
array.push(id.toString());
|
array.push(id.toString());
|
||||||
let str = array.join();
|
let str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeIdFromValue = (string, id) => {
|
const removeIdFromValue = (string, id) => {
|
||||||
let array = string.split(',');
|
let array = string.split(",");
|
||||||
array = array.filter(el => el !== id.toString());
|
array = array.filter((el) => el !== id.toString());
|
||||||
let str = array.join();
|
let str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
@@ -30,7 +30,10 @@ const store = createStore({
|
|||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
suggestedEntities(state) {
|
suggestedEntities(state) {
|
||||||
if (typeof state.activity.accompanyingPeriod === "undefined" || state.activity.accompanyingPeriod === null) {
|
if (
|
||||||
|
typeof state.activity.accompanyingPeriod === "undefined" ||
|
||||||
|
state.activity.accompanyingPeriod === null
|
||||||
|
) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const allEntities = [
|
const allEntities = [
|
||||||
@@ -44,7 +47,7 @@ const store = createStore({
|
|||||||
];
|
];
|
||||||
return Array.from(
|
return Array.from(
|
||||||
uniqueIds,
|
uniqueIds,
|
||||||
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0]
|
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestedPersons(state) {
|
suggestedPersons(state) {
|
||||||
@@ -62,7 +65,7 @@ const store = createStore({
|
|||||||
}
|
}
|
||||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||||
const existingThirdPartyIds = state.activity.thirdParties.map(
|
const existingThirdPartyIds = state.activity.thirdParties.map(
|
||||||
(p) => p.id
|
(p) => p.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return [state.activity.accompanyingPeriod.requestor].filter(
|
return [state.activity.accompanyingPeriod.requestor].filter(
|
||||||
@@ -72,7 +75,7 @@ const store = createStore({
|
|||||||
state.activity.activityType.personsVisible !== 0) ||
|
state.activity.activityType.personsVisible !== 0) ||
|
||||||
(r.type === "thirdparty" &&
|
(r.type === "thirdparty" &&
|
||||||
!existingThirdPartyIds.includes(r.id) &&
|
!existingThirdPartyIds.includes(r.id) &&
|
||||||
state.activity.activityType.thirdPartiesVisible !== 0)
|
state.activity.activityType.thirdPartiesVisible !== 0),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestedUser(state) {
|
suggestedUser(state) {
|
||||||
@@ -80,14 +83,14 @@ const store = createStore({
|
|||||||
return state.activity.activityType.usersVisible === 0
|
return state.activity.activityType.usersVisible === 0
|
||||||
? []
|
? []
|
||||||
: [state.activity.accompanyingPeriod.user].filter(
|
: [state.activity.accompanyingPeriod.user].filter(
|
||||||
(u) => u !== null && !existingUserIds.includes(u.id)
|
(u) => u !== null && !existingUserIds.includes(u.id),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestedResources(state) {
|
suggestedResources(state) {
|
||||||
const resources = state.activity.accompanyingPeriod.resources;
|
const resources = state.activity.accompanyingPeriod.resources;
|
||||||
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||||
const existingThirdPartyIds = state.activity.thirdParties.map(
|
const existingThirdPartyIds = state.activity.thirdParties.map(
|
||||||
(p) => p.id
|
(p) => p.id,
|
||||||
);
|
);
|
||||||
return state.activity.accompanyingPeriod.resources
|
return state.activity.accompanyingPeriod.resources
|
||||||
.map((r) => r.resource)
|
.map((r) => r.resource)
|
||||||
@@ -98,11 +101,13 @@ const store = createStore({
|
|||||||
state.activity.activityType.personsVisible !== 0) ||
|
state.activity.activityType.personsVisible !== 0) ||
|
||||||
(r.type === "thirdparty" &&
|
(r.type === "thirdparty" &&
|
||||||
!existingThirdPartyIds.includes(r.id) &&
|
!existingThirdPartyIds.includes(r.id) &&
|
||||||
state.activity.activityType.thirdPartiesVisible !== 0)
|
state.activity.activityType.thirdPartiesVisible !== 0),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
socialActionsListSorted(state) {
|
socialActionsListSorted(state) {
|
||||||
return [ ...state.socialActionsList].sort((a, b) => a.ordering - b.ordering);
|
return [...state.socialActionsList].sort(
|
||||||
|
(a, b) => a.ordering - b.ordering,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
@@ -126,7 +131,7 @@ const store = createStore({
|
|||||||
removeIssueInOther(state, issue) {
|
removeIssueInOther(state, issue) {
|
||||||
//console.log('remove issue other', issue.id);
|
//console.log('remove issue other', issue.id);
|
||||||
state.socialIssuesOther = state.socialIssuesOther.filter(
|
state.socialIssuesOther = state.socialIssuesOther.filter(
|
||||||
(i) => i.id !== issue.id
|
(i) => i.id !== issue.id,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
resetActionsList(state) {
|
resetActionsList(state) {
|
||||||
@@ -145,18 +150,15 @@ const store = createStore({
|
|||||||
// remove duplicates entries
|
// remove duplicates entries
|
||||||
list = list.filter(
|
list = list.filter(
|
||||||
(value, index) =>
|
(value, index) =>
|
||||||
list.findIndex((array) => array.id === value.id) ===
|
list.findIndex((array) => array.id === value.id) === index,
|
||||||
index
|
|
||||||
);
|
);
|
||||||
// alpha sort
|
// alpha sort
|
||||||
list.sort((a, b) =>
|
list.sort((a, b) => (a.text > b.text ? 1 : b.text > a.text ? -1 : 0));
|
||||||
a.text > b.text ? 1 : b.text > a.text ? -1 : 0
|
|
||||||
);
|
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
if (list === "issues") {
|
if (list === "issues") {
|
||||||
state.activity.accompanyingPeriod.socialIssues = filterList(
|
state.activity.accompanyingPeriod.socialIssues = filterList(
|
||||||
state.activity.accompanyingPeriod.socialIssues
|
state.activity.accompanyingPeriod.socialIssues,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (list === "actions") {
|
if (list === "actions") {
|
||||||
@@ -184,18 +186,17 @@ const store = createStore({
|
|||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
case "person":
|
case "person":
|
||||||
state.activity.persons = state.activity.persons.filter(
|
state.activity.persons = state.activity.persons.filter(
|
||||||
(person) => person !== payload
|
(person) => person !== payload,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "thirdparty":
|
case "thirdparty":
|
||||||
state.activity.thirdParties =
|
state.activity.thirdParties = state.activity.thirdParties.filter(
|
||||||
state.activity.thirdParties.filter(
|
(thirdparty) => thirdparty !== payload,
|
||||||
(thirdparty) => thirdparty !== payload
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "user":
|
case "user":
|
||||||
state.activity.users = state.activity.users.filter(
|
state.activity.users = state.activity.users.filter(
|
||||||
(user) => user !== payload
|
(user) => user !== payload,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -206,39 +207,33 @@ const store = createStore({
|
|||||||
},
|
},
|
||||||
addAvailableLocationGroup(state, group) {
|
addAvailableLocationGroup(state, group) {
|
||||||
state.availableLocations.push(group);
|
state.availableLocations.push(group);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addIssueSelected({ commit }, issue) {
|
addIssueSelected({ commit }, issue) {
|
||||||
let aSocialIssues = document.getElementById(
|
let aSocialIssues = document.getElementById(
|
||||||
"chill_activitybundle_activity_socialIssues"
|
"chill_activitybundle_activity_socialIssues",
|
||||||
);
|
);
|
||||||
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
|
aSocialIssues.value = addIdToValue(aSocialIssues.value, issue.id);
|
||||||
commit("addIssueSelected", issue);
|
commit("addIssueSelected", issue);
|
||||||
},
|
},
|
||||||
updateIssuesSelected({ commit }, payload) {
|
updateIssuesSelected({ commit }, payload) {
|
||||||
let aSocialIssues = document.getElementById(
|
let aSocialIssues = document.getElementById(
|
||||||
"chill_activitybundle_activity_socialIssues"
|
"chill_activitybundle_activity_socialIssues",
|
||||||
);
|
);
|
||||||
aSocialIssues.value = "";
|
aSocialIssues.value = "";
|
||||||
payload.forEach((item) => {
|
payload.forEach((item) => {
|
||||||
aSocialIssues.value = addIdToValue(
|
aSocialIssues.value = addIdToValue(aSocialIssues.value, item.id);
|
||||||
aSocialIssues.value,
|
|
||||||
item.id
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
commit("updateIssuesSelected", payload);
|
commit("updateIssuesSelected", payload);
|
||||||
},
|
},
|
||||||
updateActionsSelected({ commit }, payload) {
|
updateActionsSelected({ commit }, payload) {
|
||||||
let aSocialActions = document.getElementById(
|
let aSocialActions = document.getElementById(
|
||||||
"chill_activitybundle_activity_socialActions"
|
"chill_activitybundle_activity_socialActions",
|
||||||
);
|
);
|
||||||
aSocialActions.value = "";
|
aSocialActions.value = "";
|
||||||
payload.forEach((item) => {
|
payload.forEach((item) => {
|
||||||
aSocialActions.value = addIdToValue(
|
aSocialActions.value = addIdToValue(aSocialActions.value, item.id);
|
||||||
aSocialActions.value,
|
|
||||||
item.id
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
commit("updateActionsSelected", payload);
|
commit("updateActionsSelected", payload);
|
||||||
},
|
},
|
||||||
@@ -250,30 +245,24 @@ const store = createStore({
|
|||||||
switch (payload.result.type) {
|
switch (payload.result.type) {
|
||||||
case "person":
|
case "person":
|
||||||
let aPersons = document.getElementById(
|
let aPersons = document.getElementById(
|
||||||
"chill_activitybundle_activity_persons"
|
"chill_activitybundle_activity_persons",
|
||||||
);
|
|
||||||
aPersons.value = addIdToValue(
|
|
||||||
aPersons.value,
|
|
||||||
payload.result.id
|
|
||||||
);
|
);
|
||||||
|
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
|
||||||
break;
|
break;
|
||||||
case "thirdparty":
|
case "thirdparty":
|
||||||
let aThirdParties = document.getElementById(
|
let aThirdParties = document.getElementById(
|
||||||
"chill_activitybundle_activity_thirdParties"
|
"chill_activitybundle_activity_thirdParties",
|
||||||
);
|
);
|
||||||
aThirdParties.value = addIdToValue(
|
aThirdParties.value = addIdToValue(
|
||||||
aThirdParties.value,
|
aThirdParties.value,
|
||||||
payload.result.id
|
payload.result.id,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "user":
|
case "user":
|
||||||
let aUsers = document.getElementById(
|
let aUsers = document.getElementById(
|
||||||
"chill_activitybundle_activity_users"
|
"chill_activitybundle_activity_users",
|
||||||
);
|
|
||||||
aUsers.value = addIdToValue(
|
|
||||||
aUsers.value,
|
|
||||||
payload.result.id
|
|
||||||
);
|
);
|
||||||
|
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
commit("addPersonsInvolved", payload);
|
commit("addPersonsInvolved", payload);
|
||||||
@@ -283,25 +272,22 @@ const store = createStore({
|
|||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
case "person":
|
case "person":
|
||||||
let aPersons = document.getElementById(
|
let aPersons = document.getElementById(
|
||||||
"chill_activitybundle_activity_persons"
|
"chill_activitybundle_activity_persons",
|
||||||
);
|
|
||||||
aPersons.value = removeIdFromValue(
|
|
||||||
aPersons.value,
|
|
||||||
payload.id
|
|
||||||
);
|
);
|
||||||
|
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
|
||||||
break;
|
break;
|
||||||
case "thirdparty":
|
case "thirdparty":
|
||||||
let aThirdParties = document.getElementById(
|
let aThirdParties = document.getElementById(
|
||||||
"chill_activitybundle_activity_thirdParties"
|
"chill_activitybundle_activity_thirdParties",
|
||||||
);
|
);
|
||||||
aThirdParties.value = removeIdFromValue(
|
aThirdParties.value = removeIdFromValue(
|
||||||
aThirdParties.value,
|
aThirdParties.value,
|
||||||
payload.id
|
payload.id,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "user":
|
case "user":
|
||||||
let aUsers = document.getElementById(
|
let aUsers = document.getElementById(
|
||||||
"chill_activitybundle_activity_users"
|
"chill_activitybundle_activity_users",
|
||||||
);
|
);
|
||||||
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
|
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
|
||||||
break;
|
break;
|
||||||
@@ -311,32 +297,30 @@ const store = createStore({
|
|||||||
updateLocation({ commit }, value) {
|
updateLocation({ commit }, value) {
|
||||||
console.log("### action: updateLocation", value);
|
console.log("### action: updateLocation", value);
|
||||||
let hiddenLocation = document.getElementById(
|
let hiddenLocation = document.getElementById(
|
||||||
"chill_activitybundle_activity_location"
|
"chill_activitybundle_activity_location",
|
||||||
);
|
);
|
||||||
if (value.onthefly) {
|
if (value.onthefly) {
|
||||||
const body = {
|
const body = {
|
||||||
"type": "location",
|
type: "location",
|
||||||
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name,
|
name:
|
||||||
"locationType": {
|
value.name === "__AccompanyingCourseLocation__" ? null : value.name,
|
||||||
"id": value.locationType.id,
|
locationType: {
|
||||||
"type": "location-type"
|
id: value.locationType.id,
|
||||||
}
|
type: "location-type",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (value.address.id) {
|
if (value.address.id) {
|
||||||
Object.assign(body, {
|
Object.assign(body, {
|
||||||
"address": {
|
address: {
|
||||||
"id": value.address.id
|
id: value.address.id,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
postLocation(body)
|
postLocation(body)
|
||||||
.then(
|
.then((location) => (hiddenLocation.value = location.id))
|
||||||
location => hiddenLocation.value = location.id
|
.catch((err) => {
|
||||||
).catch(
|
|
||||||
err => {
|
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
hiddenLocation.value = value.id;
|
hiddenLocation.value = value.id;
|
||||||
}
|
}
|
||||||
|
@@ -1,41 +1,41 @@
|
|||||||
import {getLocations, getLocationTypeByDefaultFor, getUserCurrentLocation} from "./api";
|
import {
|
||||||
|
getLocations,
|
||||||
|
getLocationTypeByDefaultFor,
|
||||||
|
getUserCurrentLocation,
|
||||||
|
} from "./api";
|
||||||
|
|
||||||
const makeConcernedPersonsLocation = (locationType, store) => {
|
const makeConcernedPersonsLocation = (locationType, store) => {
|
||||||
let locations = [];
|
let locations = [];
|
||||||
store.getters.suggestedEntities.forEach(
|
store.getters.suggestedEntities.forEach((e) => {
|
||||||
(e) => {
|
if (e.type === "person" && e.current_household_address !== null) {
|
||||||
if (e.type === 'person' && e.current_household_address !== null){
|
|
||||||
locations.push({
|
locations.push({
|
||||||
type: 'location',
|
type: "location",
|
||||||
id: -store.getters.suggestedEntities.indexOf(e)*10,
|
id: -store.getters.suggestedEntities.indexOf(e) * 10,
|
||||||
onthefly: true,
|
onthefly: true,
|
||||||
name: e.text,
|
name: e.text,
|
||||||
address: {
|
address: {
|
||||||
id: e.current_household_address.address_id,
|
id: e.current_household_address.address_id,
|
||||||
},
|
},
|
||||||
locationType: locationType
|
locationType: locationType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
)
|
|
||||||
return locations;
|
return locations;
|
||||||
};
|
};
|
||||||
const makeConcernedThirdPartiesLocation = (locationType, store) => {
|
const makeConcernedThirdPartiesLocation = (locationType, store) => {
|
||||||
let locations = [];
|
let locations = [];
|
||||||
store.getters.suggestedEntities.forEach(
|
store.getters.suggestedEntities.forEach((e) => {
|
||||||
(e) => {
|
if (e.type === "thirdparty" && e.address !== null) {
|
||||||
if (e.type === 'thirdparty' && e.address !== null){
|
|
||||||
locations.push({
|
locations.push({
|
||||||
type: 'location',
|
type: "location",
|
||||||
id: -store.getters.suggestedEntities.indexOf(e)*10,
|
id: -store.getters.suggestedEntities.indexOf(e) * 10,
|
||||||
onthefly: true,
|
onthefly: true,
|
||||||
name: e.text,
|
name: e.text,
|
||||||
address: { id: e.address.address_id },
|
address: { id: e.address.address_id },
|
||||||
locationType: locationType
|
locationType: locationType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
)
|
|
||||||
return locations;
|
return locations;
|
||||||
};
|
};
|
||||||
const makeAccompanyingPeriodLocation = (locationType, store) => {
|
const makeAccompanyingPeriodLocation = (locationType, store) => {
|
||||||
@@ -44,78 +44,86 @@ const makeAccompanyingPeriodLocation = (locationType, store) => {
|
|||||||
}
|
}
|
||||||
const accPeriodLocation = store.state.activity.accompanyingPeriod.location;
|
const accPeriodLocation = store.state.activity.accompanyingPeriod.location;
|
||||||
return {
|
return {
|
||||||
type: 'location',
|
type: "location",
|
||||||
id: -1,
|
id: -1,
|
||||||
onthefly: true,
|
onthefly: true,
|
||||||
name: '__AccompanyingCourseLocation__',
|
name: "__AccompanyingCourseLocation__",
|
||||||
address: {
|
address: {
|
||||||
id: accPeriodLocation.address_id,
|
id: accPeriodLocation.address_id,
|
||||||
text: `${accPeriodLocation.text} - ${accPeriodLocation.postcode.code} ${accPeriodLocation.postcode.name}`
|
text: `${accPeriodLocation.text} - ${accPeriodLocation.postcode.code} ${accPeriodLocation.postcode.name}`,
|
||||||
},
|
},
|
||||||
locationType: locationType
|
locationType: locationType,
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function prepareLocations(store) {
|
export default function prepareLocations(store) {
|
||||||
|
|
||||||
// find the locations
|
// find the locations
|
||||||
let allLocations = getLocations().then(
|
let allLocations = getLocations().then((results) => {
|
||||||
(results) => {
|
store.commit("addAvailableLocationGroup", {
|
||||||
store.commit('addAvailableLocationGroup', {
|
locationGroup: "Autres localisations",
|
||||||
locationGroup: 'Autres localisations',
|
locations: results,
|
||||||
locations: results
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let currentLocation = getUserCurrentLocation().then(
|
let currentLocation = getUserCurrentLocation().then((userCurrentLocation) => {
|
||||||
userCurrentLocation => {
|
|
||||||
if (null !== userCurrentLocation) {
|
if (null !== userCurrentLocation) {
|
||||||
store.commit('addAvailableLocationGroup', {
|
store.commit("addAvailableLocationGroup", {
|
||||||
locationGroup: 'Ma localisation',
|
locationGroup: "Ma localisation",
|
||||||
locations: [userCurrentLocation]
|
locations: [userCurrentLocation],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
let partiesLocations = [], partyPromise;
|
let partiesLocations = [],
|
||||||
['person', 'thirdparty'].forEach(kind => {
|
partyPromise;
|
||||||
|
["person", "thirdparty"].forEach((kind) => {
|
||||||
partyPromise = getLocationTypeByDefaultFor(kind).then(
|
partyPromise = getLocationTypeByDefaultFor(kind).then(
|
||||||
(kindLocationType) => {
|
(kindLocationType) => {
|
||||||
if (kindLocationType) {
|
if (kindLocationType) {
|
||||||
let concernedKindLocations;
|
let concernedKindLocations;
|
||||||
if (kind === 'person') {
|
if (kind === "person") {
|
||||||
concernedKindLocations = makeConcernedPersonsLocation(kindLocationType, store);
|
concernedKindLocations = makeConcernedPersonsLocation(
|
||||||
|
kindLocationType,
|
||||||
|
store,
|
||||||
|
);
|
||||||
// add location for the parcours into suggestions
|
// add location for the parcours into suggestions
|
||||||
const personLocation = makeAccompanyingPeriodLocation(kindLocationType, store);
|
const personLocation = makeAccompanyingPeriodLocation(
|
||||||
store.commit('addAvailableLocationGroup', {
|
kindLocationType,
|
||||||
locationGroup: 'Localisation du parcours',
|
store,
|
||||||
locations: [personLocation]
|
);
|
||||||
|
store.commit("addAvailableLocationGroup", {
|
||||||
|
locationGroup: "Localisation du parcours",
|
||||||
|
locations: [personLocation],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
concernedKindLocations = makeConcernedThirdPartiesLocation(kindLocationType, store);
|
concernedKindLocations = makeConcernedThirdPartiesLocation(
|
||||||
|
kindLocationType,
|
||||||
|
store,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
store.commit('addAvailableLocationGroup', {
|
store.commit("addAvailableLocationGroup", {
|
||||||
locationGroup: kind === 'person' ? 'Usagers concernés' : 'Tiers concernés',
|
locationGroup:
|
||||||
|
kind === "person" ? "Usagers concernés" : "Tiers concernés",
|
||||||
locations: concernedKindLocations,
|
locations: concernedKindLocations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
partiesLocations.push(partyPromise);
|
partiesLocations.push(partyPromise);
|
||||||
});
|
});
|
||||||
|
|
||||||
// when all location are loaded
|
// when all location are loaded
|
||||||
Promise.all([allLocations, currentLocation, ...partiesLocations]).then(() => {
|
Promise.all([allLocations, currentLocation, ...partiesLocations]).then(() => {
|
||||||
console.log('current location in activity', store.state.activity.location);
|
console.log("current location in activity", store.state.activity.location);
|
||||||
console.log('default loation id', window.default_location_id);
|
console.log("default loation id", window.default_location_id);
|
||||||
if (window.default_location_id) {
|
if (window.default_location_id) {
|
||||||
for (let group of store.state.availableLocations) {
|
for (let group of store.state.availableLocations) {
|
||||||
let location = group.locations.find((l) => l.id === window.default_location_id);
|
let location = group.locations.find(
|
||||||
|
(l) => l.id === window.default_location_id,
|
||||||
|
);
|
||||||
if (location !== undefined && store.state.activity.location === null) {
|
if (location !== undefined && store.state.activity.location === null) {
|
||||||
store.dispatch('updateLocation', location);
|
store.dispatch("updateLocation", location);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,18 @@
|
|||||||
// this file loads all assets from the Chill person bundle
|
// this file loads all assets from the Chill person bundle
|
||||||
module.exports = function(encore, entries)
|
module.exports = function (encore, entries) {
|
||||||
{
|
entries.push(__dirname + "/Resources/public/chill/index.js");
|
||||||
entries.push(__dirname + '/Resources/public/chill/index.js');
|
|
||||||
|
|
||||||
encore.addAliases({
|
encore.addAliases({
|
||||||
ChillActivityAssets: __dirname + '/Resources/public'
|
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",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1 +1 @@
|
|||||||
require('./chillbudget.scss');
|
require("./chillbudget.scss");
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
// this file loads all assets from the Chill budget bundle
|
// this file loads all assets from the Chill budget bundle
|
||||||
module.exports = function(encore, entries)
|
module.exports = function (encore, entries) {
|
||||||
{
|
|
||||||
encore.addAliases({
|
encore.addAliases({
|
||||||
ChillBudgetAssets: __dirname + '/Resources/public'
|
ChillBudgetAssets: __dirname + "/Resources/public",
|
||||||
});
|
});
|
||||||
|
|
||||||
encore.addEntry('page_budget', __dirname + '/Resources/public/page/index.js');
|
encore.addEntry("page_budget", __dirname + "/Resources/public/page/index.js");
|
||||||
};
|
};
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
import './scss/badge.scss';
|
import "./scss/badge.scss";
|
||||||
import './scss/calendar-list.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 { createApp } from 'vue';
|
import Answer from "ChillCalendarAssets/vuejs/Invite/Answer";
|
||||||
import Answer from 'ChillCalendarAssets/vuejs/Invite/Answer';
|
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
|
||||||
|
|
||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function (e) {
|
document.addEventListener("DOMContentLoaded", function (e) {
|
||||||
console.log('dom loaded answer');
|
console.log("dom loaded answer");
|
||||||
document.querySelectorAll('div[invite-answer]').forEach(function (el) {
|
document.querySelectorAll("div[invite-answer]").forEach(function (el) {
|
||||||
console.log('element found', el);
|
console.log("element found", el);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: {
|
components: {
|
||||||
@@ -18,14 +17,15 @@ document.addEventListener('DOMContentLoaded', function (e) {
|
|||||||
return {
|
return {
|
||||||
status: el.dataset.status,
|
status: el.dataset.status,
|
||||||
calendarId: Number.parseInt(el.dataset.calendarId),
|
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: {
|
methods: {
|
||||||
onStatusChanged: function(newStatus) {
|
onStatusChanged: function (newStatus) {
|
||||||
this.$data.status = newStatus;
|
this.$data.status = newStatus;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).mount(el);
|
app.use(i18n).mount(el);
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
import {EventInput} from '@fullcalendar/core';
|
import { EventInput } from "@fullcalendar/core";
|
||||||
import {DateTime, Location, User, UserAssociatedInterface} from '../../../ChillMainBundle/Resources/public/types' ;
|
import {
|
||||||
import {Person} from "../../../ChillPersonBundle/Resources/public/types";
|
DateTime,
|
||||||
|
Location,
|
||||||
|
User,
|
||||||
|
UserAssociatedInterface,
|
||||||
|
} from "../../../ChillMainBundle/Resources/public/types";
|
||||||
|
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
|
||||||
|
|
||||||
export interface CalendarRange {
|
export interface CalendarRange {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -22,8 +27,8 @@ export interface CalendarRangeCreate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarRangeEdit {
|
export interface CalendarRangeEdit {
|
||||||
startDate?: DateTime,
|
startDate?: DateTime;
|
||||||
endDate?: DateTime
|
endDate?: DateTime;
|
||||||
location?: Location;
|
location?: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,19 +54,23 @@ export interface CalendarRemote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type EventInputCalendarRange = EventInput & {
|
export type EventInputCalendarRange = EventInput & {
|
||||||
id: string,
|
id: string;
|
||||||
userId: number,
|
userId: number;
|
||||||
userLabel: string,
|
userLabel: string;
|
||||||
calendarRangeId: number,
|
calendarRangeId: number;
|
||||||
locationId: number,
|
locationId: number;
|
||||||
locationName: string,
|
locationName: string;
|
||||||
start: string,
|
start: string;
|
||||||
end: string,
|
end: string;
|
||||||
is: "range"
|
is: "range";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isEventInputCalendarRange(toBeDetermined: EventInputCalendarRange | EventInput): toBeDetermined is EventInputCalendarRange {
|
export function isEventInputCalendarRange(
|
||||||
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
|
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||||
|
): toBeDetermined is EventInputCalendarRange {
|
||||||
|
return (
|
||||||
|
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
@@ -1,63 +1,82 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<teleport to="#mainUser">
|
<teleport to="#mainUser">
|
||||||
|
|
||||||
<h2 class="chill-red">Utilisateur principal</h2>
|
<h2 class="chill-red">Utilisateur principal</h2>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="null !== this.$store.getters.getMainUser">
|
<div v-if="null !== this.$store.getters.getMainUser">
|
||||||
<calendar-active :user="this.$store.getters.getMainUser" ></calendar-active>
|
<calendar-active :user="this.$store.getters.getMainUser" />
|
||||||
</div>
|
</div>
|
||||||
<pick-entity
|
<pick-entity
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:types="['user']"
|
:types="['user']"
|
||||||
:uniqid="'main_user_calendar'"
|
:uniqid="'main_user_calendar'"
|
||||||
:picked="null !== this.$store.getters.getMainUser ? [this.$store.getters.getMainUser] : []"
|
:picked="
|
||||||
:removableIfSet="false"
|
null !== this.$store.getters.getMainUser
|
||||||
:displayPicked="false"
|
? [this.$store.getters.getMainUser]
|
||||||
|
: []
|
||||||
|
"
|
||||||
|
:removable-if-set="false"
|
||||||
|
:display-picked="false"
|
||||||
:suggested="this.suggestedUsers"
|
:suggested="this.suggestedUsers"
|
||||||
:label="'main_user'"
|
:label="'main_user'"
|
||||||
@addNewEntity="setMainUser"
|
@add-new-entity="setMainUser"
|
||||||
></pick-entity>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
|
|
||||||
<concerned-groups></concerned-groups>
|
<concerned-groups />
|
||||||
|
|
||||||
<teleport to="#schedule">
|
<teleport to="#schedule">
|
||||||
<div class="row mb-3" v-if="activity.startDate !== null">
|
<div class="row mb-3" v-if="activity.startDate !== null">
|
||||||
<label class="col-form-label col-sm-4">Date</label>
|
<label class="col-form-label col-sm-4">Date</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
{{ $d(activity.startDate, 'long') }} - {{ $d(activity.endDate, 'hoursOnly') }}
|
{{ $d(activity.startDate, "long") }} -
|
||||||
<span v-if="activity.calendarRange === null">(Pas de plage de disponibilité sélectionnée)</span>
|
{{ $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>
|
<span v-else>(Une plage de disponibilité sélectionnée)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
|
|
||||||
<location></location>
|
<location />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<teleport to="#fullCalendar">
|
<teleport to="#fullCalendar">
|
||||||
<div class="calendar-actives">
|
<div class="calendar-actives">
|
||||||
<template class="" v-for="u in getActiveUsers" :key="u.id">
|
<template v-for="u in getActiveUsers" :key="u.id">
|
||||||
<calendar-active :user="u" :invite="this.$store.getters.getInviteForUser(u)"></calendar-active>
|
<calendar-active
|
||||||
|
:user="u"
|
||||||
|
:invite="this.$store.getters.getInviteForUser(u)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="display-options row justify-content-between" style="margin-top: 1rem;">
|
<div
|
||||||
|
class="display-options row justify-content-between"
|
||||||
|
style="margin-top: 1rem"
|
||||||
|
>
|
||||||
<div class="col-sm-9 col-xs-12">
|
<div class="col-sm-9 col-xs-12">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<label class="input-group-text" for="slotDuration">Durée des créneaux</label>
|
<label class="input-group-text" for="slotDuration"
|
||||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
>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:05:00">5 minutes</option>
|
||||||
<option value="00:10:00">10 minutes</option>
|
<option value="00:10:00">10 minutes</option>
|
||||||
<option value="00:15:00">15 minutes</option>
|
<option value="00:15:00">15 minutes</option>
|
||||||
<option value="00:30:00">30 minutes</option>
|
<option value="00:30:00">30 minutes</option>
|
||||||
</select>
|
</select>
|
||||||
<label class="input-group-text" for="slotMinTime">De</label>
|
<label class="input-group-text" for="slotMinTime">De</label>
|
||||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
<select
|
||||||
|
v-model="slotMinTime"
|
||||||
|
id="slotMinTime"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option value="00:00:00">0h</option>
|
<option value="00:00:00">0h</option>
|
||||||
<option value="01:00:00">1h</option>
|
<option value="01:00:00">1h</option>
|
||||||
<option value="02:00:00">2h</option>
|
<option value="02:00:00">2h</option>
|
||||||
@@ -73,7 +92,11 @@
|
|||||||
<option value="12:00:00">12h</option>
|
<option value="12:00:00">12h</option>
|
||||||
</select>
|
</select>
|
||||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
<select
|
||||||
|
v-model="slotMaxTime"
|
||||||
|
id="slotMaxTime"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option value="12:00:00">12h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="13:00:00">13h</option>
|
<option value="13:00:00">13h</option>
|
||||||
<option value="14:00:00">14h</option>
|
<option value="14:00:00">14h</option>
|
||||||
@@ -94,21 +117,44 @@
|
|||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
<div class="form-check input-group">
|
<div class="form-check input-group">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
<input id="showHideWE" class="mt-0" type="checkbox" v-model="hideWeekends">
|
<input
|
||||||
|
id="showHideWE"
|
||||||
|
class="mt-0"
|
||||||
|
type="checkbox"
|
||||||
|
v-model="hideWeekends"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<label for="showHideWE" class="form-check-label input-group-text">Week-ends</label>
|
<label
|
||||||
|
for="showHideWE"
|
||||||
|
class="form-check-label input-group-text"
|
||||||
|
>Week-ends</label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||||
<template v-slot:eventContent='arg'>
|
<template #eventContent="arg">
|
||||||
<span>
|
<span>
|
||||||
<b v-if="arg.event.extendedProps.is === 'remote'">{{ arg.event.title}}</b>
|
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'range'">{{ arg.timeText }} {{ arg.event.extendedProps.locationName }} <small>{{ arg.event.extendedProps.userLabel }}</small></b>
|
arg.event.title
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'current'">{{ arg.timeText }} {{ $t('current_selected')}} </b>
|
}}</b>
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{ arg.event.title}}</b>
|
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
||||||
<b v-else>{{ arg.timeText }} {{ $t('current_selected')}} </b>
|
>{{ 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>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</FullCalendar>
|
</FullCalendar>
|
||||||
@@ -116,17 +162,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue';
|
import ConcernedGroups from "ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue";
|
||||||
import Location from 'ChillActivityAssets/vuejs/Activity/components/Location.vue';
|
import Location from "ChillActivityAssets/vuejs/Activity/components/Location.vue";
|
||||||
import frLocale from '@fullcalendar/core/locales/fr';
|
import frLocale from "@fullcalendar/core/locales/fr";
|
||||||
import FullCalendar from '@fullcalendar/vue3';
|
import FullCalendar from "@fullcalendar/vue3";
|
||||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
import interactionPlugin from '@fullcalendar/interaction';
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
import listPlugin from '@fullcalendar/list';
|
import listPlugin from "@fullcalendar/list";
|
||||||
import CalendarActive from './Components/CalendarActive';
|
import CalendarActive from "./Components/CalendarActive";
|
||||||
import PickEntity from 'ChillMainAssets/vuejs/PickEntity/PickEntity.vue';
|
import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||||
import {mapGetters, mapState} from "vuex";
|
import { mapGetters, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
@@ -141,24 +187,30 @@ export default {
|
|||||||
return {
|
return {
|
||||||
errorMsg: [],
|
errorMsg: [],
|
||||||
showMyCalendar: false,
|
showMyCalendar: false,
|
||||||
slotDuration: '00:05:00',
|
slotDuration: "00:05:00",
|
||||||
slotMinTime: '09:00:00',
|
slotMinTime: "09:00:00",
|
||||||
slotMaxTime: '18:00:00',
|
slotMaxTime: "18:00:00",
|
||||||
hideWeekEnds: true,
|
hideWeekEnds: true,
|
||||||
previousUser: [],
|
previousUser: [],
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['getMainUser']),
|
...mapGetters(["getMainUser"]),
|
||||||
...mapState(['activity']),
|
...mapState(["activity"]),
|
||||||
events() {
|
events() {
|
||||||
return this.$store.getters.getEventSources;
|
return this.$store.getters.getEventSources;
|
||||||
},
|
},
|
||||||
calendarOptions() {
|
calendarOptions() {
|
||||||
return {
|
return {
|
||||||
locale: frLocale,
|
locale: frLocale,
|
||||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin, dayGridPlugin, listPlugin],
|
plugins: [
|
||||||
initialView: 'timeGridWeek',
|
dayGridPlugin,
|
||||||
|
interactionPlugin,
|
||||||
|
timeGridPlugin,
|
||||||
|
dayGridPlugin,
|
||||||
|
listPlugin,
|
||||||
|
],
|
||||||
|
initialView: "timeGridWeek",
|
||||||
initialDate: this.$store.getters.getInitialDate,
|
initialDate: this.$store.getters.getInitialDate,
|
||||||
eventSources: this.events,
|
eventSources: this.events,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
@@ -173,9 +225,9 @@ export default {
|
|||||||
editable: true,
|
editable: true,
|
||||||
weekends: !this.hideWeekEnds,
|
weekends: !this.hideWeekEnds,
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
left: 'prev,next today',
|
left: "prev,next today",
|
||||||
center: 'title',
|
center: "title",
|
||||||
right: 'timeGridWeek,timeGridDay,listWeek',
|
right: "timeGridWeek,timeGridDay,listWeek",
|
||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
timeGrid: {
|
timeGrid: {
|
||||||
@@ -195,9 +247,9 @@ export default {
|
|||||||
suggestedUsers() {
|
suggestedUsers() {
|
||||||
const suggested = [];
|
const suggested = [];
|
||||||
|
|
||||||
this.$data.previousUser.forEach(u => {
|
this.$data.previousUser.forEach((u) => {
|
||||||
if (u.id !== this.$store.getters.getMainUser.id) {
|
if (u.id !== this.$store.getters.getMainUser.id) {
|
||||||
suggested.push(u)
|
suggested.push(u);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -205,83 +257,122 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setMainUser({entity}) {
|
setMainUser({ entity }) {
|
||||||
const user = entity;
|
const user = entity;
|
||||||
console.log('setMainUser APP', entity);
|
console.log("setMainUser APP", entity);
|
||||||
|
|
||||||
if (user.id !== this.$store.getters.getMainUser && (
|
if (
|
||||||
this.$store.state.activity.calendarRange !== null
|
user.id !== this.$store.getters.getMainUser &&
|
||||||
|| this.$store.state.activity.startDate !== null
|
(this.$store.state.activity.calendarRange !== null ||
|
||||||
|| this.$store.state.activity.endDate !== 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"),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (!window.confirm(this.$t('change_main_user_will_reset_event_data'))) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the previous user, if any, in the previous user list (in use for suggestion)
|
// add the previous user, if any, in the previous user list (in use for suggestion)
|
||||||
if (null !== this.$store.getters.getMainUser) {
|
if (null !== this.$store.getters.getMainUser) {
|
||||||
const suggestedUids = new Set(this.$data.previousUser.map(u => u.id));
|
const suggestedUids = new Set(
|
||||||
if (!suggestedUids.has(this.$store.getters.getMainUser.id)){
|
this.$data.previousUser.map((u) => u.id),
|
||||||
this.$data.previousUser.push(this.$store.getters.getMainUser);
|
);
|
||||||
|
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
||||||
|
this.$data.previousUser.push(
|
||||||
|
this.$store.getters.getMainUser,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.dispatch('setMainUser', user);
|
this.$store.dispatch("setMainUser", user);
|
||||||
this.$store.commit('showUserOnCalendar', {user, ranges: true, remotes: true});
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user,
|
||||||
|
ranges: true,
|
||||||
|
remotes: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
removeMainUser(user) {
|
removeMainUser(user) {
|
||||||
console.log('removeMainUser APP', user);
|
console.log("removeMainUser APP", user);
|
||||||
|
|
||||||
window.alert(this.$t('main_user_is_mandatory'));
|
window.alert(this.$t("main_user_is_mandatory"));
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
onDatesSet(event) {
|
onDatesSet(event) {
|
||||||
console.log('onDatesSet', event);
|
console.log("onDatesSet", event);
|
||||||
this.$store.dispatch('setCurrentDatesView', {start: event.start, end: event.end});
|
this.$store.dispatch("setCurrentDatesView", {
|
||||||
|
start: event.start,
|
||||||
|
end: event.end,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onDateSelect(payload) {
|
onDateSelect(payload) {
|
||||||
console.log('onDateSelect', payload);
|
console.log("onDateSelect", payload);
|
||||||
|
|
||||||
// show an alert if changing mainUser
|
// show an alert if changing mainUser
|
||||||
if ((this.$store.getters.getMainUser !== null
|
if (
|
||||||
&& this.$store.state.me.id !== this.$store.getters.getMainUser.id)
|
(this.$store.getters.getMainUser !== null &&
|
||||||
|| this.$store.getters.getMainUser === null) {
|
this.$store.state.me.id !==
|
||||||
if (!window.confirm(this.$t('will_change_main_user_for_me'))) {
|
this.$store.getters.getMainUser.id) ||
|
||||||
|
this.$store.getters.getMainUser === null
|
||||||
|
) {
|
||||||
|
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this.$store.commit('showUserOnCalendar', {user: this.$store.state.me, remotes: true, ranges: true})
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.$store.state.me,
|
||||||
|
remotes: true,
|
||||||
|
ranges: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.dispatch('setEventTimes', {start: payload.start, end: payload.end});
|
this.$store.dispatch("setEventTimes", {
|
||||||
|
start: payload.start,
|
||||||
|
end: payload.end,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onEventChange(payload) {
|
onEventChange(payload) {
|
||||||
console.log('onEventChange', payload);
|
console.log("onEventChange", payload);
|
||||||
if (this.$store.state.activity.calendarRange !== null) {
|
if (this.$store.state.activity.calendarRange !== null) {
|
||||||
throw new Error("not allowed to edit a calendar associated with a calendar range");
|
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});
|
this.$store.dispatch("setEventTimes", {
|
||||||
|
start: payload.event.start,
|
||||||
|
end: payload.event.end,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onEventClick(payload) {
|
onEventClick(payload) {
|
||||||
if (payload.event.extendedProps.is !== 'range') {
|
if (payload.event.extendedProps.is !== "range") {
|
||||||
// do nothing when clicking on remote
|
// do nothing when clicking on remote
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// show an alert if changing mainUser
|
// show an alert if changing mainUser
|
||||||
if (this.$store.getters.getMainUser !== null
|
if (
|
||||||
&& payload.event.extendedProps.userId !== this.$store.getters.getMainUser.id) {
|
this.$store.getters.getMainUser !== null &&
|
||||||
if (!window.confirm(this.$t('this_calendar_range_will_change_main_user'))) {
|
payload.event.extendedProps.userId !==
|
||||||
|
this.$store.getters.getMainUser.id
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!window.confirm(
|
||||||
|
this.$t("this_calendar_range_will_change_main_user"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.dispatch('associateCalendarToRange', {range: payload.event});
|
this.$store.dispatch("associateCalendarToRange", {
|
||||||
|
range: payload.event,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@@ -3,68 +3,107 @@
|
|||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ user.text }}
|
{{ user.text }}
|
||||||
<template v-if="invite !== null">
|
<template v-if="invite !== null">
|
||||||
<i v-if="invite.status === 'accepted'" class="fa fa-check"></i>
|
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||||
<i v-else-if="invite.status === 'declined'" class="fa fa-times"></i>
|
<i
|
||||||
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o"></i>
|
v-else-if="invite.status === 'declined'"
|
||||||
<i v-else-if="invite.status === 'tentative'" class="fa fa-question"></i>
|
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>
|
<span v-else="">{{ invite.status }}</span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span class="form-check-inline form-switch">
|
<span class="form-check-inline form-switch">
|
||||||
<input class="form-check-input" type="checkbox" id="flexSwitchCheckDefault" v-model="rangeShow">
|
<input
|
||||||
<label class="form-check-label" for="flexSwitchCheckDefault" title="Disponibilités"><i class="fa fa-calendar-check-o"></i></label>
|
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>
|
||||||
<span class="form-check-inline form-switch">
|
<span class="form-check-inline form-switch">
|
||||||
<input class="form-check-input" type="checkbox" id="flexSwitchCheckDefault" v-model="remoteShow">
|
<input
|
||||||
<label class="form-check-label" for="flexSwitchCheckDefault" title="Agenda"><i class="fa fa-calendar"></i></label>
|
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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters} from 'vuex';
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CalendarActive",
|
name: "CalendarActive",
|
||||||
props: {
|
props: {
|
||||||
user: {
|
user: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
invite: {
|
invite: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
style() {
|
style() {
|
||||||
return {
|
return {
|
||||||
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
|
backgroundColor: this.$store.getters.getUserData(this.user)
|
||||||
|
.mainColor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
rangeShow: {
|
rangeShow: {
|
||||||
set (value) {
|
set(value) {
|
||||||
this.$store.commit('showUserOnCalendar', {user: this.user, ranges: value});
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.user,
|
||||||
|
ranges: value,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
|
return this.$store.getters.isRangeShownOnCalendarForUser(
|
||||||
}
|
this.user,
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
remoteShow: {
|
remoteShow: {
|
||||||
set (value) {
|
set(value) {
|
||||||
this.$store.commit('showUserOnCalendar', {user: this.user, remotes: value});
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.user,
|
||||||
|
remotes: value,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
|
return this.$store.getters.isRemoteShownOnCalendarForUser(
|
||||||
}
|
this.user,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
.calendar-active {
|
.calendar-active {
|
||||||
margin: 0 0.25rem 0.25rem 0;
|
margin: 0 0.25rem 0.25rem 0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import {fetchResults} from '../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods';
|
import { fetchResults } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
import {datetimeToISO} from '../../../../../ChillMainBundle/Resources/public/chill/js/date';
|
import { datetimeToISO } from "../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
import {User} from '../../../../../ChillMainBundle/Resources/public/types';
|
import { User } from "../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import {CalendarLight, CalendarRange, CalendarRemote} from '../../types';
|
import { CalendarLight, CalendarRange, CalendarRemote } from "../../types";
|
||||||
|
|
||||||
// re-export whoami
|
// 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
|
* @param Date end
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
export const fetchCalendarRangeForUser = (user: User, start: Date, end: Date): Promise<CalendarRange[]> => {
|
export const fetchCalendarRangeForUser = (
|
||||||
|
user: User,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
): Promise<CalendarRange[]> => {
|
||||||
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarRange>(uri, {dateFrom, dateTo});
|
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const fetchCalendarRemoteForUser = (user: User, start: Date, end: Date): Promise<CalendarRemote[]> => {
|
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 uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarRemote>(uri, {dateFrom, dateTo});
|
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const fetchCalendarLocalForUser = (user: User, start: Date, end: Date): Promise<CalendarLight[]> => {
|
export const fetchCalendarLocalForUser = (
|
||||||
|
user: User,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
): Promise<CalendarLight[]> => {
|
||||||
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarLight>(uri, {dateFrom, dateTo});
|
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||||
}
|
};
|
||||||
|
@@ -1,19 +1,17 @@
|
|||||||
|
const COLORS = [
|
||||||
const COLORS = [ /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||||
'#8dd3c7',
|
"#8dd3c7",
|
||||||
'#ffffb3',
|
"#ffffb3",
|
||||||
'#bebada',
|
"#bebada",
|
||||||
'#fb8072',
|
"#fb8072",
|
||||||
'#80b1d3',
|
"#80b1d3",
|
||||||
'#fdb462',
|
"#fdb462",
|
||||||
'#b3de69',
|
"#b3de69",
|
||||||
'#fccde5',
|
"#fccde5",
|
||||||
'#d9d9d9',
|
"#d9d9d9",
|
||||||
'#bc80bd',
|
"#bc80bd",
|
||||||
'#ccebc5',
|
"#ccebc5",
|
||||||
'#ffed6f'
|
"#ffed6f",
|
||||||
];
|
];
|
||||||
|
|
||||||
export {
|
export { COLORS };
|
||||||
COLORS,
|
|
||||||
};
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import {personMessages} from 'ChillPersonAssets/vuejs/_js/i18n'
|
import { personMessages } from "ChillPersonAssets/vuejs/_js/i18n";
|
||||||
import {calendarUserSelectorMessages} from '../_components/CalendarUserSelector/js/i18n';
|
import { calendarUserSelectorMessages } from "../_components/CalendarUserSelector/js/i18n";
|
||||||
import {activityMessages} from 'ChillActivityAssets/vuejs/Activity/i18n';
|
import { activityMessages } from "ChillActivityAssets/vuejs/Activity/i18n";
|
||||||
|
|
||||||
const appMessages = {
|
const appMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
@@ -13,20 +13,22 @@ const appMessages = {
|
|||||||
bloc_thirdparty: "Tiers professionnels",
|
bloc_thirdparty: "Tiers professionnels",
|
||||||
bloc_users: "T(M)S",
|
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 ?",
|
this_calendar_range_will_change_main_user:
|
||||||
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 ?",
|
"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 ?",
|
||||||
main_user_is_mandatory: "L'utilisateur principal est requis. Vous pouvez le modifier, mais pas le supprimer",
|
will_change_main_user_for_me:
|
||||||
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 ?",
|
"Vous ne pouvez pas écrire dans le calendrier d'un autre utilisateur. Voulez-vous être l'utilisateur principal de ce rendez-vous ?",
|
||||||
list_three_days: 'Liste 3 jours',
|
main_user_is_mandatory:
|
||||||
current_selected: 'Rendez-vous fixé',
|
"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",
|
main_user: "Utilisateur principal",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
Object.assign(appMessages.fr, personMessages.fr);
|
Object.assign(appMessages.fr, personMessages.fr);
|
||||||
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
|
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
|
||||||
Object.assign(appMessages.fr, activityMessages.fr);
|
Object.assign(appMessages.fr, activityMessages.fr);
|
||||||
|
|
||||||
export {
|
export { appMessages };
|
||||||
appMessages
|
|
||||||
};
|
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from "vue";
|
||||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
import { appMessages } from './i18n'
|
import { appMessages } from "./i18n";
|
||||||
import store from './store'
|
import store from "./store";
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from "./App.vue";
|
||||||
|
|
||||||
const i18n = _createI18n(appMessages);
|
const i18n = _createI18n(appMessages);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app></app>`,
|
template: `<app></app>`,
|
||||||
})
|
})
|
||||||
.use(store)
|
.use(store)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component('app', App)
|
.component("app", App)
|
||||||
.mount('#calendar');
|
.mount("#calendar");
|
||||||
|
@@ -1,14 +1,11 @@
|
|||||||
import {
|
import { addIdToValue, removeIdFromValue } from "./utils";
|
||||||
addIdToValue,
|
|
||||||
removeIdFromValue,
|
|
||||||
} from './utils';
|
|
||||||
import {
|
import {
|
||||||
fetchCalendarRangeForUser,
|
fetchCalendarRangeForUser,
|
||||||
fetchCalendarRemoteForUser,
|
fetchCalendarRemoteForUser,
|
||||||
fetchCalendarLocalForUser,
|
fetchCalendarLocalForUser,
|
||||||
} from './../api';
|
} from "./../api";
|
||||||
import {datetimeToISO} from 'ChillMainAssets/chill/js/date';
|
import { datetimeToISO } from "ChillMainAssets/chill/js/date";
|
||||||
import {postLocation} from 'ChillActivityAssets/vuejs/Activity/api';
|
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will store a unique key for each value, and prevent to launch the same
|
* 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();
|
const fetchings = new Set();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setCurrentDatesView({commit, dispatch}, {start, end}) {
|
setCurrentDatesView({ commit, dispatch }, { start, end }) {
|
||||||
commit('setCurrentDatesView', {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) {
|
if (state.currentView.start === null && state.currentView.end === null) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@@ -39,101 +36,124 @@ export default {
|
|||||||
let unique = `${uid}, ${state.currentView.start.toISOString()}, ${state.currentView.end.toISOString()}`;
|
let unique = `${uid}, ${state.currentView.start.toISOString()}, ${state.currentView.end.toISOString()}`;
|
||||||
|
|
||||||
if (fetchings.has(unique)) {
|
if (fetchings.has(unique)) {
|
||||||
console.log('prevent from fetching for a user', unique);
|
console.log("prevent from fetching for a user", unique);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchings.add(unique);
|
fetchings.add(unique);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
dispatch(
|
dispatch("fetchCalendarRangeForUser", {
|
||||||
'fetchCalendarRangeForUser',
|
user: state.usersData.get(uid).user,
|
||||||
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end}
|
start: state.currentView.start,
|
||||||
)
|
end: state.currentView.end,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
promises.push(
|
promises.push(
|
||||||
dispatch(
|
dispatch("fetchCalendarRemotesForUser", {
|
||||||
'fetchCalendarRemotesForUser',
|
user: state.usersData.get(uid).user,
|
||||||
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end}
|
start: state.currentView.start,
|
||||||
)
|
end: state.currentView.end,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
promises.push(
|
promises.push(
|
||||||
dispatch(
|
dispatch("fetchCalendarLocalsForUser", {
|
||||||
'fetchCalendarLocalsForUser',
|
user: state.usersData.get(uid).user,
|
||||||
{user: state.usersData.get(uid).user, start: state.currentView.start, end: state.currentView.end}
|
start: state.currentView.start,
|
||||||
)
|
end: state.currentView.end,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
},
|
},
|
||||||
fetchCalendarRangeForUser({commit, getters}, {user, start, end}) {
|
fetchCalendarRangeForUser({ commit, getters }, { user, start, end }) {
|
||||||
if (!getters.isCalendarRangeLoadedForUser({user, start, end})) {
|
if (!getters.isCalendarRangeLoadedForUser({ user, start, end })) {
|
||||||
return fetchCalendarRangeForUser(user, start, end).then((ranges) => {
|
return fetchCalendarRangeForUser(user, start, end).then((ranges) => {
|
||||||
commit('addCalendarRangesForUser', {user, ranges, start, end});
|
commit("addCalendarRangesForUser", { user, ranges, start, end });
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchCalendarRemotesForUser({commit, getters}, {user, start, end}) {
|
fetchCalendarRemotesForUser({ commit, getters }, { user, start, end }) {
|
||||||
if (!getters.isCalendarRemoteLoadedForUser({user, start, end})) {
|
if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) {
|
||||||
return fetchCalendarRemoteForUser(user, start, end).then((remotes) => {
|
return fetchCalendarRemoteForUser(user, start, end).then((remotes) => {
|
||||||
commit('addCalendarRemotesForUser', {user, remotes, start, end});
|
commit("addCalendarRemotesForUser", { user, remotes, start, end });
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchCalendarLocalsForUser({commit, getters}, {user, start, end}) {
|
fetchCalendarLocalsForUser({ commit, getters }, { user, start, end }) {
|
||||||
if (!getters.isCalendarRemoteLoadedForUser({user, start, end})) {
|
if (!getters.isCalendarRemoteLoadedForUser({ user, start, end })) {
|
||||||
return fetchCalendarLocalForUser(user, start, end).then((locals) => {
|
return fetchCalendarLocalForUser(user, start, end).then((locals) => {
|
||||||
commit('addCalendarLocalsForUser', {user, locals, start, end});
|
commit("addCalendarLocalsForUser", { user, locals, start, end });
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addPersonsInvolved({commit, dispatch}, payload) {
|
addPersonsInvolved({ commit, dispatch }, payload) {
|
||||||
console.log('### action addPersonsInvolved', payload.result.type);
|
console.log("### action addPersonsInvolved", payload.result.type);
|
||||||
console.log('### action addPersonsInvolved payload result', payload.result);
|
console.log("### action addPersonsInvolved payload result", payload.result);
|
||||||
switch (payload.result.type) {
|
switch (payload.result.type) {
|
||||||
case 'person':
|
case "person":
|
||||||
let aPersons = document.getElementById("chill_activitybundle_activity_persons");
|
let aPersons = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_persons",
|
||||||
|
);
|
||||||
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
|
aPersons.value = addIdToValue(aPersons.value, payload.result.id);
|
||||||
break;
|
break;
|
||||||
case 'thirdparty':
|
case "thirdparty":
|
||||||
let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals");
|
let aThirdParties = document.getElementById(
|
||||||
aThirdParties.value = addIdToValue(aThirdParties.value, payload.result.id);
|
"chill_activitybundle_activity_professionals",
|
||||||
|
);
|
||||||
|
aThirdParties.value = addIdToValue(
|
||||||
|
aThirdParties.value,
|
||||||
|
payload.result.id,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'user':
|
case "user":
|
||||||
let aUsers = document.getElementById("chill_activitybundle_activity_users");
|
let aUsers = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_users",
|
||||||
|
);
|
||||||
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
|
aUsers.value = addIdToValue(aUsers.value, payload.result.id);
|
||||||
commit('showUserOnCalendar', {user: payload.result, ranges: false, remotes: true});
|
commit("showUserOnCalendar", {
|
||||||
dispatch('fetchCalendarEvents');
|
user: payload.result,
|
||||||
|
ranges: false,
|
||||||
|
remotes: true,
|
||||||
|
});
|
||||||
|
dispatch("fetchCalendarEvents");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
;
|
commit("addPersonsInvolved", payload);
|
||||||
commit('addPersonsInvolved', payload);
|
|
||||||
},
|
},
|
||||||
removePersonInvolved({commit}, payload) {
|
removePersonInvolved({ commit }, payload) {
|
||||||
//console.log('### action removePersonInvolved', payload);
|
//console.log('### action removePersonInvolved', payload);
|
||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
case 'person':
|
case "person":
|
||||||
let aPersons = document.getElementById("chill_activitybundle_activity_persons");
|
let aPersons = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_persons",
|
||||||
|
);
|
||||||
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
|
aPersons.value = removeIdFromValue(aPersons.value, payload.id);
|
||||||
break;
|
break;
|
||||||
case 'thirdparty':
|
case "thirdparty":
|
||||||
let aThirdParties = document.getElementById("chill_activitybundle_activity_professionals");
|
let aThirdParties = document.getElementById(
|
||||||
aThirdParties.value = removeIdFromValue(aThirdParties.value, payload.id);
|
"chill_activitybundle_activity_professionals",
|
||||||
|
);
|
||||||
|
aThirdParties.value = removeIdFromValue(
|
||||||
|
aThirdParties.value,
|
||||||
|
payload.id,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'user':
|
case "user":
|
||||||
let aUsers = document.getElementById("chill_activitybundle_activity_users");
|
let aUsers = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_users",
|
||||||
|
);
|
||||||
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
|
aUsers.value = removeIdFromValue(aUsers.value, payload.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
;
|
commit("removePersonInvolved", payload);
|
||||||
commit('removePersonInvolved', payload);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Calendar
|
// Calendar
|
||||||
@@ -148,31 +168,49 @@ export default {
|
|||||||
* @param start
|
* @param start
|
||||||
* @param end
|
* @param end
|
||||||
*/
|
*/
|
||||||
setEventTimes({commit, state, getters}, {start, end}) {
|
setEventTimes({ commit, state, getters }, { start, end }) {
|
||||||
console.log('### action createEvent', {start, end});
|
console.log("### action createEvent", { start, end });
|
||||||
let startDateInput = document.getElementById("chill_activitybundle_activity_startDate");
|
let startDateInput = document.getElementById(
|
||||||
startDateInput.value = null !== start ? datetimeToISO(start) : '';
|
"chill_activitybundle_activity_startDate",
|
||||||
let endDateInput = document.getElementById("chill_activitybundle_activity_endDate");
|
);
|
||||||
endDateInput.value = null !== end ? datetimeToISO(end) : '';
|
startDateInput.value = null !== start ? datetimeToISO(start) : "";
|
||||||
let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange");
|
let endDateInput = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_endDate",
|
||||||
|
);
|
||||||
|
endDateInput.value = null !== end ? datetimeToISO(end) : "";
|
||||||
|
let calendarRangeInput = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_calendarRange",
|
||||||
|
);
|
||||||
calendarRangeInput.value = "";
|
calendarRangeInput.value = "";
|
||||||
|
|
||||||
if (getters.getMainUser === null || getters.getMainUser.id !== state.me.id) {
|
if (
|
||||||
let mainUserInput = document.getElementById("chill_activitybundle_activity_mainUser");
|
getters.getMainUser === null ||
|
||||||
|
getters.getMainUser.id !== state.me.id
|
||||||
|
) {
|
||||||
|
let mainUserInput = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_mainUser",
|
||||||
|
);
|
||||||
mainUserInput.value = state.me.id;
|
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}) {
|
associateCalendarToRange({ state, commit, dispatch, getters }, { range }) {
|
||||||
console.log('### action associateCAlendarToRange', range);
|
console.log("### action associateCAlendarToRange", range);
|
||||||
let startDateInput = document.getElementById("chill_activitybundle_activity_startDate");
|
let startDateInput = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_startDate",
|
||||||
|
);
|
||||||
startDateInput.value = null !== range ? datetimeToISO(range.start) : "";
|
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) : "";
|
endDateInput.value = null !== range ? datetimeToISO(range.end) : "";
|
||||||
let calendarRangeInput = document.getElementById("chill_activitybundle_activity_calendarRange");
|
let calendarRangeInput = document.getElementById(
|
||||||
calendarRangeInput.value = null !== range ? Number(range.extendedProps.calendarRangeId) : "";
|
"chill_activitybundle_activity_calendarRange",
|
||||||
|
);
|
||||||
|
calendarRangeInput.value =
|
||||||
|
null !== range ? Number(range.extendedProps.calendarRangeId) : "";
|
||||||
|
|
||||||
if (null !== range) {
|
if (null !== range) {
|
||||||
let location = getters.getLocationById(range.extendedProps.locationId);
|
let location = getters.getLocationById(range.extendedProps.locationId);
|
||||||
@@ -181,63 +219,68 @@ export default {
|
|||||||
console.error("location not found!", range.extendedProps.locationId);
|
console.error("location not found!", range.extendedProps.locationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch('updateLocation', location);
|
dispatch("updateLocation", location);
|
||||||
|
|
||||||
const userId = range.extendedProps.userId;
|
const userId = range.extendedProps.userId;
|
||||||
if (state.activity.mainUser !== null && state.activity.mainUser.id !== userId) {
|
if (
|
||||||
dispatch('setMainUser', state.usersData.get(userId).user);
|
state.activity.mainUser !== null &&
|
||||||
|
state.activity.mainUser.id !== userId
|
||||||
|
) {
|
||||||
|
dispatch("setMainUser", state.usersData.get(userId).user);
|
||||||
|
|
||||||
// TODO: remove persons involved with this user
|
// TODO: remove persons involved with this user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commit('associateCalendarToRange', {range});
|
commit("associateCalendarToRange", { range });
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
setMainUser({commit, dispatch, state}, mainUser) {
|
setMainUser({ commit, dispatch, state }, mainUser) {
|
||||||
console.log('setMainUser', 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);
|
mainUserInput.value = Number(mainUser.id);
|
||||||
|
|
||||||
return dispatch('associateCalendarToRange', { range: null }).then(() => {
|
return dispatch("associateCalendarToRange", { range: null }).then(() => {
|
||||||
commit('setMainUser', mainUser);
|
commit("setMainUser", mainUser);
|
||||||
|
|
||||||
return dispatch('fetchCalendarEvents');
|
return dispatch("fetchCalendarEvents");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
updateLocation({commit}, value) {
|
updateLocation({ commit }, value) {
|
||||||
console.log('### action: updateLocation', value);
|
console.log("### action: updateLocation", value);
|
||||||
let hiddenLocation = document.getElementById("chill_activitybundle_activity_location");
|
let hiddenLocation = document.getElementById(
|
||||||
|
"chill_activitybundle_activity_location",
|
||||||
|
);
|
||||||
if (value.onthefly) {
|
if (value.onthefly) {
|
||||||
const body = {
|
const body = {
|
||||||
"type": "location",
|
type: "location",
|
||||||
"name": value.name === '__AccompanyingCourseLocation__' ? null : value.name,
|
name:
|
||||||
"locationType": {
|
value.name === "__AccompanyingCourseLocation__" ? null : value.name,
|
||||||
"id": value.locationType.id,
|
locationType: {
|
||||||
"type": "location-type"
|
id: value.locationType.id,
|
||||||
}
|
type: "location-type",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (value.address.id) {
|
if (value.address.id) {
|
||||||
Object.assign(body, {
|
Object.assign(body, {
|
||||||
"address": {
|
address: {
|
||||||
"id": value.address.id
|
id: value.address.id,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
postLocation(body)
|
postLocation(body)
|
||||||
.then(
|
.then((location) => (hiddenLocation.value = location.id))
|
||||||
location => hiddenLocation.value = location.id
|
.catch((err) => {
|
||||||
).catch(
|
|
||||||
err => {
|
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
hiddenLocation.value = value.id;
|
hiddenLocation.value = value.id;
|
||||||
}
|
}
|
||||||
commit("updateLocation", value);
|
commit("updateLocation", value);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
@@ -18,7 +18,7 @@ export default {
|
|||||||
if (null === state.activity.start) {
|
if (null === state.activity.start) {
|
||||||
return new Date();
|
return new Date();
|
||||||
}
|
}
|
||||||
throw 'transform date to object ?';
|
throw "transform date to object ?";
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Compute the event sources to show on the FullCalendar
|
* Compute the event sources to show on the FullCalendar
|
||||||
@@ -33,7 +33,7 @@ export default {
|
|||||||
// current calendar
|
// current calendar
|
||||||
if (state.activity.startDate !== null && state.activity.endDate !== null) {
|
if (state.activity.startDate !== null && state.activity.endDate !== null) {
|
||||||
const s = {
|
const s = {
|
||||||
id: 'current',
|
id: "current",
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
title: "Rendez-vous",
|
title: "Rendez-vous",
|
||||||
@@ -41,8 +41,8 @@ export default {
|
|||||||
end: state.activity.endDate,
|
end: state.activity.endDate,
|
||||||
allDay: false,
|
allDay: false,
|
||||||
is: "current",
|
is: "current",
|
||||||
classNames: ['iscurrent'],
|
classNames: ["iscurrent"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
editable: state.activity.calendarRange === null,
|
editable: state.activity.calendarRange === null,
|
||||||
};
|
};
|
||||||
@@ -52,7 +52,7 @@ export default {
|
|||||||
|
|
||||||
for (const [userId, kinds] of state.currentView.users.entries()) {
|
for (const [userId, kinds] of state.currentView.users.entries()) {
|
||||||
if (!state.usersData.has(userId)) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,11 +61,16 @@ export default {
|
|||||||
if (kinds.ranges && userData.calendarRanges.length > 0) {
|
if (kinds.ranges && userData.calendarRanges.length > 0) {
|
||||||
const s = {
|
const s = {
|
||||||
id: `ranges_${userId}`,
|
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,
|
color: userData.mainColor,
|
||||||
classNames: ['isrange'],
|
classNames: ["isrange"],
|
||||||
backgroundColor: 'white',
|
backgroundColor: "white",
|
||||||
textColor: 'black',
|
textColor: "black",
|
||||||
editable: false,
|
editable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,10 +79,10 @@ export default {
|
|||||||
|
|
||||||
if (kinds.remotes && userData.remotes.length > 0) {
|
if (kinds.remotes && userData.remotes.length > 0) {
|
||||||
const s = {
|
const s = {
|
||||||
'id': `remote_${userId}`,
|
id: `remote_${userId}`,
|
||||||
events: userData.remotes,
|
events: userData.remotes,
|
||||||
color: userData.mainColor,
|
color: userData.mainColor,
|
||||||
textColor: 'black',
|
textColor: "black",
|
||||||
editable: false,
|
editable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,10 +92,12 @@ export default {
|
|||||||
// if remotes is checked, we display also the locals calendars
|
// if remotes is checked, we display also the locals calendars
|
||||||
if (kinds.remotes && userData.locals.length > 0) {
|
if (kinds.remotes && userData.locals.length > 0) {
|
||||||
const s = {
|
const s = {
|
||||||
'id': `local_${userId}`,
|
id: `local_${userId}`,
|
||||||
events: userData.locals.filter(l => l.originId !== state.activity.id),
|
events: userData.locals.filter(
|
||||||
|
(l) => l.originId !== state.activity.id,
|
||||||
|
),
|
||||||
color: userData.mainColor,
|
color: userData.mainColor,
|
||||||
textColor: 'black',
|
textColor: "black",
|
||||||
editable: false,
|
editable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,7 +111,7 @@ export default {
|
|||||||
return state.activity.startDate;
|
return state.activity.startDate;
|
||||||
},
|
},
|
||||||
getInviteForUser: (state) => (user) => {
|
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
|
* get the user data for a specific user
|
||||||
@@ -138,7 +145,9 @@ export default {
|
|||||||
* @param getters
|
* @param getters
|
||||||
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
|
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
|
||||||
*/
|
*/
|
||||||
isCalendarRangeLoadedForUser: (state, getters) => ({user, start, end}) => {
|
isCalendarRangeLoadedForUser:
|
||||||
|
(state, getters) =>
|
||||||
|
({ user, start, end }) => {
|
||||||
if (!getters.hasUserData(user)) {
|
if (!getters.hasUserData(user)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -159,7 +168,9 @@ export default {
|
|||||||
* @param getters
|
* @param getters
|
||||||
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
|
* @returns {(function({user: *, start: *, end: *}): (boolean))|*}
|
||||||
*/
|
*/
|
||||||
isCalendarRemoteLoadedForUser: (state, getters) => ({user, start, end}) => {
|
isCalendarRemoteLoadedForUser:
|
||||||
|
(state, getters) =>
|
||||||
|
({ user, start, end }) => {
|
||||||
if (!getters.hasUserData(user)) {
|
if (!getters.hasUserData(user)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -180,8 +191,10 @@ export default {
|
|||||||
*/
|
*/
|
||||||
isRangeShownOnCalendarForUser: (state) => (user) => {
|
isRangeShownOnCalendarForUser: (state) => (user) => {
|
||||||
const k = state.currentView.users.get(user.id);
|
const k = state.currentView.users.get(user.id);
|
||||||
if (typeof k === 'undefined') {
|
if (typeof k === "undefined") {
|
||||||
console.error('try to determinate if calendar range is shown and user is not in currentView');
|
console.error(
|
||||||
|
"try to determinate if calendar range is shown and user is not in currentView",
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,8 +208,10 @@ export default {
|
|||||||
*/
|
*/
|
||||||
isRemoteShownOnCalendarForUser: (state) => (user) => {
|
isRemoteShownOnCalendarForUser: (state) => (user) => {
|
||||||
const k = state.currentView.users.get(user.id);
|
const k = state.currentView.users.get(user.id);
|
||||||
if (typeof k === 'undefined') {
|
if (typeof k === "undefined") {
|
||||||
console.error('try to determinate if calendar range is shown and user is not in currentView');
|
console.error(
|
||||||
|
"try to determinate if calendar range is shown and user is not in currentView",
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,8 +220,8 @@ export default {
|
|||||||
|
|
||||||
getLocationById: (state) => (id) => {
|
getLocationById: (state) => (id) => {
|
||||||
for (let group of state.availableLocations) {
|
for (let group of state.availableLocations) {
|
||||||
console.log('group', group);
|
console.log("group", group);
|
||||||
const found = group.locations.find(l => l.id === id);
|
const found = group.locations.find((l) => l.id === id);
|
||||||
if (typeof found !== "undefined") {
|
if (typeof found !== "undefined") {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
@@ -216,57 +231,60 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
suggestedEntities(state, getters) {
|
suggestedEntities(state, getters) {
|
||||||
if (typeof (state.activity.accompanyingPeriod) === 'undefined') {
|
if (typeof state.activity.accompanyingPeriod === "undefined") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const allEntities = [
|
const allEntities = [
|
||||||
...getters.suggestedPersons,
|
...getters.suggestedPersons,
|
||||||
...getters.suggestedRequestor,
|
...getters.suggestedRequestor,
|
||||||
...getters.suggestedUser,
|
...getters.suggestedUser,
|
||||||
...getters.suggestedResources
|
...getters.suggestedResources,
|
||||||
];
|
];
|
||||||
const uniqueIds = [...new Set(allEntities.map(i => `${i.type}-${i.id}`))];
|
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]);
|
return Array.from(
|
||||||
|
uniqueIds,
|
||||||
|
(id) => allEntities.filter((r) => `${r.type}-${r.id}` === id)[0],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
suggestedPersons(state) {
|
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
|
return state.activity.accompanyingPeriod.participations
|
||||||
.filter(p => p.endDate === null)
|
.filter((p) => p.endDate === null)
|
||||||
.map(p => p.person)
|
.map((p) => p.person)
|
||||||
.filter(p => !existingPersonIds.includes(p.id))
|
.filter((p) => !existingPersonIds.includes(p.id));
|
||||||
},
|
},
|
||||||
suggestedRequestor(state) {
|
suggestedRequestor(state) {
|
||||||
if (state.activity.accompanyingPeriod.requestor === null) {
|
if (state.activity.accompanyingPeriod.requestor === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingPersonIds = state.activity.persons.map(p => p.id);
|
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||||
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id);
|
const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
|
||||||
return [state.activity.accompanyingPeriod.requestor]
|
return [state.activity.accompanyingPeriod.requestor].filter(
|
||||||
.filter(r =>
|
(r) =>
|
||||||
(r.type === 'person' && !existingPersonIds.includes(r.id)) ||
|
(r.type === "person" && !existingPersonIds.includes(r.id)) ||
|
||||||
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id))
|
(r.type === "thirdparty" && !existingThirdPartyIds.includes(r.id)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestedUser(state) {
|
suggestedUser(state) {
|
||||||
if (null === state.activity.users) {
|
if (null === state.activity.users) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const existingUserIds = state.activity.users.map(p => p.id);
|
const existingUserIds = state.activity.users.map((p) => p.id);
|
||||||
return [state.activity.accompanyingPeriod.user]
|
return [state.activity.accompanyingPeriod.user].filter(
|
||||||
.filter(
|
(u) => u !== null && !existingUserIds.includes(u.id),
|
||||||
u => u !== null && !existingUserIds.includes(u.id)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestedResources(state) {
|
suggestedResources(state) {
|
||||||
const resources = state.activity.accompanyingPeriod.resources;
|
const resources = state.activity.accompanyingPeriod.resources;
|
||||||
const existingPersonIds = state.activity.persons.map(p => p.id);
|
const existingPersonIds = state.activity.persons.map((p) => p.id);
|
||||||
const existingThirdPartyIds = state.activity.thirdParties.map(p => p.id);
|
const existingThirdPartyIds = state.activity.thirdParties.map((p) => p.id);
|
||||||
return state.activity.accompanyingPeriod.resources
|
return state.activity.accompanyingPeriod.resources
|
||||||
.map(r => r.resource)
|
.map((r) => r.resource)
|
||||||
.filter(r =>
|
.filter(
|
||||||
(r.type === 'person' && !existingPersonIds.includes(r.id)) ||
|
(r) =>
|
||||||
(r.type === 'thirdparty' && !existingThirdPartyIds.includes(r.id))
|
(r.type === "person" && !existingPersonIds.includes(r.id)) ||
|
||||||
|
(r.type === "thirdparty" && !existingThirdPartyIds.includes(r.id)),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import 'es6-promise/auto';
|
import "es6-promise/auto";
|
||||||
import { createStore } from 'vuex';
|
import { createStore } from "vuex";
|
||||||
import { postLocation } from 'ChillActivityAssets/vuejs/Activity/api';
|
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
|
||||||
import getters from './getters';
|
import getters from "./getters";
|
||||||
import actions from './actions';
|
import actions from "./actions";
|
||||||
import mutations from './mutations';
|
import mutations from "./mutations";
|
||||||
import { mapEntity } from './utils';
|
import { mapEntity } from "./utils";
|
||||||
import { whoami } from '../api';
|
import { whoami } from "../api";
|
||||||
import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations";
|
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({
|
const store = createStore({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
@@ -42,16 +42,24 @@ const store = createStore({
|
|||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
|
||||||
whoami().then(me => {
|
whoami().then((me) => {
|
||||||
store.commit('setWhoAmiI', me);
|
store.commit("setWhoAmiI", me);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (null !== store.getters.getMainUser) {
|
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) {
|
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);
|
prepareLocations(store);
|
||||||
|
@@ -3,30 +3,27 @@ import {
|
|||||||
calendarRangeToFullCalendarEvent,
|
calendarRangeToFullCalendarEvent,
|
||||||
remoteToFullCalendarEvent,
|
remoteToFullCalendarEvent,
|
||||||
localsToFullCalendarEvent,
|
localsToFullCalendarEvent,
|
||||||
} from './utils';
|
} from "./utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setWhoAmiI(state, me) {
|
setWhoAmiI(state, me) {
|
||||||
state.me = me;
|
state.me = me;
|
||||||
},
|
},
|
||||||
setCurrentDatesView(state, {start, end}) {
|
setCurrentDatesView(state, { start, end }) {
|
||||||
state.currentView.start = start;
|
state.currentView.start = start;
|
||||||
state.currentView.end = end;
|
state.currentView.end = end;
|
||||||
},
|
},
|
||||||
showUserOnCalendar(state, {user, ranges, remotes}) {
|
showUserOnCalendar(state, { user, ranges, remotes }) {
|
||||||
if (!state.usersData.has(user.id)) {
|
if (!state.usersData.has(user.id)) {
|
||||||
state.usersData.set(user.id, createUserData(user, state.usersData.size));
|
state.usersData.set(user.id, createUserData(user, state.usersData.size));
|
||||||
}
|
}
|
||||||
|
|
||||||
const cur = state.currentView.users.get(user.id);
|
const cur = state.currentView.users.get(user.id);
|
||||||
|
|
||||||
state.currentView.users.set(
|
state.currentView.users.set(user.id, {
|
||||||
user.id,
|
ranges: typeof ranges !== "undefined" ? ranges : cur.ranges,
|
||||||
{
|
remotes: typeof remotes !== "undefined" ? remotes : cur.remotes,
|
||||||
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,
|
* Set the event start and end to the given start and end,
|
||||||
@@ -36,7 +33,7 @@ export default {
|
|||||||
* @param Date start
|
* @param Date start
|
||||||
* @param Date end
|
* @param Date end
|
||||||
*/
|
*/
|
||||||
setEventTimes(state, {start, end}) {
|
setEventTimes(state, { start, end }) {
|
||||||
state.activity.startDate = start;
|
state.activity.startDate = start;
|
||||||
state.activity.endDate = end;
|
state.activity.endDate = end;
|
||||||
state.activity.calendarRange = null;
|
state.activity.calendarRange = null;
|
||||||
@@ -48,8 +45,8 @@ export default {
|
|||||||
* @param state
|
* @param state
|
||||||
* @param range
|
* @param range
|
||||||
*/
|
*/
|
||||||
associateCalendarToRange(state, {range}) {
|
associateCalendarToRange(state, { range }) {
|
||||||
console.log('associateCalendarToRange', range);
|
console.log("associateCalendarToRange", range);
|
||||||
|
|
||||||
if (null === range) {
|
if (null === range) {
|
||||||
state.activity.calendarRange = null;
|
state.activity.calendarRange = null;
|
||||||
@@ -59,22 +56,25 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('userId', range.extendedProps.userId);
|
console.log("userId", range.extendedProps.userId);
|
||||||
|
|
||||||
const r = state.usersData.get(range.extendedProps.userId).calendarRanges
|
const r = state.usersData
|
||||||
.find(r => r.calendarRangeId === range.extendedProps.calendarRangeId);
|
.get(range.extendedProps.userId)
|
||||||
|
.calendarRanges.find(
|
||||||
|
(r) => r.calendarRangeId === range.extendedProps.calendarRangeId,
|
||||||
|
);
|
||||||
|
|
||||||
if (typeof r === 'undefined') {
|
if (typeof r === "undefined") {
|
||||||
throw Error('Could not find managed calendar range');
|
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.startDate = range.start;
|
||||||
state.activity.endDate = range.end;
|
state.activity.endDate = range.end;
|
||||||
state.activity.calendarRange = r;
|
state.activity.calendarRange = r;
|
||||||
|
|
||||||
console.log('activity', state.activity);
|
console.log("activity", state.activity);
|
||||||
},
|
},
|
||||||
|
|
||||||
setMainUser(state, user) {
|
setMainUser(state, user) {
|
||||||
@@ -85,32 +85,36 @@ export default {
|
|||||||
addPersonsInvolved(state, payload) {
|
addPersonsInvolved(state, payload) {
|
||||||
//console.log('### mutation addPersonsInvolved', payload.result.type);
|
//console.log('### mutation addPersonsInvolved', payload.result.type);
|
||||||
switch (payload.result.type) {
|
switch (payload.result.type) {
|
||||||
case 'person':
|
case "person":
|
||||||
state.activity.persons.push(payload.result);
|
state.activity.persons.push(payload.result);
|
||||||
break;
|
break;
|
||||||
case 'thirdparty':
|
case "thirdparty":
|
||||||
state.activity.thirdParties.push(payload.result);
|
state.activity.thirdParties.push(payload.result);
|
||||||
break;
|
break;
|
||||||
case 'user':
|
case "user":
|
||||||
state.activity.users.push(payload.result);
|
state.activity.users.push(payload.result);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
;
|
|
||||||
},
|
},
|
||||||
removePersonInvolved(state, payload) {
|
removePersonInvolved(state, payload) {
|
||||||
//console.log('### mutation removePersonInvolved', payload.type);
|
//console.log('### mutation removePersonInvolved', payload.type);
|
||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
case 'person':
|
case "person":
|
||||||
state.activity.persons = state.activity.persons.filter(person => person !== payload);
|
state.activity.persons = state.activity.persons.filter(
|
||||||
|
(person) => person !== payload,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'thirdparty':
|
case "thirdparty":
|
||||||
state.activity.thirdParties = state.activity.thirdParties.filter(thirdparty => thirdparty !== payload);
|
state.activity.thirdParties = state.activity.thirdParties.filter(
|
||||||
|
(thirdparty) => thirdparty !== payload,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'user':
|
case "user":
|
||||||
state.activity.users = state.activity.users.filter(user => user !== payload);
|
state.activity.users = state.activity.users.filter(
|
||||||
|
(user) => user !== payload,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
;
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Add CalendarRange object for an user
|
* Add CalendarRange object for an user
|
||||||
@@ -121,7 +125,7 @@ export default {
|
|||||||
* @param start
|
* @param start
|
||||||
* @param end
|
* @param end
|
||||||
*/
|
*/
|
||||||
addCalendarRangesForUser(state, {user, ranges, start, end}) {
|
addCalendarRangesForUser(state, { user, ranges, start, end }) {
|
||||||
let userData;
|
let userData;
|
||||||
if (state.usersData.has(user.id)) {
|
if (state.usersData.has(user.id)) {
|
||||||
userData = state.usersData.get(user.id);
|
userData = state.usersData.get(user.id);
|
||||||
@@ -131,18 +135,18 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const eventRanges = ranges
|
const eventRanges = ranges
|
||||||
.filter(r => !state.existingEvents.has(`range_${r.id}`))
|
.filter((r) => !state.existingEvents.has(`range_${r.id}`))
|
||||||
.map(r => {
|
.map((r) => {
|
||||||
// add to existing ids
|
// add to existing ids
|
||||||
state.existingEvents.add(`range_${r.id}`);
|
state.existingEvents.add(`range_${r.id}`);
|
||||||
return r;
|
return r;
|
||||||
})
|
})
|
||||||
.map(r => calendarRangeToFullCalendarEvent(r));
|
.map((r) => calendarRangeToFullCalendarEvent(r));
|
||||||
|
|
||||||
userData.calendarRanges = userData.calendarRanges.concat(eventRanges);
|
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;
|
let userData;
|
||||||
if (state.usersData.has(user.id)) {
|
if (state.usersData.has(user.id)) {
|
||||||
userData = state.usersData.get(user.id);
|
userData = state.usersData.get(user.id);
|
||||||
@@ -152,18 +156,18 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const eventRemotes = remotes
|
const eventRemotes = remotes
|
||||||
.filter(r => !state.existingEvents.has(`remote_${r.id}`))
|
.filter((r) => !state.existingEvents.has(`remote_${r.id}`))
|
||||||
.map(r => {
|
.map((r) => {
|
||||||
// add to existing ids
|
// add to existing ids
|
||||||
state.existingEvents.add(`remote_${r.id}`);
|
state.existingEvents.add(`remote_${r.id}`);
|
||||||
return r;
|
return r;
|
||||||
})
|
})
|
||||||
.map(r => remoteToFullCalendarEvent(r));
|
.map((r) => remoteToFullCalendarEvent(r));
|
||||||
|
|
||||||
userData.remotes = userData.remotes.concat(eventRemotes);
|
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;
|
let userData;
|
||||||
if (state.usersData.has(user.id)) {
|
if (state.usersData.has(user.id)) {
|
||||||
userData = state.usersData.get(user.id);
|
userData = state.usersData.get(user.id);
|
||||||
@@ -173,20 +177,20 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const eventRemotes = locals
|
const eventRemotes = locals
|
||||||
.filter(r => !state.existingEvents.has(`locals_${r.id}`))
|
.filter((r) => !state.existingEvents.has(`locals_${r.id}`))
|
||||||
.map(r => {
|
.map((r) => {
|
||||||
// add to existing ids
|
// add to existing ids
|
||||||
state.existingEvents.add(`locals_${r.id}`);
|
state.existingEvents.add(`locals_${r.id}`);
|
||||||
return r;
|
return r;
|
||||||
})
|
})
|
||||||
.map(r => localsToFullCalendarEvent(r));
|
.map((r) => localsToFullCalendarEvent(r));
|
||||||
|
|
||||||
userData.locals = userData.locals.concat(eventRemotes);
|
userData.locals = userData.locals.concat(eventRemotes);
|
||||||
userData.localsLoaded.push({start, end});
|
userData.localsLoaded.push({ start, end });
|
||||||
},
|
},
|
||||||
// Location
|
// Location
|
||||||
updateLocation(state, value) {
|
updateLocation(state, value) {
|
||||||
console.log('### mutation: updateLocation', value);
|
console.log("### mutation: updateLocation", value);
|
||||||
state.activity.location = value;
|
state.activity.location = value;
|
||||||
},
|
},
|
||||||
addAvailableLocationGroup(state, group) {
|
addAvailableLocationGroup(state, group) {
|
||||||
|
@@ -1,43 +1,46 @@
|
|||||||
import {COLORS} from '../const';
|
import { COLORS } from "../const";
|
||||||
import {ISOToDatetime} from '../../../../../../ChillMainBundle/Resources/public/chill/js/date';
|
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
import {DateTime, User} from '../../../../../../ChillMainBundle/Resources/public/types';
|
import {
|
||||||
import {CalendarLight, CalendarRange, CalendarRemote} from '../../../types';
|
DateTime,
|
||||||
import type {EventInputCalendarRange} from '../../../types';
|
User,
|
||||||
import {EventInput} from '@fullcalendar/core';
|
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
|
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
|
||||||
|
import type { EventInputCalendarRange } from "../../../types";
|
||||||
|
import { EventInput } from "@fullcalendar/core";
|
||||||
|
|
||||||
export interface UserData {
|
export interface UserData {
|
||||||
user: User,
|
user: User;
|
||||||
calendarRanges: CalendarRange[],
|
calendarRanges: CalendarRange[];
|
||||||
calendarRangesLoaded: {}[],
|
calendarRangesLoaded: {}[];
|
||||||
remotes: CalendarRemote[],
|
remotes: CalendarRemote[];
|
||||||
remotesLoaded: {}[],
|
remotesLoaded: {}[];
|
||||||
locals: CalendarRemote[],
|
locals: CalendarRemote[];
|
||||||
localsLoaded: {}[],
|
localsLoaded: {}[];
|
||||||
mainColor: string,
|
mainColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addIdToValue = (string: string, id: number): string => {
|
export const addIdToValue = (string: string, id: number): string => {
|
||||||
let array = string ? string.split(',') : [];
|
const array = string ? string.split(",") : [];
|
||||||
array.push(id.toString());
|
array.push(id.toString());
|
||||||
let str = array.join();
|
const str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeIdFromValue = (string: string, id: number) => {
|
export const removeIdFromValue = (string: string, id: number) => {
|
||||||
let array = string.split(',');
|
let array = string.split(",");
|
||||||
array = array.filter(el => el !== id.toString());
|
array = array.filter((el) => el !== id.toString());
|
||||||
let str = array.join();
|
const str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assign missing keys for the ConcernedGroups component
|
* Assign missing keys for the ConcernedGroups component
|
||||||
*/
|
*/
|
||||||
export const mapEntity = (entity: EventInput): EventInput => {
|
export const mapEntity = (entity: EventInput): EventInput => {
|
||||||
let calendar = { ...entity};
|
const calendar = { ...entity };
|
||||||
Object.assign(calendar, {thirdParties: entity.professionals});
|
Object.assign(calendar, { thirdParties: entity.professionals });
|
||||||
|
|
||||||
if (entity.startDate !== null ) {
|
if (entity.startDate !== null) {
|
||||||
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
||||||
}
|
}
|
||||||
if (entity.endDate !== null) {
|
if (entity.endDate !== null) {
|
||||||
@@ -64,11 +67,13 @@ export const createUserData = (user: User, colorIndex: number): UserData => {
|
|||||||
locals: [],
|
locals: [],
|
||||||
localsLoaded: [],
|
localsLoaded: [],
|
||||||
mainColor: COLORS[colorId],
|
mainColor: COLORS[colorId],
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
||||||
export const calendarRangeToFullCalendarEvent = (entity: CalendarRange): EventInputCalendarRange => {
|
export const calendarRangeToFullCalendarEvent = (
|
||||||
|
entity: CalendarRange,
|
||||||
|
): EventInputCalendarRange => {
|
||||||
return {
|
return {
|
||||||
id: `range_${entity.id}`,
|
id: `range_${entity.id}`,
|
||||||
title: "(" + entity.user.text + ")",
|
title: "(" + entity.user.text + ")",
|
||||||
@@ -80,29 +85,33 @@ export const calendarRangeToFullCalendarEvent = (entity: CalendarRange): EventIn
|
|||||||
calendarRangeId: entity.id,
|
calendarRangeId: entity.id,
|
||||||
locationId: entity.location.id,
|
locationId: entity.location.id,
|
||||||
locationName: entity.location.name,
|
locationName: entity.location.name,
|
||||||
is: 'range',
|
is: "range",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const remoteToFullCalendarEvent = (entity: CalendarRemote): EventInput & {id: string} => {
|
export const remoteToFullCalendarEvent = (
|
||||||
|
entity: CalendarRemote,
|
||||||
|
): EventInput & { id: string } => {
|
||||||
return {
|
return {
|
||||||
id: `range_${entity.id}`,
|
id: `range_${entity.id}`,
|
||||||
title: entity.title,
|
title: entity.title,
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: entity.isAllDay,
|
allDay: entity.isAllDay,
|
||||||
is: 'remote',
|
is: "remote",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const localsToFullCalendarEvent = (entity: CalendarLight): EventInput & {id: string; originId: number;} => {
|
export const localsToFullCalendarEvent = (
|
||||||
|
entity: CalendarLight,
|
||||||
|
): EventInput & { id: string; originId: number } => {
|
||||||
return {
|
return {
|
||||||
id: `local_${entity.id}`,
|
id: `local_${entity.id}`,
|
||||||
title: entity.persons.map(p => p.text).join(', '),
|
title: entity.persons.map((p) => p.text).join(", "),
|
||||||
originId: entity.id,
|
originId: entity.id,
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: false,
|
allDay: false,
|
||||||
is: 'local',
|
is: "local",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
@@ -1,61 +1,99 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button id="btnGroupDrop1" type="button" class="btn btn-misc dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button
|
||||||
|
id="btnGroupDrop1"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-misc dropdown-toggle"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
<template v-if="status === Statuses.PENDING">
|
<template v-if="status === Statuses.PENDING">
|
||||||
<span class="fa fa-hourglass"></span> {{ $t('Give_an_answer')}}
|
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="status === Statuses.ACCEPTED">
|
<template v-else-if="status === Statuses.ACCEPTED">
|
||||||
<span class="fa fa-check"></span> {{ $t('Accepted')}}
|
<span class="fa fa-check"></span> {{ $t("Accepted") }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="status === Statuses.DECLINED">
|
<template v-else-if="status === Statuses.DECLINED">
|
||||||
<span class="fa fa-times"></span> {{ $t('Declined')}}
|
<span class="fa fa-times"></span> {{ $t("Declined") }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
||||||
<span class="fa fa-question"></span> {{ $t('Tentative')}}
|
<span class="fa fa-question"></span> {{ $t("Tentative") }}
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
<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.ACCEPTED">
|
||||||
<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>
|
<a
|
||||||
<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>
|
class="dropdown-item"
|
||||||
<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>
|
@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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent, PropType} from 'vue';
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
const ACCEPTED = 'accepted';
|
const ACCEPTED = "accepted";
|
||||||
const DECLINED = 'declined';
|
const DECLINED = "declined";
|
||||||
const PENDING = 'pending';
|
const PENDING = "pending";
|
||||||
const TENTATIVELY_ACCEPTED = 'tentative';
|
const TENTATIVELY_ACCEPTED = "tentative";
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
messages: {
|
messages: {
|
||||||
fr: {
|
fr: {
|
||||||
"Give_an_answer": "Répondre",
|
Give_an_answer: "Répondre",
|
||||||
"Accepted": "Accepté",
|
Accepted: "Accepté",
|
||||||
"Declined": "Refusé",
|
Declined: "Refusé",
|
||||||
"Tentative": "Accepté provisoirement",
|
Tentative: "Accepté provisoirement",
|
||||||
"Accept": "Accepter",
|
Accept: "Accepter",
|
||||||
"Decline": "Refuser",
|
Decline: "Refuser",
|
||||||
"Tentatively_accept": "Accepter provisoirement",
|
Tentatively_accept: "Accepter provisoirement",
|
||||||
"Set_pending": "Ne pas répondre",
|
Set_pending: "Ne pas répondre",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Answer",
|
name: "Answer",
|
||||||
i18n,
|
i18n,
|
||||||
props: {
|
props: {
|
||||||
calendarId: { type: Number, required: true},
|
calendarId: { type: Number, required: true },
|
||||||
status: {type: String as PropType<"accepted" | "declined" | "pending" | "tentative">, required: true},
|
status: {
|
||||||
|
type: String as PropType<
|
||||||
|
"accepted" | "declined" | "pending" | "tentative"
|
||||||
|
>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: {
|
emits: {
|
||||||
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
|
statusChanged(
|
||||||
|
payload: "accepted" | "declined" | "pending" | "tentative",
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -67,29 +105,29 @@ export default defineComponent({
|
|||||||
PENDING,
|
PENDING,
|
||||||
TENTATIVELY_ACCEPTED,
|
TENTATIVELY_ACCEPTED,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
changeStatus: function (newStatus: "accepted" | "declined" | "pending" | "tentative") {
|
changeStatus: function (
|
||||||
console.log('changeStatus', newStatus);
|
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
||||||
|
) {
|
||||||
|
console.log("changeStatus", newStatus);
|
||||||
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
|
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
|
||||||
window.fetch(url, {
|
window
|
||||||
method: 'POST',
|
.fetch(url, {
|
||||||
}).then((r: Response) => {
|
method: "POST",
|
||||||
|
})
|
||||||
|
.then((r: Response) => {
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
console.error('could not confirm answer', newStatus);
|
console.error("could not confirm answer", newStatus);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('answer sent', newStatus);
|
console.log("answer sent", newStatus);
|
||||||
this.$emit('statusChanged', newStatus);
|
this.$emit("statusChanged", newStatus);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
@@ -23,14 +23,22 @@
|
|||||||
<label class="input-group-text" for="slotDuration"
|
<label class="input-group-text" for="slotDuration"
|
||||||
>Durée des créneaux</label
|
>Durée des créneaux</label
|
||||||
>
|
>
|
||||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
<select
|
||||||
|
v-model="slotDuration"
|
||||||
|
id="slotDuration"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option value="00:05:00">5 minutes</option>
|
<option value="00:05:00">5 minutes</option>
|
||||||
<option value="00:10:00">10 minutes</option>
|
<option value="00:10:00">10 minutes</option>
|
||||||
<option value="00:15:00">15 minutes</option>
|
<option value="00:15:00">15 minutes</option>
|
||||||
<option value="00:30:00">30 minutes</option>
|
<option value="00:30:00">30 minutes</option>
|
||||||
</select>
|
</select>
|
||||||
<label class="input-group-text" for="slotMinTime">De</label>
|
<label class="input-group-text" for="slotMinTime">De</label>
|
||||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
<select
|
||||||
|
v-model="slotMinTime"
|
||||||
|
id="slotMinTime"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option value="00:00:00">0h</option>
|
<option value="00:00:00">0h</option>
|
||||||
<option value="01:00:00">1h</option>
|
<option value="01:00:00">1h</option>
|
||||||
<option value="02:00:00">2h</option>
|
<option value="02:00:00">2h</option>
|
||||||
@@ -46,7 +54,11 @@
|
|||||||
<option value="12:00:00">12h</option>
|
<option value="12:00:00">12h</option>
|
||||||
</select>
|
</select>
|
||||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
<select
|
||||||
|
v-model="slotMaxTime"
|
||||||
|
id="slotMaxTime"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option value="12:00:00">12h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="13:00:00">13h</option>
|
<option value="13:00:00">13h</option>
|
||||||
<option value="14:00:00">14h</option>
|
<option value="14:00:00">14h</option>
|
||||||
@@ -74,7 +86,9 @@
|
|||||||
v-model="showWeekends"
|
v-model="showWeekends"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<label for="showHideWE" class="form-check-label input-group-text"
|
<label
|
||||||
|
for="showHideWE"
|
||||||
|
class="form-check-label input-group-text"
|
||||||
>Week-ends</label
|
>Week-ends</label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,7 +102,8 @@
|
|||||||
arg.event.title
|
arg.event.title
|
||||||
}}</b>
|
}}</b>
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
||||||
>{{ arg.timeText }} - {{ arg.event.extendedProps.locationName }}</b
|
>{{ arg.timeText }} -
|
||||||
|
{{ arg.event.extendedProps.locationName }}</b
|
||||||
>
|
>
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||||
arg.event.title
|
arg.event.title
|
||||||
@@ -106,29 +121,45 @@
|
|||||||
|
|
||||||
<div id="copy-widget">
|
<div id="copy-widget">
|
||||||
<div class="container mt-2 mb-2">
|
<div class="container mt-2 mb-2">
|
||||||
|
|
||||||
<div class="row justify-content-between align-items-center mb-4">
|
<div class="row justify-content-between align-items-center mb-4">
|
||||||
<div class="col-xs-12 col-sm-3 col-md-2">
|
<div class="col-xs-12 col-sm-3 col-md-2">
|
||||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||||
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
|
<select
|
||||||
|
v-model="dayOrWeek"
|
||||||
|
id="dayOrWeek"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||||
<option value="week">{{ $t("from_week_to_week") }}</option>
|
<option value="week">
|
||||||
|
{{ $t("from_week_to_week") }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="dayOrWeek === 'day'">
|
<template v-if="dayOrWeek === 'day'">
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
<input class="form-control" type="date" v-model="copyFrom" />
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="date"
|
||||||
|
v-model="copyFrom"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||||
<i class="fa fa-angle-double-right"></i>
|
<i class="fa fa-angle-double-right"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
<input class="form-control" type="date" v-model="copyTo" />
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="date"
|
||||||
|
v-model="copyTo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||||
<button class="btn btn-action float-end" @click="copyDay">
|
<button
|
||||||
|
class="btn btn-action float-end"
|
||||||
|
@click="copyDay"
|
||||||
|
>
|
||||||
{{ $t("copy_range") }}
|
{{ $t("copy_range") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,7 +171,11 @@
|
|||||||
id="copyFromWeek"
|
id="copyFromWeek"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
>
|
>
|
||||||
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
|
<option
|
||||||
|
v-for="w in lastWeeks"
|
||||||
|
:value="w.value"
|
||||||
|
:key="w.value"
|
||||||
|
>
|
||||||
{{ w.text }}
|
{{ w.text }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -149,21 +184,31 @@
|
|||||||
<i class="fa fa-angle-double-right"></i>
|
<i class="fa fa-angle-double-right"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
|
<select
|
||||||
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
|
v-model="copyToWeek"
|
||||||
|
id="copyToWeek"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="w in nextWeeks"
|
||||||
|
:value="w.value"
|
||||||
|
:key="w.value"
|
||||||
|
>
|
||||||
{{ w.text }}
|
{{ w.text }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||||
<button class="btn btn-action float-end" @click="copyWeek">
|
<button
|
||||||
|
class="btn btn-action float-end"
|
||||||
|
@click="copyWeek"
|
||||||
|
>
|
||||||
{{ $t("copy_range") }}
|
{{ $t("copy_range") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- not directly seen, but include in a modal -->
|
<!-- not directly seen, but include in a modal -->
|
||||||
@@ -224,7 +269,7 @@ interface Weeks {
|
|||||||
const getMonday = (week: number): Date => {
|
const getMonday = (week: number): Date => {
|
||||||
const lastMonday = new Date();
|
const lastMonday = new Date();
|
||||||
lastMonday.setDate(
|
lastMonday.setDate(
|
||||||
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7
|
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
||||||
);
|
);
|
||||||
return lastMonday;
|
return lastMonday;
|
||||||
};
|
};
|
||||||
@@ -238,12 +283,12 @@ const dateOptions: Intl.DateTimeFormatOptions = {
|
|||||||
|
|
||||||
const lastWeeks = computed((): Weeks[] =>
|
const lastWeeks = computed((): Weeks[] =>
|
||||||
Array.from(Array(30).keys()).map((w) => {
|
Array.from(Array(30).keys()).map((w) => {
|
||||||
const lastMonday = getMonday(15-w);
|
const lastMonday = getMonday(15 - w);
|
||||||
return {
|
return {
|
||||||
value: dateToISO(lastMonday),
|
value: dateToISO(lastMonday),
|
||||||
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextWeeks = computed((): Weeks[] =>
|
const nextWeeks = computed((): Weeks[] =>
|
||||||
@@ -253,7 +298,7 @@ const nextWeeks = computed((): Weeks[] =>
|
|||||||
value: dateToISO(nextMonday),
|
value: dateToISO(nextMonday),
|
||||||
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const baseOptions = ref<CalendarOptions>({
|
const baseOptions = ref<CalendarOptions>({
|
||||||
@@ -298,7 +343,9 @@ const pickedLocation = computed<Location | null>({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
set(newLocation: Location | null): void {
|
set(newLocation: Location | null): void {
|
||||||
store.commit("locations/setLocationPicked", newLocation, { root: true });
|
store.commit("locations/setLocationPicked", newLocation, {
|
||||||
|
root: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -354,7 +401,7 @@ function onDatesSet(event: DatesSetArg): void {
|
|||||||
function onDateSelect(event: DateSelectArg): void {
|
function onDateSelect(event: DateSelectArg): void {
|
||||||
if (null === pickedLocation.value) {
|
if (null === pickedLocation.value) {
|
||||||
window.alert(
|
window.alert(
|
||||||
"Indiquez une localisation avant de créer une période de disponibilité."
|
"Indiquez une localisation avant de créer une période de disponibilité.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -376,7 +423,7 @@ function onClickDelete(event: EventApi): void {
|
|||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
"calendarRanges/deleteRange",
|
"calendarRanges/deleteRange",
|
||||||
event.extendedProps.calendarRangeId
|
event.extendedProps.calendarRangeId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,51 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Teleport" to="body">
|
<component :is="Teleport" to="body">
|
||||||
<modal v-if="showModal"
|
<modal v-if="showModal" @close="closeModal">
|
||||||
@close="closeModal">
|
|
||||||
|
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h3>{{ 'Modifier le lieu' }}</h3>
|
<h3>{{ "Modifier le lieu" }}</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<div>
|
<div></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<label>Localisation</label>
|
<label>Localisation</label>
|
||||||
<vue-multiselect v-model="location" :options="locations" :label="'name'" :track-by="'id'"></vue-multiselect>
|
<vue-multiselect
|
||||||
|
v-model="location"
|
||||||
|
:options="locations"
|
||||||
|
:label="'name'"
|
||||||
|
:track-by="'id'"
|
||||||
|
></vue-multiselect>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:footer>
|
<template v-slot:footer>
|
||||||
<button class="btn btn-save" @click="saveAndClose">{{ 'Enregistrer' }}</button>
|
<button class="btn btn-save" @click="saveAndClose">
|
||||||
|
{{ "Enregistrer" }}
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</modal>
|
</modal>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Modal from "../../../../../../ChillMainBundle/Resources/public/vuejs/_components/Modal.vue";
|
import Modal from "../../../../../../ChillMainBundle/Resources/public/vuejs/_components/Modal.vue";
|
||||||
import {computed, ref} from "vue";
|
import { computed, ref } from "vue";
|
||||||
import {EventApi} from "@fullcalendar/core";
|
import { EventApi } from "@fullcalendar/core";
|
||||||
import {useStore} from "vuex";
|
import { useStore } from "vuex";
|
||||||
import {key} from "../store";
|
import { key } from "../store";
|
||||||
import {Location} from "../../../../../../ChillMainBundle/Resources/public/types";
|
import { Location } from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
//import type {Teleport} from "vue";
|
//import type {Teleport} from "vue";
|
||||||
|
|
||||||
// see https://github.com/vuejs/core/issues/2855
|
// see https://github.com/vuejs/core/issues/2855
|
||||||
import {
|
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
||||||
Teleport as teleport_,
|
|
||||||
TeleportProps,
|
|
||||||
VNodeProps
|
|
||||||
} from 'vue'
|
|
||||||
|
|
||||||
const Teleport = teleport_ as {
|
|
||||||
new (): {
|
|
||||||
$props: VNodeProps & TeleportProps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const Teleport = teleport_ as new () => {
|
||||||
|
$props: VNodeProps & TeleportProps;
|
||||||
|
};
|
||||||
|
|
||||||
const store = useStore(key);
|
const store = useStore(key);
|
||||||
|
|
||||||
@@ -58,31 +53,37 @@ const locations = computed<Location[]>(() => {
|
|||||||
return store.state.locations.locations;
|
return store.state.locations.locations;
|
||||||
});
|
});
|
||||||
|
|
||||||
const startEdit = function(event: EventApi): void {
|
const startEdit = function (event: EventApi): void {
|
||||||
console.log('startEditing', event);
|
console.log("startEditing", event);
|
||||||
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
||||||
location.value = store.getters['locations/getLocationById'](event.extendedProps.locationId) || null;
|
location.value =
|
||||||
|
store.getters["locations/getLocationById"](
|
||||||
|
event.extendedProps.locationId,
|
||||||
|
) || null;
|
||||||
|
|
||||||
console.log('new location value', location.value);
|
console.log("new location value", location.value);
|
||||||
console.log('calendar range id', calendarRangeId.value);
|
console.log("calendar range id", calendarRangeId.value);
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const saveAndClose = function(e: Event): void {
|
const saveAndClose = function (e: Event): void {
|
||||||
console.log('saveEditAndClose', e);
|
console.log("saveEditAndClose", e);
|
||||||
|
|
||||||
store.dispatch('calendarRanges/patchRangeLocation', {location: location.value, calendarRangeId: calendarRangeId.value})
|
store
|
||||||
.then(_ => {showModal.value = false;})
|
.dispatch("calendarRanges/patchRangeLocation", {
|
||||||
}
|
location: location.value,
|
||||||
|
calendarRangeId: calendarRangeId.value,
|
||||||
const closeModal = function(_: any): void {
|
})
|
||||||
|
.then((_) => {
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
defineExpose({startEdit});
|
const closeModal = function (_: any): void {
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ startEdit });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@@ -8,7 +8,8 @@ const appMessages = {
|
|||||||
copy_range_from_to: "Copier les plages",
|
copy_range_from_to: "Copier les plages",
|
||||||
from_day_to_day: "d'un jour à l'autre",
|
from_day_to_day: "d'un jour à l'autre",
|
||||||
from_week_to_week: "d'une semaine à l'autre",
|
from_week_to_week: "d'une semaine à l'autre",
|
||||||
copy_range_how_to: "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.",
|
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",
|
new_range_to_save: "Nouvelles plages à enregistrer",
|
||||||
update_range_to_save: "Plages à modifier",
|
update_range_to_save: "Plages à modifier",
|
||||||
delete_range_to_save: "Plages à supprimer",
|
delete_range_to_save: "Plages à supprimer",
|
||||||
@@ -20,9 +21,7 @@ const appMessages = {
|
|||||||
week: "Semaine",
|
week: "Semaine",
|
||||||
month: "Mois",
|
month: "Mois",
|
||||||
today: "Aujourd'hui",
|
today: "Aujourd'hui",
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
appMessages
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { appMessages };
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from "vue";
|
||||||
import { _createI18n } from '../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n'
|
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||||
import { appMessages } from './i18n'
|
import { appMessages } from "./i18n";
|
||||||
import futureStore, {key} from './store/index'
|
import futureStore, { key } from "./store/index";
|
||||||
|
|
||||||
import App2 from './App2.vue';
|
import App2 from "./App2.vue";
|
||||||
import {useI18n} from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
futureStore().then((store) => {
|
futureStore().then((store) => {
|
||||||
const i18n = _createI18n(appMessages, false);
|
const i18n = _createI18n(appMessages, false);
|
||||||
@@ -14,6 +14,6 @@ futureStore().then((store) => {
|
|||||||
})
|
})
|
||||||
.use(store, key)
|
.use(store, key)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component('app', App2)
|
.component("app", App2)
|
||||||
.mount('#myCalendar');
|
.mount("#myCalendar");
|
||||||
});
|
});
|
||||||
|
@@ -1,29 +1,31 @@
|
|||||||
import 'es6-promise/auto';
|
import "es6-promise/auto";
|
||||||
import {Store, createStore} from 'vuex';
|
import { Store, createStore } from "vuex";
|
||||||
import {InjectionKey} from "vue";
|
import { InjectionKey } from "vue";
|
||||||
import me, {MeState} from './modules/me';
|
import me, { MeState } from "./modules/me";
|
||||||
import fullCalendar, {FullCalendarState} from './modules/fullcalendar';
|
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
||||||
import calendarRanges, {CalendarRangesState} from './modules/calendarRanges';
|
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
||||||
import calendarRemotes, {CalendarRemotesState} from './modules/calendarRemotes';
|
import calendarRemotes, {
|
||||||
import {whoami} from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
CalendarRemotesState,
|
||||||
import {User} from '../../../../../../ChillMainBundle/Resources/public/types';
|
} from "./modules/calendarRemotes";
|
||||||
import locations, {LocationState} from "./modules/location";
|
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||||
import calendarLocals, {CalendarLocalsState} from "./modules/calendarLocals";
|
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 {
|
export interface State {
|
||||||
calendarRanges: CalendarRangesState,
|
calendarRanges: CalendarRangesState;
|
||||||
calendarRemotes: CalendarRemotesState,
|
calendarRemotes: CalendarRemotesState;
|
||||||
calendarLocals: CalendarLocalsState,
|
calendarLocals: CalendarLocalsState;
|
||||||
fullCalendar: FullCalendarState,
|
fullCalendar: FullCalendarState;
|
||||||
me: MeState,
|
me: MeState;
|
||||||
locations: LocationState
|
locations: LocationState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const key: InjectionKey<Store<State>> = Symbol();
|
export const key: InjectionKey<Store<State>> = Symbol();
|
||||||
|
|
||||||
const futureStore = function(): Promise<Store<State>> {
|
const futureStore = function (): Promise<Store<State>> {
|
||||||
return whoami().then((user: User) => {
|
return whoami().then((user: User) => {
|
||||||
const store = createStore<State>({
|
const store = createStore<State>({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
@@ -35,16 +37,20 @@ const futureStore = function(): Promise<Store<State>> {
|
|||||||
calendarLocals,
|
calendarLocals,
|
||||||
locations,
|
locations,
|
||||||
},
|
},
|
||||||
mutations: {}
|
mutations: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
store.commit('me/setWhoAmi', user, {root: true});
|
store.commit("me/setWhoAmi", user, { root: true });
|
||||||
store.dispatch('locations/getLocations', null, {root: true}).then(_ => {
|
store
|
||||||
return store.dispatch('locations/getCurrentLocation', null, {root: true});
|
.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;
|
export default futureStore;
|
||||||
|
@@ -1,33 +1,38 @@
|
|||||||
import {State} from './../index';
|
import { State } from "./../index";
|
||||||
import {ActionContext, Module} from 'vuex';
|
import { ActionContext, Module } from "vuex";
|
||||||
import {CalendarLight} from '../../../../types';
|
import { CalendarLight } from "../../../../types";
|
||||||
import {fetchCalendarLocalForUser} from '../../../Calendar/api';
|
import { fetchCalendarLocalForUser } from "../../../Calendar/api";
|
||||||
import {EventInput} from '@fullcalendar/core';
|
import { EventInput } from "@fullcalendar/core";
|
||||||
import {localsToFullCalendarEvent} from "../../../Calendar/store/utils";
|
import { localsToFullCalendarEvent } from "../../../Calendar/store/utils";
|
||||||
import {TransportExceptionInterface} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
import {COLORS} from "../../../Calendar/const";
|
import { COLORS } from "../../../Calendar/const";
|
||||||
|
|
||||||
export interface CalendarLocalsState {
|
export interface CalendarLocalsState {
|
||||||
locals: EventInput[],
|
locals: EventInput[];
|
||||||
localsLoaded: {start: number, end: number}[],
|
localsLoaded: { start: number; end: number }[];
|
||||||
localsIndex: Set<string>,
|
localsIndex: Set<string>;
|
||||||
key: number
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarLocalsState, State>;
|
type Context = ActionContext<CalendarLocalsState, State>;
|
||||||
|
|
||||||
export default <Module<CalendarLocalsState, State>> {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarLocalsState => ({
|
state: (): CalendarLocalsState => ({
|
||||||
locals: [],
|
locals: [],
|
||||||
localsLoaded: [],
|
localsLoaded: [],
|
||||||
localsIndex: new Set<string>(),
|
localsIndex: new Set<string>(),
|
||||||
key: 0
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isLocalsLoaded: (state: CalendarLocalsState) => ({start, end}: {start: Date, end: Date}): boolean => {
|
isLocalsLoaded:
|
||||||
for (let range of state.localsLoaded) {
|
(state: CalendarLocalsState) =>
|
||||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
({ 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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,11 +42,11 @@ export default <Module<CalendarLocalsState, State>> {
|
|||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
||||||
console.log('addLocals', ranges);
|
console.log("addLocals", ranges);
|
||||||
|
|
||||||
const toAdd = ranges
|
const toAdd = ranges
|
||||||
.map(cr => localsToFullCalendarEvent(cr))
|
.map((cr) => localsToFullCalendarEvent(cr))
|
||||||
.filter(r => !state.localsIndex.has(r.id));
|
.filter((r) => !state.localsIndex.has(r.id));
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
toAdd.forEach((r) => {
|
||||||
state.localsIndex.add(r.id);
|
state.localsIndex.add(r.id);
|
||||||
@@ -49,40 +54,56 @@ export default <Module<CalendarLocalsState, State>> {
|
|||||||
});
|
});
|
||||||
state.key = state.key + toAdd.length;
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
addLoaded(state: CalendarLocalsState, payload: {start: Date, end: Date}) {
|
addLoaded(
|
||||||
state.localsLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()});
|
state: CalendarLocalsState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
) {
|
||||||
|
state.localsLoaded.push({
|
||||||
|
start: payload.start.getTime(),
|
||||||
|
end: payload.end.getTime(),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
fetchLocals(ctx: Context, payload: {start: Date, end: Date}): Promise<null> {
|
fetchLocals(
|
||||||
|
ctx: Context,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
const start = payload.start;
|
const start = payload.start;
|
||||||
const end = payload.end;
|
const end = payload.end;
|
||||||
|
|
||||||
if (ctx.rootGetters['me/getMe'] === null) {
|
if (ctx.rootGetters["me/getMe"] === null) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.getters.isLocalsLoaded({start, end})) {
|
if (ctx.getters.isLocalsLoaded({ start, end })) {
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.commit('addLoaded', {
|
ctx.commit("addLoaded", {
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchCalendarLocalForUser(
|
return fetchCalendarLocalForUser(
|
||||||
ctx.rootGetters['me/getMe'],
|
ctx.rootGetters["me/getMe"],
|
||||||
start,
|
start,
|
||||||
end
|
end,
|
||||||
)
|
)
|
||||||
.then((remotes: CalendarLight[]) => {
|
.then((remotes: CalendarLight[]) => {
|
||||||
// to be add when reactivity problem will be solve ?
|
// to be add when reactivity problem will be solve ?
|
||||||
//ctx.commit('addRemotes', remotes);
|
//ctx.commit('addRemotes', remotes);
|
||||||
const inputs = remotes
|
const inputs = remotes
|
||||||
.map(cr => localsToFullCalendarEvent(cr))
|
.map((cr) => localsToFullCalendarEvent(cr))
|
||||||
.map(cr => ({...cr, backgroundColor: COLORS[0], textColor: 'black', editable: false}))
|
.map((cr) => ({
|
||||||
ctx.commit('calendarRanges/addExternals', inputs, {root: true});
|
...cr,
|
||||||
|
backgroundColor: COLORS[0],
|
||||||
|
textColor: "black",
|
||||||
|
editable: false,
|
||||||
|
}));
|
||||||
|
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||||
|
root: true,
|
||||||
|
});
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
})
|
})
|
||||||
.catch((e: TransportExceptionInterface) => {
|
.catch((e: TransportExceptionInterface) => {
|
||||||
@@ -90,6 +111,6 @@ export default <Module<CalendarLocalsState, State>> {
|
|||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
} as Module<CalendarLocalsState, State>;
|
||||||
|
@@ -1,52 +1,65 @@
|
|||||||
import {State} from './../index';
|
import { State } from "./../index";
|
||||||
import {ActionContext, Module} from 'vuex';
|
import { ActionContext, Module } from "vuex";
|
||||||
import {CalendarRange, CalendarRangeCreate, CalendarRangeEdit, isEventInputCalendarRange} from "../../../../types";
|
import {
|
||||||
import {Location} from "../../../../../../../ChillMainBundle/Resources/public/types";
|
CalendarRange,
|
||||||
import {fetchCalendarRangeForUser} from '../../../Calendar/api';
|
CalendarRangeCreate,
|
||||||
import {calendarRangeToFullCalendarEvent} from '../../../Calendar/store/utils';
|
CalendarRangeEdit,
|
||||||
import {EventInput} from '@fullcalendar/core';
|
isEventInputCalendarRange,
|
||||||
import {makeFetch} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
} 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 {
|
import {
|
||||||
datetimeToISO,
|
datetimeToISO,
|
||||||
dateToISO,
|
dateToISO,
|
||||||
ISOToDatetime
|
ISOToDatetime,
|
||||||
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
import type {EventInputCalendarRange} from "../../../../types";
|
import type { EventInputCalendarRange } from "../../../../types";
|
||||||
|
|
||||||
export interface CalendarRangesState {
|
export interface CalendarRangesState {
|
||||||
ranges: (EventInput | EventInputCalendarRange) [],
|
ranges: (EventInput | EventInputCalendarRange)[];
|
||||||
rangesLoaded: { start: number, end: number }[],
|
rangesLoaded: { start: number; end: number }[];
|
||||||
rangesIndex: Set<string>,
|
rangesIndex: Set<string>;
|
||||||
key: number
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarRangesState, State>;
|
type Context = ActionContext<CalendarRangesState, State>;
|
||||||
|
|
||||||
export default <Module<CalendarRangesState, State>>{
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarRangesState => ({
|
state: (): CalendarRangesState => ({
|
||||||
ranges: [],
|
ranges: [],
|
||||||
rangesLoaded: [],
|
rangesLoaded: [],
|
||||||
rangesIndex: new Set<string>(),
|
rangesIndex: new Set<string>(),
|
||||||
key: 0
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isRangeLoaded: (state: CalendarRangesState) => ({start, end}: { start: Date, end: Date }): boolean => {
|
isRangeLoaded:
|
||||||
for (let range of state.rangesLoaded) {
|
(state: CalendarRangesState) =>
|
||||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
({ 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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
getRangesOnDate: (state: CalendarRangesState) => (date: Date): EventInputCalendarRange[] => {
|
getRangesOnDate:
|
||||||
|
(state: CalendarRangesState) =>
|
||||||
|
(date: Date): EventInputCalendarRange[] => {
|
||||||
const founds = [];
|
const founds = [];
|
||||||
const dateStr = <string>dateToISO(date);
|
const dateStr = dateToISO(date) as string;
|
||||||
|
|
||||||
for (let range of state.ranges) {
|
for (const range of state.ranges) {
|
||||||
if (isEventInputCalendarRange(range)
|
if (
|
||||||
&& range.start.startsWith(dateStr)
|
isEventInputCalendarRange(range) &&
|
||||||
|
range.start.startsWith(dateStr)
|
||||||
) {
|
) {
|
||||||
founds.push(range);
|
founds.push(range);
|
||||||
}
|
}
|
||||||
@@ -54,15 +67,18 @@ export default <Module<CalendarRangesState, State>>{
|
|||||||
|
|
||||||
return founds;
|
return founds;
|
||||||
},
|
},
|
||||||
getRangesOnWeek: (state: CalendarRangesState) => (mondayDate: Date): EventInputCalendarRange[] => {
|
getRangesOnWeek:
|
||||||
|
(state: CalendarRangesState) =>
|
||||||
|
(mondayDate: Date): EventInputCalendarRange[] => {
|
||||||
const founds = [];
|
const founds = [];
|
||||||
for (let d of Array.from(Array(7).keys())) {
|
for (const d of Array.from(Array(7).keys())) {
|
||||||
const dateOfWeek = new Date(mondayDate);
|
const dateOfWeek = new Date(mondayDate);
|
||||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||||
const dateStr = <string>dateToISO(dateOfWeek);
|
const dateStr = dateToISO(dateOfWeek) as string;
|
||||||
for (let range of state.ranges) {
|
for (const range of state.ranges) {
|
||||||
if (isEventInputCalendarRange(range)
|
if (
|
||||||
&& range.start.startsWith(dateStr)
|
isEventInputCalendarRange(range) &&
|
||||||
|
range.start.startsWith(dateStr)
|
||||||
) {
|
) {
|
||||||
founds.push(range);
|
founds.push(range);
|
||||||
}
|
}
|
||||||
@@ -75,12 +91,14 @@ export default <Module<CalendarRangesState, State>>{
|
|||||||
mutations: {
|
mutations: {
|
||||||
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
||||||
const toAdd = ranges
|
const toAdd = ranges
|
||||||
.map(cr => calendarRangeToFullCalendarEvent(cr))
|
.map((cr) => calendarRangeToFullCalendarEvent(cr))
|
||||||
.map(cr => ({
|
.map((cr) => ({
|
||||||
...cr, backgroundColor: 'white', borderColor: '#3788d8',
|
...cr,
|
||||||
textColor: 'black'
|
backgroundColor: "white",
|
||||||
|
borderColor: "#3788d8",
|
||||||
|
textColor: "black",
|
||||||
}))
|
}))
|
||||||
.filter(r => !state.rangesIndex.has(r.id));
|
.filter((r) => !state.rangesIndex.has(r.id));
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
toAdd.forEach((r) => {
|
||||||
state.rangesIndex.add(r.id);
|
state.rangesIndex.add(r.id);
|
||||||
@@ -89,8 +107,9 @@ export default <Module<CalendarRangesState, State>>{
|
|||||||
state.key = state.key + toAdd.length;
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
addExternals(state, externalEvents: (EventInput & { id: string })[]) {
|
addExternals(state, externalEvents: (EventInput & { id: string })[]) {
|
||||||
const toAdd = externalEvents
|
const toAdd = externalEvents.filter(
|
||||||
.filter(r => !state.rangesIndex.has(r.id));
|
(r) => !state.rangesIndex.has(r.id),
|
||||||
|
);
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
toAdd.forEach((r) => {
|
||||||
state.rangesIndex.add(r.id);
|
state.rangesIndex.add(r.id);
|
||||||
@@ -98,24 +117,43 @@ export default <Module<CalendarRangesState, State>>{
|
|||||||
});
|
});
|
||||||
state.key = state.key + toAdd.length;
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
addLoaded(state: CalendarRangesState, payload: { start: Date, end: Date }) {
|
addLoaded(
|
||||||
state.rangesLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()});
|
state: CalendarRangesState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
) {
|
||||||
|
state.rangesLoaded.push({
|
||||||
|
start: payload.start.getTime(),
|
||||||
|
end: payload.end.getTime(),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
||||||
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
||||||
state.ranges.push({...asEvent, backgroundColor: 'white', borderColor: '#3788d8', textColor: 'black'});
|
state.ranges.push({
|
||||||
|
...asEvent,
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderColor: "#3788d8",
|
||||||
|
textColor: "black",
|
||||||
|
});
|
||||||
state.rangesIndex.add(asEvent.id);
|
state.rangesIndex.add(asEvent.id);
|
||||||
state.key = state.key + 1;
|
state.key = state.key + 1;
|
||||||
},
|
},
|
||||||
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
||||||
const found = state.ranges.find(r => r.calendarRangeId === calendarRangeId && r.is === "range");
|
const found = state.ranges.find(
|
||||||
|
(r) =>
|
||||||
|
r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||||
|
);
|
||||||
|
|
||||||
if (found !== undefined) {
|
if (found !== undefined) {
|
||||||
state.ranges = state.ranges.filter(
|
state.ranges = state.ranges.filter(
|
||||||
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range")
|
(r) =>
|
||||||
|
!(
|
||||||
|
r.calendarRangeId === calendarRangeId &&
|
||||||
|
r.is === "range"
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (typeof found.id === "string") { // should always be true
|
if (typeof found.id === "string") {
|
||||||
|
// should always be true
|
||||||
state.rangesIndex.delete(found.id);
|
state.rangesIndex.delete(found.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +161,9 @@ export default <Module<CalendarRangesState, State>>{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateRange(state, range: CalendarRange) {
|
updateRange(state, range: CalendarRange) {
|
||||||
const found = state.ranges.find(r => r.calendarRangeId === range.id && r.is === "range");
|
const found = state.ranges.find(
|
||||||
|
(r) => r.calendarRangeId === range.id && r.is === "range",
|
||||||
|
);
|
||||||
const newEvent = calendarRangeToFullCalendarEvent(range);
|
const newEvent = calendarRangeToFullCalendarEvent(range);
|
||||||
|
|
||||||
if (found !== undefined) {
|
if (found !== undefined) {
|
||||||
@@ -134,64 +174,76 @@ export default <Module<CalendarRangesState, State>>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.key = state.key + 1;
|
state.key = state.key + 1;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
fetchRanges(ctx: Context, payload: { start: Date, end: Date }): Promise<null> {
|
fetchRanges(
|
||||||
|
ctx: Context,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
const start = payload.start;
|
const start = payload.start;
|
||||||
const end = payload.end;
|
const end = payload.end;
|
||||||
|
|
||||||
if (ctx.rootGetters['me/getMe'] === null) {
|
if (ctx.rootGetters["me/getMe"] === null) {
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.getters.isRangeLoaded({start, end})) {
|
if (ctx.getters.isRangeLoaded({ start, end })) {
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.commit('addLoaded', {
|
ctx.commit("addLoaded", {
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchCalendarRangeForUser(
|
return fetchCalendarRangeForUser(
|
||||||
ctx.rootGetters['me/getMe'],
|
ctx.rootGetters["me/getMe"],
|
||||||
start,
|
start,
|
||||||
end
|
end,
|
||||||
)
|
).then((ranges: CalendarRange[]) => {
|
||||||
.then((ranges: CalendarRange[]) => {
|
ctx.commit("addRanges", ranges);
|
||||||
ctx.commit('addRanges', ranges);
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
createRange(ctx, {start, end, location}: { start: Date, end: Date, location: Location }): Promise<null> {
|
createRange(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
location,
|
||||||
|
}: { start: Date; end: Date; location: Location },
|
||||||
|
): Promise<null> {
|
||||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||||
|
|
||||||
if (ctx.rootState.me.me === null) {
|
if (ctx.rootState.me.me === null) {
|
||||||
throw new Error('user is currently null');
|
throw new Error("user is currently null");
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
user: {
|
user: {
|
||||||
id: ctx.rootState.me.me.id,
|
id: ctx.rootState.me.me.id,
|
||||||
type: "user"
|
type: "user",
|
||||||
},
|
},
|
||||||
startDate: {
|
startDate: {
|
||||||
datetime: datetimeToISO(start),
|
datetime: datetimeToISO(start),
|
||||||
},
|
},
|
||||||
endDate: {
|
endDate: {
|
||||||
datetime: datetimeToISO(end)
|
datetime: datetimeToISO(end),
|
||||||
},
|
},
|
||||||
location: {
|
location: {
|
||||||
id: location.id,
|
id: location.id,
|
||||||
type: "location"
|
type: "location",
|
||||||
}
|
},
|
||||||
} as CalendarRangeCreate;
|
} as CalendarRangeCreate;
|
||||||
|
|
||||||
return makeFetch<CalendarRangeCreate, CalendarRange>('POST', url, body)
|
return makeFetch<CalendarRangeCreate, CalendarRange>(
|
||||||
|
"POST",
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
)
|
||||||
.then((newRange) => {
|
.then((newRange) => {
|
||||||
|
ctx.commit("addRange", newRange);
|
||||||
ctx.commit('addRange', newRange);
|
|
||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
})
|
})
|
||||||
@@ -199,88 +251,127 @@ export default <Module<CalendarRangesState, State>>{
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
deleteRange(ctx, calendarRangeId: number) {
|
deleteRange(ctx, calendarRangeId: number) {
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
|
||||||
makeFetch<undefined, never>('DELETE', url)
|
makeFetch<undefined, never>("DELETE", url).then((_) => {
|
||||||
.then((_) => {
|
ctx.commit("removeRange", calendarRangeId);
|
||||||
ctx.commit('removeRange', calendarRangeId);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
patchRangeTime(ctx, {calendarRangeId, start, end}: {calendarRangeId: number, start: Date, end: Date}): Promise<null> {
|
patchRangeTime(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
calendarRangeId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
}: { calendarRangeId: number; start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
const body = {
|
const body = {
|
||||||
startDate: {
|
startDate: {
|
||||||
datetime: datetimeToISO(start)
|
datetime: datetimeToISO(start),
|
||||||
},
|
},
|
||||||
endDate: {
|
endDate: {
|
||||||
datetime: datetimeToISO(end)
|
datetime: datetimeToISO(end),
|
||||||
},
|
},
|
||||||
} as CalendarRangeEdit;
|
} as CalendarRangeEdit;
|
||||||
|
|
||||||
return makeFetch<CalendarRangeEdit, CalendarRange>('PATCH', url, body)
|
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||||
|
"PATCH",
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
)
|
||||||
.then((range) => {
|
.then((range) => {
|
||||||
ctx.commit('updateRange', range);
|
ctx.commit("updateRange", range);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
patchRangeLocation(ctx, {location, calendarRangeId}: {location: Location, calendarRangeId: number}): Promise<null> {
|
patchRangeLocation(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
location,
|
||||||
|
calendarRangeId,
|
||||||
|
}: { location: Location; calendarRangeId: number },
|
||||||
|
): Promise<null> {
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
const body = {
|
const body = {
|
||||||
location: {
|
location: {
|
||||||
id: location.id,
|
id: location.id,
|
||||||
type: "location"
|
type: "location",
|
||||||
},
|
},
|
||||||
} as CalendarRangeEdit;
|
} as CalendarRangeEdit;
|
||||||
|
|
||||||
return makeFetch<CalendarRangeEdit, CalendarRange>('PATCH', url, body)
|
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||||
|
"PATCH",
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
)
|
||||||
.then((range) => {
|
.then((range) => {
|
||||||
ctx.commit('updateRange', range);
|
ctx.commit("updateRange", range);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
copyFromDayToAnotherDay(ctx, {from, to}: {from: Date, to: Date}): Promise<null> {
|
copyFromDayToAnotherDay(
|
||||||
const rangesToCopy: EventInputCalendarRange[] = ctx.getters['getRangesOnDate'](from);
|
ctx,
|
||||||
|
{ from, to }: { from: Date; to: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
|
ctx.getters["getRangesOnDate"](from);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (let r of rangesToCopy) {
|
for (const r of rangesToCopy) {
|
||||||
let start = new Date(<Date>ISOToDatetime(r.start));
|
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||||
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
start.setFullYear(
|
||||||
let end = new Date(<Date>ISOToDatetime(r.end));
|
to.getFullYear(),
|
||||||
|
to.getMonth(),
|
||||||
|
to.getDate(),
|
||||||
|
);
|
||||||
|
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||||
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||||
let location = ctx.rootGetters['locations/getLocationById'](r.locationId);
|
const location = ctx.rootGetters["locations/getLocationById"](
|
||||||
|
r.locationId,
|
||||||
|
);
|
||||||
|
|
||||||
promises.push(ctx.dispatch('createRange', {start, end, location}));
|
promises.push(
|
||||||
|
ctx.dispatch("createRange", { start, end, location }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then(_ => Promise.resolve(null));
|
return Promise.all(promises).then((_) => Promise.resolve(null));
|
||||||
},
|
},
|
||||||
copyFromWeekToAnotherWeek(ctx, {fromMonday, toMonday}: {fromMonday: Date, toMonday: Date}): Promise<null> {
|
copyFromWeekToAnotherWeek(
|
||||||
|
ctx,
|
||||||
const rangesToCopy: EventInputCalendarRange[] = ctx.getters['getRangesOnWeek'](fromMonday);
|
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
|
ctx.getters["getRangesOnWeek"](fromMonday);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
||||||
for (let r of rangesToCopy) {
|
for (const r of rangesToCopy) {
|
||||||
let start = new Date(<Date>ISOToDatetime(r.start));
|
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||||
let end = new Date(<Date>ISOToDatetime(r.end));
|
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||||
start.setTime(start.getTime() + diffTime);
|
start.setTime(start.getTime() + diffTime);
|
||||||
end.setTime(end.getTime() + diffTime);
|
end.setTime(end.getTime() + diffTime);
|
||||||
let location = ctx.rootGetters['locations/getLocationById'](r.locationId);
|
const location = ctx.rootGetters["locations/getLocationById"](
|
||||||
|
r.locationId,
|
||||||
|
);
|
||||||
|
|
||||||
promises.push(ctx.dispatch('createRange', {start, end, location}));
|
promises.push(
|
||||||
|
ctx.dispatch("createRange", { start, end, location }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then(_ => Promise.resolve(null));
|
return Promise.all(promises).then((_) => Promise.resolve(null));
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
} as Module<CalendarRangesState, State>;
|
||||||
|
@@ -1,33 +1,38 @@
|
|||||||
import {State} from './../index';
|
import { State } from "./../index";
|
||||||
import {ActionContext, Module} from 'vuex';
|
import { ActionContext, Module } from "vuex";
|
||||||
import {CalendarRemote} from '../../../../types';
|
import { CalendarRemote } from "../../../../types";
|
||||||
import {fetchCalendarRemoteForUser} from '../../../Calendar/api';
|
import { fetchCalendarRemoteForUser } from "../../../Calendar/api";
|
||||||
import {EventInput} from '@fullcalendar/core';
|
import { EventInput } from "@fullcalendar/core";
|
||||||
import {remoteToFullCalendarEvent} from "../../../Calendar/store/utils";
|
import { remoteToFullCalendarEvent } from "../../../Calendar/store/utils";
|
||||||
import {TransportExceptionInterface} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
import { TransportExceptionInterface } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
import {COLORS} from "../../../Calendar/const";
|
import { COLORS } from "../../../Calendar/const";
|
||||||
|
|
||||||
export interface CalendarRemotesState {
|
export interface CalendarRemotesState {
|
||||||
remotes: EventInput[],
|
remotes: EventInput[];
|
||||||
remotesLoaded: {start: number, end: number}[],
|
remotesLoaded: { start: number; end: number }[];
|
||||||
remotesIndex: Set<string>,
|
remotesIndex: Set<string>;
|
||||||
key: number
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarRemotesState, State>;
|
type Context = ActionContext<CalendarRemotesState, State>;
|
||||||
|
|
||||||
export default <Module<CalendarRemotesState, State>> {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarRemotesState => ({
|
state: (): CalendarRemotesState => ({
|
||||||
remotes: [],
|
remotes: [],
|
||||||
remotesLoaded: [],
|
remotesLoaded: [],
|
||||||
remotesIndex: new Set<string>(),
|
remotesIndex: new Set<string>(),
|
||||||
key: 0
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isRemotesLoaded: (state: CalendarRemotesState) => ({start, end}: {start: Date, end: Date}): boolean => {
|
isRemotesLoaded:
|
||||||
for (let range of state.remotesLoaded) {
|
(state: CalendarRemotesState) =>
|
||||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
({ 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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,11 +42,11 @@ export default <Module<CalendarRemotesState, State>> {
|
|||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
||||||
console.log('addRemotes', ranges);
|
console.log("addRemotes", ranges);
|
||||||
|
|
||||||
const toAdd = ranges
|
const toAdd = ranges
|
||||||
.map(cr => remoteToFullCalendarEvent(cr))
|
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||||
.filter(r => !state.remotesIndex.has(r.id));
|
.filter((r) => !state.remotesIndex.has(r.id));
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
toAdd.forEach((r) => {
|
||||||
state.remotesIndex.add(r.id);
|
state.remotesIndex.add(r.id);
|
||||||
@@ -49,40 +54,56 @@ export default <Module<CalendarRemotesState, State>> {
|
|||||||
});
|
});
|
||||||
state.key = state.key + toAdd.length;
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
addLoaded(state: CalendarRemotesState, payload: {start: Date, end: Date}) {
|
addLoaded(
|
||||||
state.remotesLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()});
|
state: CalendarRemotesState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
) {
|
||||||
|
state.remotesLoaded.push({
|
||||||
|
start: payload.start.getTime(),
|
||||||
|
end: payload.end.getTime(),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
fetchRemotes(ctx: Context, payload: {start: Date, end: Date}): Promise<null> {
|
fetchRemotes(
|
||||||
|
ctx: Context,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
const start = payload.start;
|
const start = payload.start;
|
||||||
const end = payload.end;
|
const end = payload.end;
|
||||||
|
|
||||||
if (ctx.rootGetters['me/getMe'] === null) {
|
if (ctx.rootGetters["me/getMe"] === null) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.getters.isRemotesLoaded({start, end})) {
|
if (ctx.getters.isRemotesLoaded({ start, end })) {
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.commit('addLoaded', {
|
ctx.commit("addLoaded", {
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchCalendarRemoteForUser(
|
return fetchCalendarRemoteForUser(
|
||||||
ctx.rootGetters['me/getMe'],
|
ctx.rootGetters["me/getMe"],
|
||||||
start,
|
start,
|
||||||
end
|
end,
|
||||||
)
|
)
|
||||||
.then((remotes: CalendarRemote[]) => {
|
.then((remotes: CalendarRemote[]) => {
|
||||||
// to be add when reactivity problem will be solve ?
|
// to be add when reactivity problem will be solve ?
|
||||||
//ctx.commit('addRemotes', remotes);
|
//ctx.commit('addRemotes', remotes);
|
||||||
const inputs = remotes
|
const inputs = remotes
|
||||||
.map(cr => remoteToFullCalendarEvent(cr))
|
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||||
.map(cr => ({...cr, backgroundColor: COLORS[0], textColor: 'black', editable: false}))
|
.map((cr) => ({
|
||||||
ctx.commit('calendarRanges/addExternals', inputs, {root: true});
|
...cr,
|
||||||
|
backgroundColor: COLORS[0],
|
||||||
|
textColor: "black",
|
||||||
|
editable: false,
|
||||||
|
}));
|
||||||
|
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||||
|
root: true,
|
||||||
|
});
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
})
|
})
|
||||||
.catch((e: TransportExceptionInterface) => {
|
.catch((e: TransportExceptionInterface) => {
|
||||||
@@ -90,6 +111,6 @@ export default <Module<CalendarRemotesState, State>> {
|
|||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
} as Module<CalendarRemotesState, State>;
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import {State} from './../index';
|
import { State } from "./../index";
|
||||||
import {ActionContext} from 'vuex';
|
import { ActionContext } from "vuex";
|
||||||
|
|
||||||
export interface FullCalendarState {
|
export interface FullCalendarState {
|
||||||
currentView: {
|
currentView: {
|
||||||
start: Date|null,
|
start: Date | null;
|
||||||
end: Date|null,
|
end: Date | null;
|
||||||
},
|
};
|
||||||
key: number
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<FullCalendarState, State>;
|
type Context = ActionContext<FullCalendarState, State>;
|
||||||
@@ -21,36 +21,58 @@ export default {
|
|||||||
key: 0,
|
key: 0,
|
||||||
}),
|
}),
|
||||||
mutations: {
|
mutations: {
|
||||||
setCurrentDatesView: function(state: FullCalendarState, payload: {start: Date, end: Date}): void {
|
setCurrentDatesView: function (
|
||||||
|
state: FullCalendarState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
): void {
|
||||||
state.currentView.start = payload.start;
|
state.currentView.start = payload.start;
|
||||||
state.currentView.end = payload.end;
|
state.currentView.end = payload.end;
|
||||||
},
|
},
|
||||||
increaseKey: function(state: FullCalendarState): void {
|
increaseKey: function (state: FullCalendarState): void {
|
||||||
state.key = state.key + 1;
|
state.key = state.key + 1;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setCurrentDatesView(ctx: Context, {start, end}: {start: Date|null, end: Date|null}): Promise<null> {
|
setCurrentDatesView(
|
||||||
console.log('dispatch setCurrentDatesView', {start, end});
|
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) {
|
if (
|
||||||
ctx.commit('setCurrentDatesView', {start, end});
|
ctx.state.currentView.start !== start ||
|
||||||
|
ctx.state.currentView.end !== end
|
||||||
|
) {
|
||||||
|
ctx.commit("setCurrentDatesView", { start, end });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start !== null && end !== null) {
|
if (start !== null && end !== null) {
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
ctx.dispatch('calendarRanges/fetchRanges', {start, end}, {root: true}).then(_ => Promise.resolve(null)),
|
ctx
|
||||||
ctx.dispatch('calendarRemotes/fetchRemotes', {start, end}, {root: true}).then(_ => Promise.resolve(null)),
|
.dispatch(
|
||||||
ctx.dispatch('calendarLocals/fetchLocals', {start, end}, {root: true}).then(_ => Promise.resolve(null))
|
"calendarRanges/fetchRanges",
|
||||||
]
|
{ start, end },
|
||||||
).then(_ => Promise.resolve(null));
|
{ 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 {
|
} else {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import {Location} from "../../../../../../../ChillMainBundle/Resources/public/types";
|
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import {State} from '../index';
|
import { State } from "../index";
|
||||||
import {Module} from 'vuex';
|
import { Module } from "vuex";
|
||||||
import {getLocations} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/locations";
|
import { getLocations } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/locations";
|
||||||
import {whereami} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||||
|
|
||||||
export interface LocationState {
|
export interface LocationState {
|
||||||
locations: Location[];
|
locations: Location[];
|
||||||
@@ -10,18 +10,20 @@ export interface LocationState {
|
|||||||
currentLocation: Location | null;
|
currentLocation: Location | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default <Module<LocationState, State>>{
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): LocationState => {
|
state: (): LocationState => {
|
||||||
return {
|
return {
|
||||||
locations: [],
|
locations: [],
|
||||||
locationPicked: null,
|
locationPicked: null,
|
||||||
currentLocation: null,
|
currentLocation: null,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getLocationById: (state) => (id: number): Location|undefined => {
|
getLocationById:
|
||||||
return state.locations.find(l => l.id === id);
|
(state) =>
|
||||||
|
(id: number): Location | undefined => {
|
||||||
|
return state.locations.find((l) => l.id === id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
@@ -34,7 +36,8 @@ export default <Module<LocationState, State>>{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.locationPicked = state.locations.find(l => l.id === location.id) || null;
|
state.locationPicked =
|
||||||
|
state.locations.find((l) => l.id === location.id) || null;
|
||||||
},
|
},
|
||||||
setCurrentLocation(state, location: Location | null): void {
|
setCurrentLocation(state, location: Location | null): void {
|
||||||
if (null === location) {
|
if (null === location) {
|
||||||
@@ -42,21 +45,21 @@ export default <Module<LocationState, State>>{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.currentLocation = state.locations.find(l => l.id === location.id) || null;
|
state.currentLocation =
|
||||||
}
|
state.locations.find((l) => l.id === location.id) || null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
getLocations(ctx): Promise<void> {
|
getLocations(ctx): Promise<void> {
|
||||||
return getLocations().then(locations => {
|
return getLocations().then((locations) => {
|
||||||
ctx.commit('setLocations', locations);
|
ctx.commit("setLocations", locations);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getCurrentLocation(ctx): Promise<void> {
|
getCurrentLocation(ctx): Promise<void> {
|
||||||
return whereami().then(location => {
|
return whereami().then((location) => {
|
||||||
ctx.commit('setCurrentLocation', location);
|
ctx.commit("setCurrentLocation", location);
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
} as Module<LocationState, State>;
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import {State} from './../index';
|
import { State } from "./../index";
|
||||||
import {User} from '../../../../../../../ChillMainBundle/Resources/public/types';
|
import { User } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import {ActionContext} from 'vuex';
|
import { ActionContext } from "vuex";
|
||||||
|
|
||||||
export interface MeState {
|
export interface MeState {
|
||||||
me: User|null,
|
me: User | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<MeState, State>;
|
type Context = ActionContext<MeState, State>;
|
||||||
@@ -14,7 +14,7 @@ export default {
|
|||||||
me: null,
|
me: null,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getMe: function(state: MeState): User|null {
|
getMe: function (state: MeState): User | null {
|
||||||
return state.me;
|
return state.me;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -22,8 +22,5 @@ export default {
|
|||||||
setWhoAmi(state: MeState, me: User) {
|
setWhoAmi(state: MeState, me: User) {
|
||||||
state.me = me;
|
state.me = me;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,95 +1,103 @@
|
|||||||
/*
|
/*
|
||||||
* Endpoint chill_api_single_calendar_range
|
* Endpoint chill_api_single_calendar_range
|
||||||
* method GET, get Calendar ranges
|
* method GET, get Calendar ranges
|
||||||
* @returns {Promise} a promise containing all Calendar ranges objects
|
* @returns {Promise} a promise containing all Calendar ranges objects
|
||||||
*/
|
*/
|
||||||
const fetchCalendarRanges = () => {
|
const fetchCalendarRanges = () => {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
const url = `/api/1.0/calendar/calendar-range-available.json`;
|
const url = `/api/1.0/calendar/calendar-range-available.json`;
|
||||||
return fetch(url)
|
return fetch(url).then((response) => {
|
||||||
.then(response => {
|
if (response.ok) {
|
||||||
if (response.ok) { return response.json(); }
|
return response.json();
|
||||||
throw Error('Error with request resource response');
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchCalendarRangesByUser = (userId) => {
|
const fetchCalendarRangesByUser = (userId) => {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
const url = `/api/1.0/calendar/calendar-range-available.json?user=${userId}`;
|
const url = `/api/1.0/calendar/calendar-range-available.json?user=${userId}`;
|
||||||
return fetch(url)
|
return fetch(url).then((response) => {
|
||||||
.then(response => {
|
if (response.ok) {
|
||||||
if (response.ok) { return response.json(); }
|
return response.json();
|
||||||
throw Error('Error with request resource response');
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Endpoint chill_api_single_calendar
|
* Endpoint chill_api_single_calendar
|
||||||
* method GET, get Calendar events, can be filtered by mainUser
|
* method GET, get Calendar events, can be filtered by mainUser
|
||||||
* @returns {Promise} a promise containing all Calendar objects
|
* @returns {Promise} a promise containing all Calendar objects
|
||||||
*/
|
*/
|
||||||
const fetchCalendar = (mainUserId) => {
|
const fetchCalendar = (mainUserId) => {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
const url = `/api/1.0/calendar/calendar.json?main_user=${mainUserId}&item_per_page=1000`;
|
const url = `/api/1.0/calendar/calendar.json?main_user=${mainUserId}&item_per_page=1000`;
|
||||||
return fetch(url)
|
return fetch(url).then((response) => {
|
||||||
.then(response => {
|
if (response.ok) {
|
||||||
if (response.ok) { return response.json(); }
|
return response.json();
|
||||||
throw Error('Error with request resource response');
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Endpoint chill_api_single_calendar_range__entity_create
|
* Endpoint chill_api_single_calendar_range__entity_create
|
||||||
* method POST, post CalendarRange entity
|
* method POST, post CalendarRange entity
|
||||||
*/
|
*/
|
||||||
const postCalendarRange = (body) => {
|
const postCalendarRange = (body) => {
|
||||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json;charset=utf-8'
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body),
|
||||||
}).then(response => {
|
}).then((response) => {
|
||||||
if (response.ok) { return response.json(); }
|
if (response.ok) {
|
||||||
throw Error('Error with request resource response');
|
return response.json();
|
||||||
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Endpoint chill_api_single_calendar_range__entity
|
* Endpoint chill_api_single_calendar_range__entity
|
||||||
* method PATCH, patch CalendarRange entity
|
* method PATCH, patch CalendarRange entity
|
||||||
*/
|
*/
|
||||||
const patchCalendarRange = (id, body) => {
|
const patchCalendarRange = (id, body) => {
|
||||||
console.log(body)
|
console.log(body);
|
||||||
const url = `/api/1.0/calendar/calendar-range/${id}.json`;
|
const url = `/api/1.0/calendar/calendar-range/${id}.json`;
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'PATCH',
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json;charset=utf-8'
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body),
|
||||||
}).then(response => {
|
}).then((response) => {
|
||||||
if (response.ok) { return response.json(); }
|
if (response.ok) {
|
||||||
throw Error('Error with request resource response');
|
return response.json();
|
||||||
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Endpoint chill_api_single_calendar_range__entity
|
* Endpoint chill_api_single_calendar_range__entity
|
||||||
* method DELETE, delete CalendarRange entity
|
* method DELETE, delete CalendarRange entity
|
||||||
*/
|
*/
|
||||||
const deleteCalendarRange = (id) => {
|
const deleteCalendarRange = (id) => {
|
||||||
const url = `/api/1.0/calendar/calendar-range/${id}.json`;
|
const url = `/api/1.0/calendar/calendar-range/${id}.json`;
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'DELETE',
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json;charset=utf-8'
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
},
|
},
|
||||||
}).then(response => {
|
}).then((response) => {
|
||||||
if (response.ok) { return response.json(); }
|
if (response.ok) {
|
||||||
throw Error('Error with request resource response');
|
return response.json();
|
||||||
|
}
|
||||||
|
throw Error("Error with request resource response");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,5 +107,5 @@ export {
|
|||||||
fetchCalendarRangesByUser,
|
fetchCalendarRangesByUser,
|
||||||
postCalendarRange,
|
postCalendarRange,
|
||||||
patchCalendarRange,
|
patchCalendarRange,
|
||||||
deleteCalendarRange
|
deleteCalendarRange,
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="chill-red">{{ $t('choose_your_calendar_user') }}</h2>
|
<h2 class="chill-red">
|
||||||
|
{{ $t("choose_your_calendar_user") }}
|
||||||
|
</h2>
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="field"
|
name="field"
|
||||||
id="calendarUserSelector"
|
id="calendarUserSelector"
|
||||||
@@ -19,50 +21,70 @@
|
|||||||
@select="selectUsers"
|
@select="selectUsers"
|
||||||
@remove="unSelectUsers"
|
@remove="unSelectUsers"
|
||||||
@close="coloriseSelectedValues"
|
@close="coloriseSelectedValues"
|
||||||
:options="options">
|
:options="options"
|
||||||
</VueMultiselect>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" id="myCalendar" class="form-check-input" v-model="showMyCalendarWidget" />
|
<input
|
||||||
<label class="form-check-label" for="myCalendar">{{ $t('show_my_calendar') }}</label>
|
type="checkbox"
|
||||||
|
id="myCalendar"
|
||||||
|
class="form-check-input"
|
||||||
|
v-model="showMyCalendarWidget"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="myCalendar">{{
|
||||||
|
$t("show_my_calendar")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" id="weekends" class="form-check-input" @click="toggleWeekends" />
|
<input
|
||||||
<label class="form-check-label" for="weekends">{{ $t('show_weekends') }}</label>
|
type="checkbox"
|
||||||
|
id="weekends"
|
||||||
|
class="form-check-input"
|
||||||
|
@click="toggleWeekends"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="weekends">{{
|
||||||
|
$t("show_weekends")
|
||||||
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
|
||||||
|
import VueMultiselect from "vue-multiselect";
|
||||||
|
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
|
||||||
|
|
||||||
import { fetchCalendarRanges, fetchCalendar } from '../../_api/api'
|
const COLORS = [
|
||||||
import VueMultiselect from 'vue-multiselect';
|
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||||
import { whoami } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api';
|
"#8dd3c7",
|
||||||
|
"#ffffb3",
|
||||||
const COLORS = [ /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
"#bebada",
|
||||||
'#8dd3c7',
|
"#fb8072",
|
||||||
'#ffffb3',
|
"#80b1d3",
|
||||||
'#bebada',
|
"#fdb462",
|
||||||
'#fb8072',
|
"#b3de69",
|
||||||
'#80b1d3',
|
"#fccde5",
|
||||||
'#fdb462',
|
"#d9d9d9",
|
||||||
'#b3de69',
|
"#bc80bd",
|
||||||
'#fccde5',
|
"#ccebc5",
|
||||||
'#d9d9d9',
|
"#ffed6f",
|
||||||
'#bc80bd',
|
|
||||||
'#ccebc5',
|
|
||||||
'#ffed6f'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CalendarUserSelector',
|
name: "CalendarUserSelector",
|
||||||
components: { VueMultiselect },
|
components: { VueMultiselect },
|
||||||
props: ['users', 'updateEventsSource', 'calendarEvents', 'showMyCalendar', 'toggleMyCalendar', 'toggleWeekends'],
|
props: [
|
||||||
|
"users",
|
||||||
|
"updateEventsSource",
|
||||||
|
"calendarEvents",
|
||||||
|
"showMyCalendar",
|
||||||
|
"toggleMyCalendar",
|
||||||
|
"toggleWeekends",
|
||||||
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
errorMsg: [],
|
errorMsg: [],
|
||||||
value: [],
|
value: [],
|
||||||
options: []
|
options: [],
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showMyCalendarWidget: {
|
showMyCalendarWidget: {
|
||||||
@@ -72,86 +94,113 @@ export default {
|
|||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.showMyCalendar;
|
return this.showMyCalendar;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
this.fetchData()
|
this.fetchData();
|
||||||
},
|
},
|
||||||
fetchData() {
|
fetchData() {
|
||||||
fetchCalendarRanges().then(calendarRanges => new Promise((resolve, reject) => {
|
fetchCalendarRanges()
|
||||||
|
.then(
|
||||||
|
(calendarRanges) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
let results = calendarRanges.results;
|
let results = calendarRanges.results;
|
||||||
|
|
||||||
let users = [];
|
let users = [];
|
||||||
|
|
||||||
results.forEach(i => {
|
results.forEach((i) => {
|
||||||
if (!(users.some(j => i.user.id === j.id))){
|
if (!users.some((j) => i.user.id === j.id)) {
|
||||||
let ratio = Math.floor(users.length / COLORS.length);
|
let ratio = Math.floor(
|
||||||
let colorIndex = users.length - ratio * COLORS.length;
|
users.length / COLORS.length,
|
||||||
|
);
|
||||||
|
let colorIndex =
|
||||||
|
users.length - ratio * COLORS.length;
|
||||||
users.push({
|
users.push({
|
||||||
id: i.user.id,
|
id: i.user.id,
|
||||||
username: i.user.username,
|
username: i.user.username,
|
||||||
color: COLORS[colorIndex]
|
color: COLORS[colorIndex],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let calendarEvents = [];
|
let calendarEvents = [];
|
||||||
users.forEach(u => {
|
users.forEach((u) => {
|
||||||
let arr = results.filter(i => i.user.id === u.id).map(i =>
|
let arr = results
|
||||||
({
|
.filter((i) => i.user.id === u.id)
|
||||||
|
.map((i) => ({
|
||||||
start: i.startDate.datetime,
|
start: i.startDate.datetime,
|
||||||
end: i.endDate.datetime,
|
end: i.endDate.datetime,
|
||||||
calendarRangeId: i.id,
|
calendarRangeId: i.id,
|
||||||
sourceColor: u.color
|
sourceColor: u.color,
|
||||||
//display: 'background' // can be an option for the disponibility
|
//display: 'background' // can be an option for the disponibility
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
calendarEvents.push({
|
calendarEvents.push({
|
||||||
events: arr,
|
events: arr,
|
||||||
color: u.color,
|
color: u.color,
|
||||||
textColor: '#444444',
|
textColor: "#444444",
|
||||||
editable: false,
|
editable: false,
|
||||||
id: u.id
|
id: u.id,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
this.users.loaded = users;
|
this.users.loaded = users;
|
||||||
this.options = users;
|
this.options = users;
|
||||||
|
|
||||||
this.calendarEvents.loaded = calendarEvents;
|
this.calendarEvents.loaded = calendarEvents;
|
||||||
whoami().then(me => new Promise((resolve, reject) => {
|
whoami().then(
|
||||||
|
(me) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
this.users.logged = me;
|
this.users.logged = me;
|
||||||
let currentUser = users.find(u => u.id === me.id);
|
let currentUser = users.find(
|
||||||
|
(u) => u.id === me.id,
|
||||||
|
);
|
||||||
this.value = currentUser;
|
this.value = currentUser;
|
||||||
|
|
||||||
fetchCalendar(currentUser.id).then(calendar => new Promise((resolve, reject) => {
|
fetchCalendar(currentUser.id).then(
|
||||||
let results = calendar.results;
|
(calendar) =>
|
||||||
let events = results.map(i =>
|
new Promise(
|
||||||
({
|
(resolve, reject) => {
|
||||||
start: i.startDate.datetime,
|
let results =
|
||||||
end: i.endDate.datetime,
|
calendar.results;
|
||||||
})
|
let events =
|
||||||
|
results.map(
|
||||||
|
(i) => ({
|
||||||
|
start: i
|
||||||
|
.startDate
|
||||||
|
.datetime,
|
||||||
|
end: i
|
||||||
|
.endDate
|
||||||
|
.datetime,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
let calendarEventsCurrentUser = {
|
let calendarEventsCurrentUser =
|
||||||
|
{
|
||||||
events: events,
|
events: events,
|
||||||
color: 'darkblue',
|
color: "darkblue",
|
||||||
id: 1000,
|
id: 1000,
|
||||||
editable: false
|
editable: false,
|
||||||
};
|
};
|
||||||
this.calendarEvents.user = calendarEventsCurrentUser;
|
this.calendarEvents.user =
|
||||||
|
calendarEventsCurrentUser;
|
||||||
|
|
||||||
this.selectUsers(currentUser);
|
this.selectUsers(
|
||||||
|
currentUser,
|
||||||
|
);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.errorMsg.push(error.message);
|
this.errorMsg.push(error.message);
|
||||||
});
|
});
|
||||||
@@ -160,29 +209,32 @@ export default {
|
|||||||
return `${value.username}`;
|
return `${value.username}`;
|
||||||
},
|
},
|
||||||
coloriseSelectedValues() {
|
coloriseSelectedValues() {
|
||||||
let tags = document.querySelectorAll('div.multiselect__tags-wrap')[0];
|
let tags = document.querySelectorAll(
|
||||||
|
"div.multiselect__tags-wrap",
|
||||||
|
)[0];
|
||||||
|
|
||||||
if (tags.hasChildNodes()) {
|
if (tags.hasChildNodes()) {
|
||||||
let children = tags.childNodes;
|
let children = tags.childNodes;
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
let child = children[i];
|
let child = children[i];
|
||||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
if (child.nodeType === Node.ELEMENT_NODE) {
|
||||||
this.users.selected.forEach(u => {
|
this.users.selected.forEach((u) => {
|
||||||
if (child.hasChildNodes()) {
|
if (child.hasChildNodes()) {
|
||||||
if (child.firstChild.innerText == u.username) {
|
if (child.firstChild.innerText == u.username) {
|
||||||
child.style.background = u.color;
|
child.style.background = u.color;
|
||||||
child.firstChild.style.color = '#444444';
|
child.firstChild.style.color = "#444444";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectEvents() {
|
selectEvents() {
|
||||||
let selectedUsersId = this.users.selected.map(a => a.id);
|
let selectedUsersId = this.users.selected.map((a) => a.id);
|
||||||
this.calendarEvents.selected = this.calendarEvents.loaded.filter(a => selectedUsersId.includes(a.id));
|
this.calendarEvents.selected = this.calendarEvents.loaded.filter(
|
||||||
|
(a) => selectedUsersId.includes(a.id),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
selectUsers(value) {
|
selectUsers(value) {
|
||||||
this.users.selected.push(value);
|
this.users.selected.push(value);
|
||||||
@@ -191,16 +243,17 @@ export default {
|
|||||||
this.updateEventsSource();
|
this.updateEventsSource();
|
||||||
},
|
},
|
||||||
unSelectUsers(value) {
|
unSelectUsers(value) {
|
||||||
this.users.selected = this.users.selected.filter(a => a.id != value.id);
|
this.users.selected = this.users.selected.filter(
|
||||||
|
(a) => a.id != value.id,
|
||||||
|
);
|
||||||
this.selectEvents();
|
this.selectEvents();
|
||||||
this.updateEventsSource();
|
this.updateEventsSource();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||||
|
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
import { multiSelectMessages } from 'ChillMainAssets/vuejs/_js/i18n'
|
import { multiSelectMessages } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
|
|
||||||
const calendarUserSelectorMessages = {
|
const calendarUserSelectorMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
choose_your_calendar_user: "Afficher les plages de disponibilités",
|
choose_your_calendar_user: "Afficher les plages de disponibilités",
|
||||||
select_user: "Sélectionnez des calendriers",
|
select_user: "Sélectionnez des calendriers",
|
||||||
show_my_calendar: "Afficher mon calendrier",
|
show_my_calendar: "Afficher mon calendrier",
|
||||||
show_weekends: "Afficher les week-ends"
|
show_weekends: "Afficher les week-ends",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(calendarUserSelectorMessages.fr, multiSelectMessages.fr);
|
Object.assign(calendarUserSelectorMessages.fr, multiSelectMessages.fr);
|
||||||
|
|
||||||
export {
|
export { calendarUserSelectorMessages };
|
||||||
calendarUserSelectorMessages
|
|
||||||
};
|
|
||||||
|
|
||||||
|
@@ -18,8 +18,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
||||||
|
|
||||||
use Monolog\DateTimeImmutable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* * Lundi => Envoi des rdv du mardi et mercredi.
|
* * Lundi => Envoi des rdv du mardi et mercredi.
|
||||||
* * Mardi => Envoi des rdv du jeudi.
|
* * Mardi => Envoi des rdv du jeudi.
|
||||||
@@ -31,7 +29,7 @@ class DefaultRangeGenerator implements RangeGeneratorInterface
|
|||||||
{
|
{
|
||||||
public function generateRange(\DateTimeImmutable $date): ?array
|
public function generateRange(\DateTimeImmutable $date): ?array
|
||||||
{
|
{
|
||||||
$onMidnight = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d').' 00:00:00');
|
$onMidnight = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d').' 00:00:00');
|
||||||
|
|
||||||
switch ($dow = (int) $onMidnight->format('w')) {
|
switch ($dow = (int) $onMidnight->format('w')) {
|
||||||
case 6: // Saturday
|
case 6: // Saturday
|
||||||
|
@@ -1,14 +1,25 @@
|
|||||||
// this file loads all assets from the Chill calendar bundle
|
// this file loads all assets from the Chill calendar bundle
|
||||||
module.exports = function(encore, entries) {
|
module.exports = function (encore, entries) {
|
||||||
|
entries.push(__dirname + "/Resources/public/chill/chill.js");
|
||||||
entries.push(__dirname + '/Resources/public/chill/chill.js');
|
|
||||||
|
|
||||||
encore.addAliases({
|
encore.addAliases({
|
||||||
ChillCalendarAssets: __dirname + '/Resources/public'
|
ChillCalendarAssets: __dirname + "/Resources/public",
|
||||||
});
|
});
|
||||||
|
|
||||||
encore.addEntry('vue_calendar', __dirname + '/Resources/public/vuejs/Calendar/index.js');
|
encore.addEntry(
|
||||||
encore.addEntry('vue_mycalendarrange', __dirname + '/Resources/public/vuejs/MyCalendarRange/index2.ts');
|
"vue_calendar",
|
||||||
encore.addEntry('page_calendar', __dirname + '/Resources/public/chill/index.js');
|
__dirname + "/Resources/public/vuejs/Calendar/index.js",
|
||||||
encore.addEntry('mod_answer', __dirname + '/Resources/public/module/Invite/answer.js');
|
);
|
||||||
|
encore.addEntry(
|
||||||
|
"vue_mycalendarrange",
|
||||||
|
__dirname + "/Resources/public/vuejs/MyCalendarRange/index2.ts",
|
||||||
|
);
|
||||||
|
encore.addEntry(
|
||||||
|
"page_calendar",
|
||||||
|
__dirname + "/Resources/public/chill/index.js",
|
||||||
|
);
|
||||||
|
encore.addEntry(
|
||||||
|
"mod_answer",
|
||||||
|
__dirname + "/Resources/public/module/Invite/answer.js",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@@ -298,7 +298,7 @@ class CustomFieldsGroupController extends AbstractController
|
|||||||
->setCustomFieldsGroup($customFieldsGroup);
|
->setCustomFieldsGroup($customFieldsGroup);
|
||||||
|
|
||||||
$builder = $this->get('form.factory')
|
$builder = $this->get('form.factory')
|
||||||
->createNamedBuilder(null, FormType::class, $customfield, [
|
->createNamedBuilder('', FormType::class, $customfield, [
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'action' => $this->generateUrl('customfield_new'),
|
'action' => $this->generateUrl('customfield_new'),
|
||||||
'csrf_protection' => false,
|
'csrf_protection' => false,
|
||||||
|
@@ -3,8 +3,6 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.ts";
|
|||||||
const fetchTemplates = (entityClass) => {
|
const fetchTemplates = (entityClass) => {
|
||||||
let fqdnEntityClass = encodeURI(entityClass);
|
let fqdnEntityClass = encodeURI(entityClass);
|
||||||
return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`);
|
return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`);
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
fetchTemplates
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { fetchTemplates };
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
|
const buildLink = function (templateId, entityId, entityClass) {
|
||||||
const buildLink = function(templateId, entityId, entityClass) {
|
const entityIdEncoded = encodeURI(entityId),
|
||||||
const
|
returnPath = encodeURIComponent(
|
||||||
entityIdEncoded = encodeURI(entityId),
|
window.location.pathname + window.location.search + window.location.hash,
|
||||||
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
|
),
|
||||||
entityClassEncoded = encodeURI(entityClass),
|
entityClassEncoded = encodeURI(entityClass),
|
||||||
url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}`
|
url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}`;
|
||||||
;
|
console.log("computed Url");
|
||||||
console.log('computed Url');
|
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
export {buildLink};
|
export { buildLink };
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
import {createApp} from 'vue';
|
import { createApp } from "vue";
|
||||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
import PickTemplate from "ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue";
|
||||||
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
|
import { fetchTemplates } from "ChillDocGeneratorAssets/api/pickTemplate.js";
|
||||||
import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n';
|
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
|
|
||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
|
document.querySelectorAll("div[data-docgen-template-picker]").forEach((el) => {
|
||||||
fetchTemplates(el.dataset.entityClass).then(templates => {
|
fetchTemplates(el.dataset.entityClass).then((templates) => {
|
||||||
let
|
let picker = {
|
||||||
picker = {
|
template:
|
||||||
template: '<pick-template :templates="this.templates" :entityId="this.entityId"></pick-template>',
|
'<pick-template :templates="this.templates" :entityId="this.entityId"></pick-template>',
|
||||||
components: {
|
components: {
|
||||||
PickTemplate,
|
PickTemplate,
|
||||||
},
|
},
|
||||||
@@ -17,11 +17,9 @@ document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
|
|||||||
return {
|
return {
|
||||||
templates: templates,
|
templates: templates,
|
||||||
entityId: el.dataset.entityId,
|
entityId: el.dataset.entityId,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
;
|
|
||||||
createApp(picker).use(i18n).mount(el);
|
createApp(picker).use(i18n).mount(el);
|
||||||
})
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -2,26 +2,44 @@
|
|||||||
<div>
|
<div>
|
||||||
<template v-if="templates.length > 0">
|
<template v-if="templates.length > 0">
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
<h2>{{ $t('generate_document')}}</h2>
|
<h2>{{ $t("generate_document") }}</h2>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<slot name="label">
|
<slot name="label">
|
||||||
<label>{{ $t('select_a_template') }}</label>
|
<label>{{ $t("select_a_template") }}</label>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<select class="form-select" v-model="template">
|
<select class="form-select" v-model="template">
|
||||||
<option disabled selected value="">{{ $t('choose_a_template') }}</option>
|
<option disabled selected value="">
|
||||||
|
{{ $t("choose_a_template") }}
|
||||||
|
</option>
|
||||||
<template v-for="t in templates" :key="t.id">
|
<template v-for="t in templates" :key="t.id">
|
||||||
<option :value="t.id" >{{ t.name.fr || 'Aucun nom défini' }}</option>
|
<option :value="t.id">
|
||||||
|
{{ t.name.fr || "Aucun nom défini" }}
|
||||||
|
</option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a>
|
<a
|
||||||
<a v-else class="btn btn-update btn-sm change-icon" href="#" disabled ><i class="fa fa-fw fa-cog"></i></a>
|
v-if="canGenerate"
|
||||||
|
class="btn btn-update btn-sm change-icon"
|
||||||
|
:href="buildUrlGenerate"
|
||||||
|
@click.prevent="
|
||||||
|
clickGenerate($event, buildUrlGenerate)
|
||||||
|
"
|
||||||
|
><i class="fa fa-fw fa-cog"
|
||||||
|
/></a>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
class="btn btn-update btn-sm change-icon"
|
||||||
|
href="#"
|
||||||
|
disabled
|
||||||
|
><i class="fa fa-fw fa-cog"
|
||||||
|
/></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,16 +48,13 @@
|
|||||||
<p>{{ getDescription }}</p>
|
<p>{{ getDescription }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
|
||||||
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PickTemplate",
|
name: "PickTemplate",
|
||||||
@@ -57,13 +72,13 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: ['goToGenerateDocument'],
|
},
|
||||||
|
emits: ["goToGenerateDocument"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
template: null,
|
template: null,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
canGenerate() {
|
canGenerate() {
|
||||||
@@ -78,21 +93,21 @@ export default {
|
|||||||
},
|
},
|
||||||
getDescription() {
|
getDescription() {
|
||||||
if (null === this.template) {
|
if (null === this.template) {
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
let desc = this.templates.find(t => t.id === this.template);
|
let desc = this.templates.find((t) => t.id === this.template);
|
||||||
if (null === desc) {
|
if (null === desc) {
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
return desc.description || '';
|
return desc.description || "";
|
||||||
},
|
},
|
||||||
buildUrlGenerate() {
|
buildUrlGenerate() {
|
||||||
if (null === this.template) {
|
if (null === this.template) {
|
||||||
return '#';
|
return "#";
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildLink(this.template, this.entityId, this.entityClass);
|
return buildLink(this.template, this.entityId, this.entityClass);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clickGenerate(event, link) {
|
clickGenerate(event, link) {
|
||||||
@@ -100,21 +115,23 @@ export default {
|
|||||||
window.location.assign(link);
|
window.location.assign(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('goToGenerateDocument', {event, link, template: this.template});
|
this.$emit("goToGenerateDocument", {
|
||||||
|
event,
|
||||||
|
link,
|
||||||
|
template: this.template,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
messages: {
|
messages: {
|
||||||
fr: {
|
fr: {
|
||||||
generate_document: 'Générer un document',
|
generate_document: "Générer un document",
|
||||||
select_a_template: 'Choisir un modèle',
|
select_a_template: "Choisir un modèle",
|
||||||
choose_a_template: 'Choisir',
|
choose_a_template: "Choisir",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
// this file loads all assets from the Chill DocGenerator bundle
|
// this file loads all assets from the Chill DocGenerator bundle
|
||||||
module.exports = function(encore, entries) {
|
module.exports = function (encore, entries) {
|
||||||
encore.addAliases({
|
encore.addAliases({
|
||||||
ChillDocGeneratorAssets: __dirname + '/Resources/public'
|
ChillDocGeneratorAssets: __dirname + "/Resources/public",
|
||||||
});
|
});
|
||||||
|
|
||||||
encore.addEntry('mod_docgen_picktemplate', __dirname + '/Resources/public/module/PickTemplate/index.js');
|
encore.addEntry(
|
||||||
|
"mod_docgen_picktemplate",
|
||||||
|
__dirname + "/Resources/public/module/PickTemplate/index.js",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,227 @@
|
|||||||
|
<?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\DocStoreBundle\AsyncUpload\Driver\LocalStorage;
|
||||||
|
|
||||||
|
use Base64Url\Base64Url;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
|
||||||
|
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
||||||
|
use Chill\DocStoreBundle\Service\Cryptography\KeyGenerator;
|
||||||
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Symfony\Component\Filesystem\Path;
|
||||||
|
|
||||||
|
class StoredObjectManager implements StoredObjectManagerInterface
|
||||||
|
{
|
||||||
|
private readonly string $baseDir;
|
||||||
|
|
||||||
|
private readonly Filesystem $filesystem;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ParameterBagInterface $parameterBag,
|
||||||
|
private readonly KeyGenerator $keyGenerator,
|
||||||
|
) {
|
||||||
|
$this->baseDir = $parameterBag->get('chill_doc_store')['local_storage']['storage_path'];
|
||||||
|
$this->filesystem = new Filesystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastModified(StoredObject|StoredObjectVersion $document): \DateTimeInterface
|
||||||
|
{
|
||||||
|
$version = $document instanceof StoredObject ? $document->getCurrentVersion() : $document;
|
||||||
|
|
||||||
|
if (null === $version) {
|
||||||
|
throw StoredObjectManagerException::storedObjectDoesNotContainsVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $this->buildPath($version->getFilename());
|
||||||
|
|
||||||
|
if (false === $ts = filemtime($path)) {
|
||||||
|
throw StoredObjectManagerException::unableToReadDocumentOnDisk($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \DateTimeImmutable::createFromFormat('U', (string) $ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContentLength(StoredObject|StoredObjectVersion $document): int
|
||||||
|
{
|
||||||
|
return strlen($this->read($document));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exists(StoredObject|StoredObjectVersion $document): bool
|
||||||
|
{
|
||||||
|
$version = $document instanceof StoredObject ? $document->getCurrentVersion() : $document;
|
||||||
|
|
||||||
|
if (null === $version) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->existsContent($version->getFilename());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read(StoredObject|StoredObjectVersion $document): string
|
||||||
|
{
|
||||||
|
$version = $document instanceof StoredObject ? $document->getCurrentVersion() : $document;
|
||||||
|
|
||||||
|
if (null === $version) {
|
||||||
|
throw StoredObjectManagerException::storedObjectDoesNotContainsVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $this->readContent($version->getFilename());
|
||||||
|
|
||||||
|
if (!$this->isVersionEncrypted($version)) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$clearData = openssl_decrypt(
|
||||||
|
$content,
|
||||||
|
self::ALGORITHM,
|
||||||
|
// TODO: Why using this library and not use base64_decode() ?
|
||||||
|
Base64Url::decode($version->getKeyInfos()['k']),
|
||||||
|
\OPENSSL_RAW_DATA,
|
||||||
|
pack('C*', ...$version->getIv())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (false === $clearData) {
|
||||||
|
throw StoredObjectManagerException::unableToDecrypt(openssl_error_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clearData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function write(StoredObject $document, string $clearContent, ?string $contentType = null): StoredObjectVersion
|
||||||
|
{
|
||||||
|
$newIv = $document->isEncrypted() ? $document->getIv() : $this->keyGenerator->generateIv();
|
||||||
|
$newKey = $document->isEncrypted() ? $document->getKeyInfos() : $this->keyGenerator->generateKey(self::ALGORITHM);
|
||||||
|
$newType = $contentType ?? $document->getType();
|
||||||
|
$version = $document->registerVersion(
|
||||||
|
$newIv,
|
||||||
|
$newKey,
|
||||||
|
$newType
|
||||||
|
);
|
||||||
|
|
||||||
|
$encryptedContent = $this->isVersionEncrypted($version)
|
||||||
|
? openssl_encrypt(
|
||||||
|
$clearContent,
|
||||||
|
self::ALGORITHM,
|
||||||
|
// TODO: Why using this library and not use base64_decode() ?
|
||||||
|
Base64Url::decode($version->getKeyInfos()['k']),
|
||||||
|
\OPENSSL_RAW_DATA,
|
||||||
|
pack('C*', ...$version->getIv())
|
||||||
|
)
|
||||||
|
: $clearContent;
|
||||||
|
|
||||||
|
if (false === $encryptedContent) {
|
||||||
|
throw StoredObjectManagerException::unableToEncryptDocument((string) openssl_error_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeContent($version->getFilename(), $encryptedContent);
|
||||||
|
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readContent(string $filename): string
|
||||||
|
{
|
||||||
|
$path = $this->buildPath($filename);
|
||||||
|
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
throw StoredObjectManagerException::unableToFindDocumentOnDisk($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $content = file_get_contents($path)) {
|
||||||
|
throw StoredObjectManagerException::unableToReadDocumentOnDisk($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeContent(string $filename, string $encryptedContent): void
|
||||||
|
{
|
||||||
|
$fullPath = $this->buildPath($filename);
|
||||||
|
$dir = Path::getDirectory($fullPath);
|
||||||
|
|
||||||
|
if (!$this->filesystem->exists($dir)) {
|
||||||
|
$this->filesystem->mkdir($dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = file_put_contents($fullPath, $encryptedContent);
|
||||||
|
|
||||||
|
if (false === $result) {
|
||||||
|
throw StoredObjectManagerException::unableToStoreDocumentOnDisk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function existsContent(string $filename): bool
|
||||||
|
{
|
||||||
|
$path = $this->buildPath($filename);
|
||||||
|
|
||||||
|
return $this->filesystem->exists($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPath(string $filename): string
|
||||||
|
{
|
||||||
|
$dirs = [$this->baseDir];
|
||||||
|
|
||||||
|
for ($i = 0; $i < min(strlen($filename), 8); ++$i) {
|
||||||
|
$dirs[] = $filename[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
$dirs[] = $filename;
|
||||||
|
|
||||||
|
return Path::canonicalize(implode(DIRECTORY_SEPARATOR, $dirs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(StoredObjectVersion $storedObjectVersion): void
|
||||||
|
{
|
||||||
|
if (!$this->exists($storedObjectVersion)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $this->buildPath($storedObjectVersion->getFilename());
|
||||||
|
|
||||||
|
$this->filesystem->remove($path);
|
||||||
|
$this->removeDirectoriesRecursively(Path::getDirectory($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeDirectoriesRecursively(string $path): void
|
||||||
|
{
|
||||||
|
if ($path === $this->baseDir) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = scandir($path);
|
||||||
|
|
||||||
|
// if it does contains only "." and "..", we can remove the directory
|
||||||
|
if (2 === count($files) && in_array('.', $files, true) && in_array('..', $files, true)) {
|
||||||
|
$this->filesystem->remove($path);
|
||||||
|
$this->removeDirectoriesRecursively(Path::getDirectory($path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws StoredObjectManagerException
|
||||||
|
*/
|
||||||
|
public function etag(StoredObject|StoredObjectVersion $document): string
|
||||||
|
{
|
||||||
|
return md5($this->read($document));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearCache(): void
|
||||||
|
{
|
||||||
|
// there is no cache: nothing to do here !
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isVersionEncrypted(StoredObjectVersion $storedObjectVersion): bool
|
||||||
|
{
|
||||||
|
return $storedObjectVersion->isEncrypted();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,107 @@
|
|||||||
|
<?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\DocStoreBundle\AsyncUpload\Driver\LocalStorage;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\SignedUrl;
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\SignedUrlPost;
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
|
||||||
|
class TempUrlLocalStorageGenerator implements TempUrlGeneratorInterface
|
||||||
|
{
|
||||||
|
private const SIGNATURE_DURATION = 180;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $secret,
|
||||||
|
private readonly ClockInterface $clock,
|
||||||
|
private readonly UrlGeneratorInterface $urlGenerator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function generate(string $method, string $object_name, ?int $expire_delay = null): SignedUrl
|
||||||
|
{
|
||||||
|
$expiration = $this->clock->now()->getTimestamp() + min($expire_delay ?? self::SIGNATURE_DURATION, self::SIGNATURE_DURATION);
|
||||||
|
|
||||||
|
return new SignedUrl(
|
||||||
|
strtoupper($method),
|
||||||
|
$this->urlGenerator->generate('chill_docstore_stored_object_operate', [
|
||||||
|
'object_name' => $object_name,
|
||||||
|
'exp' => $expiration,
|
||||||
|
'sig' => $this->sign(strtoupper($method), $object_name, $expiration),
|
||||||
|
], UrlGeneratorInterface::ABSOLUTE_URL),
|
||||||
|
\DateTimeImmutable::createFromFormat('U', (string) $expiration),
|
||||||
|
$object_name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generatePost(?int $expire_delay = null, ?int $submit_delay = null, int $max_file_count = 1, ?string $object_name = null): SignedUrlPost
|
||||||
|
{
|
||||||
|
$submitDelayComputed = min($submit_delay ?? self::SIGNATURE_DURATION, self::SIGNATURE_DURATION);
|
||||||
|
$expireDelayComputed = min($expire_delay ?? self::SIGNATURE_DURATION, self::SIGNATURE_DURATION);
|
||||||
|
$objectNameComputed = $object_name ?? StoredObject::generatePrefix();
|
||||||
|
$expiration = $this->clock->now()->getTimestamp() + $expireDelayComputed + $submitDelayComputed;
|
||||||
|
|
||||||
|
return new SignedUrlPost(
|
||||||
|
$this->urlGenerator->generate(
|
||||||
|
'chill_docstore_storedobject_post',
|
||||||
|
['prefix' => $objectNameComputed],
|
||||||
|
UrlGeneratorInterface::ABSOLUTE_URL
|
||||||
|
),
|
||||||
|
\DateTimeImmutable::createFromFormat('U', (string) $expiration),
|
||||||
|
$objectNameComputed,
|
||||||
|
15_000_000,
|
||||||
|
1,
|
||||||
|
$submitDelayComputed,
|
||||||
|
'',
|
||||||
|
$objectNameComputed,
|
||||||
|
$this->sign('POST', $object_name, $expiration),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sign(string $method, string $object_name, int $expiration): string
|
||||||
|
{
|
||||||
|
return hash('sha512', sprintf('%s.%s.%s.%d', $method, $this->secret, $object_name, $expiration));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateSignaturePost(string $signature, string $prefix, int $expiration, int $maxFileSize, int $maxFileCount): bool
|
||||||
|
{
|
||||||
|
if (15_000_000 !== $maxFileSize || 1 !== $maxFileCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->internalValidateSignature($signature, 'POST', $prefix, $expiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function internalValidateSignature(string $signature, string $method, string $object_name, int $expiration): bool
|
||||||
|
{
|
||||||
|
if ($expiration < $this->clock->now()->format('U')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $object_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $this->sign($method, $object_name, $expiration) === $signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateSignature(string $signature, string $method, string $objectName, int $expiration): bool
|
||||||
|
{
|
||||||
|
if (!in_array($method, ['GET', 'HEAD'], true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->internalValidateSignature($signature, $method, $objectName, $expiration);
|
||||||
|
}
|
||||||
|
}
|
@@ -9,7 +9,7 @@ declare(strict_types=1);
|
|||||||
* the LICENSE file that was distributed with this source code.
|
* the LICENSE file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Chill\DocStoreBundle\AsyncUpload\Command;
|
namespace Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore;
|
||||||
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
@@ -9,13 +9,14 @@ declare(strict_types=1);
|
|||||||
* the LICENSE file that was distributed with this source code.
|
* the LICENSE file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Chill\DocStoreBundle\Service;
|
namespace Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore;
|
||||||
|
|
||||||
use Base64Url\Base64Url;
|
use Base64Url\Base64Url;
|
||||||
use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
|
use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
|
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
|
||||||
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
||||||
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||||
@@ -24,8 +25,6 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
|
|||||||
|
|
||||||
final class StoredObjectManager implements StoredObjectManagerInterface
|
final class StoredObjectManager implements StoredObjectManagerInterface
|
||||||
{
|
{
|
||||||
private const ALGORITHM = 'AES-256-CBC';
|
|
||||||
|
|
||||||
private array $inMemory = [];
|
private array $inMemory = [];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -361,6 +360,6 @@ final class StoredObjectManager implements StoredObjectManagerInterface
|
|||||||
|
|
||||||
private function isVersionEncrypted(StoredObjectVersion $storedObjectVersion): bool
|
private function isVersionEncrypted(StoredObjectVersion $storedObjectVersion): bool
|
||||||
{
|
{
|
||||||
return ([] !== $storedObjectVersion->getKeyInfos()) && ([] !== $storedObjectVersion->getIv());
|
return $storedObjectVersion->isEncrypted();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle;
|
namespace Chill\DocStoreBundle;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\DependencyInjection\Compiler\StorageConfigurationCompilerPass;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||||
@@ -27,5 +28,7 @@ class ChillDocStoreBundle extends Bundle
|
|||||||
->addTag('chill_doc_store.generic_doc_person_provider');
|
->addTag('chill_doc_store.generic_doc_person_provider');
|
||||||
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
|
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
|
||||||
->addTag('chill_doc_store.generic_doc_renderer');
|
->addTag('chill_doc_store.generic_doc_renderer');
|
||||||
|
|
||||||
|
$container->addCompilerPass(new StorageConfigurationCompilerPass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,120 @@
|
|||||||
|
<?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\DocStoreBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\StoredObjectManager;
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller to deal with local storage operation.
|
||||||
|
*/
|
||||||
|
final readonly class StoredObjectContentToLocalStorageController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private StoredObjectManager $storedObjectManager,
|
||||||
|
private TempUrlLocalStorageGenerator $tempUrlLocalStorageGenerator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/public/stored-object/post', name: 'chill_docstore_storedobject_post', methods: ['POST'])]
|
||||||
|
public function postContent(Request $request): Response
|
||||||
|
{
|
||||||
|
$prefix = $request->query->get('prefix', '');
|
||||||
|
|
||||||
|
if ('' === $prefix) {
|
||||||
|
throw new BadRequestHttpException('Prefix parameter is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $maxFileSize = $request->request->getInt('max_file_size', 0)) {
|
||||||
|
throw new BadRequestHttpException('Max file size is not set or equal to zero');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 !== $maxFileCount = $request->request->getInt('max_file_count', 0)) {
|
||||||
|
throw new BadRequestHttpException('Max file count is not set or equal to zero');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $expiration = $request->request->getInt('expires', 0)) {
|
||||||
|
throw new BadRequestHttpException('Expiration is not set or equal to zero');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $signature = $request->request->get('signature', '')) {
|
||||||
|
throw new BadRequestHttpException('Signature is not set or is a blank string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->tempUrlLocalStorageGenerator->validateSignaturePost($signature, $prefix, $expiration, $maxFileSize, $maxFileCount)) {
|
||||||
|
throw new AccessDeniedHttpException('Invalid signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyFiles = $request->files->keys();
|
||||||
|
|
||||||
|
if ($maxFileCount < count($keyFiles)) {
|
||||||
|
throw new AccessDeniedHttpException('More files than max file count');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === count($keyFiles)) {
|
||||||
|
throw new BadRequestHttpException('Zero files given');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($keyFiles as $keyFile) {
|
||||||
|
/** @var UploadedFile $file */
|
||||||
|
$file = $request->files->get($keyFile);
|
||||||
|
|
||||||
|
if ($maxFileSize < strlen($file->getContent())) {
|
||||||
|
throw new AccessDeniedHttpException('File is too big');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!str_starts_with((string) $keyFile, $prefix)) {
|
||||||
|
throw new AccessDeniedHttpException('Filename does not start with signed prefix');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->storedObjectManager->writeContent($keyFile, $file->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(status: Response::HTTP_NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/public/stored-object/operate', name: 'chill_docstore_stored_object_operate', methods: ['GET', 'HEAD'])]
|
||||||
|
public function contentOperate(Request $request): Response
|
||||||
|
{
|
||||||
|
if ('' === $objectName = $request->query->get('object_name', '')) {
|
||||||
|
throw new BadRequestHttpException('Object name parameter is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $expiration = $request->query->getInt('exp', 0)) {
|
||||||
|
throw new BadRequestHttpException('Expiration is not set or equal to zero');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $signature = $request->query->get('sig', '')) {
|
||||||
|
throw new BadRequestHttpException('Signature is not set or is a blank string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->tempUrlLocalStorageGenerator->validateSignature($signature, strtoupper($request->getMethod()), $objectName, $expiration)) {
|
||||||
|
throw new AccessDeniedHttpException('Invalid signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->storedObjectManager->existsContent($objectName)) {
|
||||||
|
throw new NotFoundHttpException('Object does not exists on disk');
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ($request->getMethod()) {
|
||||||
|
'GET' => new Response($this->storedObjectManager->readContent($objectName)),
|
||||||
|
'HEAD' => new Response(''),
|
||||||
|
default => throw new BadRequestHttpException('method not supported'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -53,7 +53,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
|||||||
$this->prependTwig($container);
|
$this->prependTwig($container);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prependAuthorization(ContainerBuilder $container)
|
private function prependAuthorization(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$container->prependExtensionConfig('security', [
|
$container->prependExtensionConfig('security', [
|
||||||
'role_hierarchy' => [
|
'role_hierarchy' => [
|
||||||
@@ -69,7 +69,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prependRoute(ContainerBuilder $container)
|
private function prependRoute(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
// declare routes for task bundle
|
// declare routes for task bundle
|
||||||
$container->prependExtensionConfig('chill_main', [
|
$container->prependExtensionConfig('chill_main', [
|
||||||
@@ -81,7 +81,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prependTwig(ContainerBuilder $container)
|
private function prependTwig(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$twigConfig = [
|
$twigConfig = [
|
||||||
'form_themes' => ['@ChillDocStore/Form/fields.html.twig'],
|
'form_themes' => ['@ChillDocStore/Form/fields.html.twig'],
|
||||||
|
@@ -0,0 +1,86 @@
|
|||||||
|
<?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\DocStoreBundle\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator;
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\ConfigureOpenstackObjectStorageCommand;
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\TempUrlOpenstackGenerator;
|
||||||
|
use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
|
||||||
|
use Chill\DocStoreBundle\Controller\StoredObjectContentToLocalStorageController;
|
||||||
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
|
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
|
||||||
|
class StorageConfigurationCompilerPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
private const SERVICES_OPENSTACK = [
|
||||||
|
\Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\StoredObjectManager::class,
|
||||||
|
TempUrlOpenstackGenerator::class,
|
||||||
|
ConfigureOpenstackObjectStorageCommand::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const SERVICES_LOCAL_STORAGE = [
|
||||||
|
\Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\StoredObjectManager::class,
|
||||||
|
TempUrlLocalStorageGenerator::class,
|
||||||
|
StoredObjectContentToLocalStorageController::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
$config = $container
|
||||||
|
->getParameterBag()
|
||||||
|
->resolveValue($container->getParameter('chill_doc_store'));
|
||||||
|
|
||||||
|
if (array_key_exists('local_storage', $config) && !array_key_exists('openstack', $config)) {
|
||||||
|
$driver = 'local_storage';
|
||||||
|
$this->checkUseDriverConfiguration($config['use_driver'] ?? null, $driver);
|
||||||
|
} elseif (!array_key_exists('local_storage', $config) && array_key_exists('openstack', $config)) {
|
||||||
|
$driver = 'openstack';
|
||||||
|
$this->checkUseDriverConfiguration($config['use_driver'] ?? null, $driver);
|
||||||
|
} elseif (array_key_exists('openstack', $config) && array_key_exists('local_storage', $config)) {
|
||||||
|
$driver = $config['use_driver'] ?? null;
|
||||||
|
if (null === $driver) {
|
||||||
|
throw new InvalidConfigurationException('There are multiple drivers configured for chill_doc_store, set the one you want to use with the variable use_driver');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new InvalidConfigurationException('No driver defined for storing document. Define one in chill_doc_store configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('local_storage' === $driver) {
|
||||||
|
foreach (self::SERVICES_OPENSTACK as $service) {
|
||||||
|
$container->removeDefinition($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
$container->setAlias(StoredObjectManagerInterface::class, \Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\StoredObjectManager::class);
|
||||||
|
$container->setAlias(TempUrlGeneratorInterface::class, TempUrlLocalStorageGenerator::class);
|
||||||
|
} else {
|
||||||
|
foreach (self::SERVICES_LOCAL_STORAGE as $service) {
|
||||||
|
$container->removeDefinition($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
$container->setAlias(StoredObjectManagerInterface::class, \Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\StoredObjectManager::class);
|
||||||
|
$container->setAlias(TempUrlGeneratorInterface::class, TempUrlOpenstackGenerator::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkUseDriverConfiguration(?string $useDriver, string $driver): void
|
||||||
|
{
|
||||||
|
if (null === $useDriver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($useDriver !== $driver) {
|
||||||
|
throw new InvalidConfigurationException(sprintf('The "use_driver" configuration require a driver (%s) which is not configured. Configure this driver in order to use it.', $useDriver));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -30,10 +30,22 @@ class Configuration implements ConfigurationInterface
|
|||||||
|
|
||||||
/* @phpstan-ignore-next-line As there are inconsistencies in return types, but the code works... */
|
/* @phpstan-ignore-next-line As there are inconsistencies in return types, but the code works... */
|
||||||
$rootNode->children()
|
$rootNode->children()
|
||||||
|
->enumNode('use_driver')
|
||||||
|
->values(['local_storage', 'openstack'])
|
||||||
|
->info('Driver to use. Default to the single one if multiple driver are defined. Configuration will raise an error if there are multiple drivers defined, and if this key is not set')
|
||||||
|
->end()
|
||||||
|
->arrayNode('local_storage')
|
||||||
|
->info('where the stored object should be stored')
|
||||||
|
->children()
|
||||||
|
->scalarNode('storage_path')
|
||||||
|
->info('the folder where the stored object should be stored')
|
||||||
|
->isRequired()->cannotBeEmpty()
|
||||||
|
->end() // end of storage_path
|
||||||
|
->end() // end of children
|
||||||
|
->end() // end of local_storage
|
||||||
// openstack node
|
// openstack node
|
||||||
->arrayNode('openstack')
|
->arrayNode('openstack')
|
||||||
->info('parameters to authenticate and generate temp url against the openstack object storage service')
|
->info('parameters to authenticate and generate temp url against the openstack object storage service')
|
||||||
->addDefaultsIfNotSet()
|
|
||||||
->children()
|
->children()
|
||||||
// openstack.temp_url
|
// openstack.temp_url
|
||||||
->arrayNode('temp_url')
|
->arrayNode('temp_url')
|
||||||
|
@@ -448,4 +448,12 @@ class StoredObject implements Document, TrackCreationInterface
|
|||||||
{
|
{
|
||||||
return $storedObject->getDeleteAt() < $now && $storedObject->getVersions()->isEmpty();
|
return $storedObject->getDeleteAt() < $now && $storedObject->getVersions()->isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if it has a current version, and if the current version is encrypted.
|
||||||
|
*/
|
||||||
|
public function isEncrypted(): bool
|
||||||
|
{
|
||||||
|
return $this->hasCurrentVersion() && $this->getCurrentVersion()->isEncrypted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -226,4 +226,9 @@ class StoredObjectVersion implements TrackCreationInterface
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isEncrypted(): bool
|
||||||
|
{
|
||||||
|
return ([] !== $this->getKeyInfos()) && ([] !== $this->getIv());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,4 +34,29 @@ final class StoredObjectManagerException extends \Exception
|
|||||||
{
|
{
|
||||||
return new self('Unable to get content from response.', 500, $exception);
|
return new self('Unable to get content from response.', 500, $exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function unableToStoreDocumentOnDisk(?\Throwable $exception = null): self
|
||||||
|
{
|
||||||
|
return new self('Unable to store document on disk.', previous: $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unableToFindDocumentOnDisk(string $path): self
|
||||||
|
{
|
||||||
|
return new self('Unable to find document on disk at path "'.$path.'".');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unableToReadDocumentOnDisk(string $path): self
|
||||||
|
{
|
||||||
|
return new self('Unable to read document on disk at path "'.$path.'".');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unableToEncryptDocument(string $errors): self
|
||||||
|
{
|
||||||
|
return new self('Unable to encrypt document: '.$errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function storedObjectDoesNotContainsVersion(): self
|
||||||
|
{
|
||||||
|
return new self('Stored object does not contains any version');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,19 @@
|
|||||||
import {makeFetch} from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
import {PostStoreObjectSignature, StoredObject} from "../../types";
|
import { PostStoreObjectSignature, StoredObject } from "../../types";
|
||||||
|
|
||||||
const algo = 'AES-CBC';
|
const algo = "AES-CBC";
|
||||||
|
|
||||||
const URL_POST = '/asyncupload/temp_url/generate/post';
|
const URL_POST = "/asyncupload/temp_url/generate/post";
|
||||||
|
|
||||||
const keyDefinition = {
|
const keyDefinition = {
|
||||||
name: algo,
|
name: algo,
|
||||||
length: 256
|
length: 256,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFilename = (): string => {
|
const createFilename = (): string => {
|
||||||
var text = "";
|
let text = "";
|
||||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const possible =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
@@ -29,14 +30,22 @@ const createFilename = (): string => {
|
|||||||
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
|
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
|
||||||
*/
|
*/
|
||||||
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
|
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
|
||||||
return makeFetch("POST", '/api/1.0/doc-store/stored-object/create', null);
|
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const uploadVersion = async (uploadFile: ArrayBuffer, storedObject: StoredObject): Promise<string> => {
|
export const uploadVersion = async (
|
||||||
|
uploadFile: ArrayBuffer,
|
||||||
|
storedObject: StoredObject,
|
||||||
|
): Promise<string> => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('expires_delay', "180");
|
params.append("expires_delay", "180");
|
||||||
params.append('submit_delay', "180");
|
params.append("submit_delay", "180");
|
||||||
const asyncData: PostStoreObjectSignature = await makeFetch("GET", `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` + "?" + params.toString());
|
const asyncData: PostStoreObjectSignature = await makeFetch(
|
||||||
|
"GET",
|
||||||
|
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
||||||
|
"?" +
|
||||||
|
params.toString(),
|
||||||
|
);
|
||||||
const suffix = createFilename();
|
const suffix = createFilename();
|
||||||
const filename = asyncData.prefix + suffix;
|
const filename = asyncData.prefix + suffix;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -50,7 +59,7 @@ export const uploadVersion = async (uploadFile: ArrayBuffer, storedObject: Store
|
|||||||
const response = await window.fetch(asyncData.url, {
|
const response = await window.fetch(asyncData.url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Error while sending file to store", response);
|
console.error("Error while sending file to store", response);
|
||||||
@@ -58,13 +67,22 @@ export const uploadVersion = async (uploadFile: ArrayBuffer, storedObject: Store
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(filename);
|
return Promise.resolve(filename);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const encryptFile = async (originalFile: ArrayBuffer): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
export const encryptFile = async (
|
||||||
|
originalFile: ArrayBuffer,
|
||||||
|
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||||
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [ "encrypt", "decrypt" ]);
|
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
||||||
const exportedKey = await window.crypto.subtle.exportKey('jwk', key);
|
"encrypt",
|
||||||
const encrypted = await window.crypto.subtle.encrypt({ name: algo, iv: iv}, key, originalFile);
|
"decrypt",
|
||||||
|
]);
|
||||||
|
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||||
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
|
{ name: algo, iv: iv },
|
||||||
|
key,
|
||||||
|
originalFile,
|
||||||
|
);
|
||||||
|
|
||||||
return Promise.resolve([encrypted, iv, exportedKey]);
|
return Promise.resolve([encrypted, iv, exportedKey]);
|
||||||
};
|
};
|
||||||
|
@@ -1,29 +1,27 @@
|
|||||||
var mime = require('mime');
|
var mime = require("mime");
|
||||||
|
|
||||||
var algo = 'AES-CBC';
|
var algo = "AES-CBC";
|
||||||
|
|
||||||
var initializeButtons = (root) => {
|
var initializeButtons = (root) => {
|
||||||
var
|
var buttons = root.querySelectorAll("a[data-download-button]");
|
||||||
buttons = root.querySelectorAll('a[data-download-button]');
|
|
||||||
|
|
||||||
for (let i = 0; i < buttons.length; i ++) {
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
initialize(buttons[i]);
|
initialize(buttons[i]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var initialize = (button) => {
|
var initialize = (button) => {
|
||||||
button.addEventListener('click', onClick);
|
button.addEventListener("click", onClick);
|
||||||
};
|
};
|
||||||
|
|
||||||
var onClick = e => download(e.target);
|
var onClick = (e) => download(e.target);
|
||||||
|
|
||||||
var download = (button) => {
|
var download = (button) => {
|
||||||
var
|
var keyData = JSON.parse(button.dataset.key),
|
||||||
keyData = JSON.parse(button.dataset.key),
|
|
||||||
ivData = JSON.parse(button.dataset.iv),
|
ivData = JSON.parse(button.dataset.iv),
|
||||||
iv = new Uint8Array(ivData),
|
iv = new Uint8Array(ivData),
|
||||||
urlGenerator = button.dataset.tempUrlGetGenerator,
|
urlGenerator = button.dataset.tempUrlGetGenerator,
|
||||||
hasFilename = 'filename' in button.dataset,
|
hasFilename = "filename" in button.dataset,
|
||||||
filename = button.dataset.filename,
|
filename = button.dataset.filename,
|
||||||
labelPreparing = button.dataset.labelPreparing,
|
labelPreparing = button.dataset.labelPreparing,
|
||||||
labelReady = button.dataset.labelReady,
|
labelReady = button.dataset.labelReady,
|
||||||
@@ -31,65 +29,70 @@ var download = (button) => {
|
|||||||
extension = mime.getExtension(mimeType),
|
extension = mime.getExtension(mimeType),
|
||||||
decryptError = "Error while decrypting file",
|
decryptError = "Error while decrypting file",
|
||||||
fetchError = "Error while fetching file",
|
fetchError = "Error while fetching file",
|
||||||
key, url
|
key,
|
||||||
;
|
url;
|
||||||
|
|
||||||
button.textContent = labelPreparing;
|
button.textContent = labelPreparing;
|
||||||
|
|
||||||
window.fetch(urlGenerator)
|
window
|
||||||
|
.fetch(urlGenerator)
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
return r.json();
|
return r.json();
|
||||||
} else {
|
} else {
|
||||||
throw new Error("error while downloading url " + r.status + " " + r.statusText);
|
throw new Error(
|
||||||
|
"error while downloading url " + r.status + " " + r.statusText,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
return window.fetch(data.url);
|
return window.fetch(data.url);
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.arrayBuffer();
|
return response.arrayBuffer();
|
||||||
}
|
}
|
||||||
throw new Error(response.status + response.statusText);
|
throw new Error(response.status + response.statusText);
|
||||||
})
|
})
|
||||||
.then(buffer => {
|
.then((buffer) => {
|
||||||
if (keyData.alg !== undefined) {
|
if (keyData.alg !== undefined) {
|
||||||
return window.crypto.subtle
|
return window.crypto.subtle
|
||||||
.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt'])
|
.importKey("jwk", keyData, { name: algo, iv: iv }, false, ["decrypt"])
|
||||||
.then(key => {
|
.then((key) => {
|
||||||
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
|
return window.crypto.subtle.decrypt(
|
||||||
|
{ name: algo, iv: iv },
|
||||||
|
key,
|
||||||
|
buffer,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve(buffer);
|
return Promise.resolve(buffer);
|
||||||
})
|
})
|
||||||
.then(decrypted => {
|
.then((decrypted) => {
|
||||||
var
|
var blob = new Blob([decrypted], { type: mimeType }),
|
||||||
blob = new Blob([decrypted], { type: mimeType }),
|
url = window.URL.createObjectURL(blob);
|
||||||
url = window.URL.createObjectURL(blob)
|
|
||||||
;
|
|
||||||
button.href = url;
|
button.href = url;
|
||||||
button.target = '_blank';
|
button.target = "_blank";
|
||||||
button.type = mimeType;
|
button.type = mimeType;
|
||||||
button.textContent = labelReady;
|
button.textContent = labelReady;
|
||||||
if (hasFilename) {
|
if (hasFilename) {
|
||||||
|
|
||||||
button.download = filename;
|
button.download = filename;
|
||||||
if (extension !== false) {
|
if (extension !== false) {
|
||||||
button.download = button.download + '.' + extension;
|
button.download = button.download + "." + extension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button.removeEventListener('click', onClick);
|
button.removeEventListener("click", onClick);
|
||||||
button.click();
|
button.click();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
button.textContent = "";
|
button.textContent = "";
|
||||||
button.appendChild(document.createTextNode("error while handling decrypted file"));
|
button.appendChild(
|
||||||
})
|
document.createTextNode("error while handling decrypted file"),
|
||||||
;
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('load', function(e) {
|
window.addEventListener("load", function (e) {
|
||||||
initializeButtons(e.target);
|
initializeButtons(e.target);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
require('./uploader.js');
|
require("./uploader.js");
|
||||||
require('./downloader.js');
|
require("./downloader.js");
|
||||||
|
@@ -1,18 +1,22 @@
|
|||||||
import {CollectionEventPayload} from "../../../../../ChillMainBundle/Resources/public/module/collection";
|
import { CollectionEventPayload } from "../../../../../ChillMainBundle/Resources/public/module/collection";
|
||||||
import {createApp} from "vue";
|
import { createApp } from "vue";
|
||||||
import DropFileWidget from "../../vuejs/DropFileWidget/DropFileWidget.vue"
|
import DropFileWidget from "../../vuejs/DropFileWidget/DropFileWidget.vue";
|
||||||
import {StoredObject, StoredObjectVersion} from "../../types";
|
import { StoredObject, StoredObjectVersion } from "../../types";
|
||||||
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
const startApp = (divElement: HTMLDivElement, collectionEntry: null|HTMLLIElement): void => {
|
const startApp = (
|
||||||
console.log('app started', divElement);
|
divElement: HTMLDivElement,
|
||||||
const input_stored_object: HTMLInputElement|null = divElement.querySelector("input[data-stored-object]");
|
collectionEntry: null | HTMLLIElement,
|
||||||
|
): void => {
|
||||||
|
console.log("app started", divElement);
|
||||||
|
const input_stored_object: HTMLInputElement | null =
|
||||||
|
divElement.querySelector("input[data-stored-object]");
|
||||||
if (null === input_stored_object) {
|
if (null === input_stored_object) {
|
||||||
throw new Error('input to stored object not found');
|
throw new Error("input to stored object not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let existingDoc: StoredObject|null = null;
|
let existingDoc: StoredObject | null = null;
|
||||||
if (input_stored_object.value !== "") {
|
if (input_stored_object.value !== "") {
|
||||||
existingDoc = JSON.parse(input_stored_object.value);
|
existingDoc = JSON.parse(input_stored_object.value);
|
||||||
}
|
}
|
||||||
@@ -20,69 +24,87 @@ const startApp = (divElement: HTMLDivElement, collectionEntry: null|HTMLLIElemen
|
|||||||
divElement.appendChild(app_container);
|
divElement.appendChild(app_container);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: '<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
template:
|
||||||
|
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
||||||
data(vm) {
|
data(vm) {
|
||||||
return {
|
return {
|
||||||
existingDoc: existingDoc,
|
existingDoc: existingDoc,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
DropFileWidget,
|
DropFileWidget,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addDocument: function({stored_object, stored_object_version}: {stored_object: StoredObject, stored_object_version: StoredObjectVersion}): void {
|
addDocument: function ({
|
||||||
console.log('object added', stored_object);
|
stored_object,
|
||||||
console.log('version added', stored_object_version);
|
stored_object_version,
|
||||||
|
}: {
|
||||||
|
stored_object: StoredObject;
|
||||||
|
stored_object_version: StoredObjectVersion;
|
||||||
|
}): void {
|
||||||
|
console.log("object added", stored_object);
|
||||||
|
console.log("version added", stored_object_version);
|
||||||
this.$data.existingDoc = stored_object;
|
this.$data.existingDoc = stored_object;
|
||||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||||
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
|
input_stored_object.value = JSON.stringify(
|
||||||
|
this.$data.existingDoc,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
removeDocument: function(object: StoredObject): void {
|
removeDocument: function (object: StoredObject): void {
|
||||||
console.log('catch remove document', object);
|
console.log("catch remove document", object);
|
||||||
input_stored_object.value = "";
|
input_stored_object.value = "";
|
||||||
this.$data.existingDoc = undefined;
|
this.$data.existingDoc = undefined;
|
||||||
console.log('collectionEntry', collectionEntry);
|
console.log("collectionEntry", collectionEntry);
|
||||||
|
|
||||||
if (null !== collectionEntry) {
|
if (null !== collectionEntry) {
|
||||||
console.log('will remove collection');
|
console.log("will remove collection");
|
||||||
collectionEntry.remove();
|
collectionEntry.remove();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).mount(app_container);
|
app.use(i18n).mount(app_container);
|
||||||
}
|
};
|
||||||
window.addEventListener('collection-add-entry', ((e: CustomEvent<CollectionEventPayload>) => {
|
window.addEventListener("collection-add-entry", ((
|
||||||
|
e: CustomEvent<CollectionEventPayload>,
|
||||||
|
) => {
|
||||||
const detail = e.detail;
|
const detail = e.detail;
|
||||||
const divElement: null|HTMLDivElement = detail.entry.querySelector('div[data-stored-object]');
|
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
||||||
|
"div[data-stored-object]",
|
||||||
|
);
|
||||||
|
|
||||||
if (null === divElement) {
|
if (null === divElement) {
|
||||||
throw new Error('div[data-stored-object] not found');
|
throw new Error("div[data-stored-object] not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
startApp(divElement, detail.entry);
|
startApp(divElement, detail.entry);
|
||||||
}) as EventListener);
|
}) as EventListener);
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll('div[data-stored-object]');
|
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
||||||
|
"div[data-stored-object]",
|
||||||
|
);
|
||||||
|
|
||||||
upload_inputs.forEach((input: HTMLDivElement): void => {
|
upload_inputs.forEach((input: HTMLDivElement): void => {
|
||||||
// test for a parent to check if this is a collection entry
|
// test for a parent to check if this is a collection entry
|
||||||
let collectionEntry: null|HTMLLIElement = null;
|
let collectionEntry: null | HTMLLIElement = null;
|
||||||
let parent = input.parentElement;
|
const parent = input.parentElement;
|
||||||
console.log('parent', parent);
|
console.log("parent", parent);
|
||||||
if (null !== parent) {
|
if (null !== parent) {
|
||||||
let grandParent = parent.parentElement;
|
const grandParent = parent.parentElement;
|
||||||
console.log('grandParent', grandParent);
|
console.log("grandParent", grandParent);
|
||||||
if (null !== grandParent) {
|
if (null !== grandParent) {
|
||||||
if (grandParent.tagName.toLowerCase() === 'li' && grandParent.classList.contains('entry')) {
|
if (
|
||||||
|
grandParent.tagName.toLowerCase() === "li" &&
|
||||||
|
grandParent.classList.contains("entry")
|
||||||
|
) {
|
||||||
collectionEntry = grandParent as HTMLLIElement;
|
collectionEntry = grandParent as HTMLLIElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startApp(input, collectionEntry);
|
startApp(input, collectionEntry);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export {}
|
export {};
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
var algo = 'AES-CBC';
|
var algo = "AES-CBC";
|
||||||
import Dropzone from 'dropzone';
|
import Dropzone from "dropzone";
|
||||||
import { initializeButtons } from './downloader.js';
|
import { initializeButtons } from "./downloader.js";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -23,8 +22,8 @@ import { initializeButtons } from './downloader.js';
|
|||||||
|
|
||||||
// load css
|
// load css
|
||||||
//require('dropzone/dist/basic.css');
|
//require('dropzone/dist/basic.css');
|
||||||
require('dropzone/dist/dropzone.css');
|
require("dropzone/dist/dropzone.css");
|
||||||
require('./index.scss');
|
require("./index.scss");
|
||||||
//
|
//
|
||||||
|
|
||||||
// disable dropzone autodiscover
|
// disable dropzone autodiscover
|
||||||
@@ -32,22 +31,21 @@ Dropzone.autoDiscover = false;
|
|||||||
|
|
||||||
var keyDefinition = {
|
var keyDefinition = {
|
||||||
name: algo,
|
name: algo,
|
||||||
length: 256
|
length: 256,
|
||||||
};
|
};
|
||||||
|
|
||||||
var searchForZones = function(root) {
|
var searchForZones = function (root) {
|
||||||
var zones = root.querySelectorAll('div[data-stored-object]');
|
var zones = root.querySelectorAll("div[data-stored-object]");
|
||||||
for(let i=0; i < zones.length; i++) {
|
for (let i = 0; i < zones.length; i++) {
|
||||||
initialize(zones[i]);
|
initialize(zones[i]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var getUploadUrl = function(zoneData, files) {
|
var getUploadUrl = function (zoneData, files) {
|
||||||
var
|
var generateTempUrlPost = zoneData.zone.querySelector(
|
||||||
generateTempUrlPost = zoneData.zone.querySelector('input[data-async-file-upload]').dataset.generateTempUrlPost,
|
"input[data-async-file-upload]",
|
||||||
oReq = new XMLHttpRequest()
|
).dataset.generateTempUrlPost,
|
||||||
;
|
oReq = new XMLHttpRequest();
|
||||||
|
|
||||||
// arg, dropzone, you cannot handle async upload...
|
// arg, dropzone, you cannot handle async upload...
|
||||||
oReq.open("GET", generateTempUrlPost, false);
|
oReq.open("GET", generateTempUrlPost, false);
|
||||||
oReq.send();
|
oReq.send();
|
||||||
@@ -61,36 +59,41 @@ var getUploadUrl = function(zoneData, files) {
|
|||||||
return zoneData.params.url;
|
return zoneData.params.url;
|
||||||
};
|
};
|
||||||
|
|
||||||
var encryptFile = function(originalFile, zoneData, done) {
|
var encryptFile = function (originalFile, zoneData, done) {
|
||||||
var
|
var iv = crypto.getRandomValues(new Uint8Array(16)),
|
||||||
iv = crypto.getRandomValues(new Uint8Array(16)),
|
|
||||||
reader = new FileReader(),
|
reader = new FileReader(),
|
||||||
jsKey, rawKey
|
jsKey,
|
||||||
;
|
rawKey;
|
||||||
|
|
||||||
zoneData.originalType = originalFile.type;
|
zoneData.originalType = originalFile.type;
|
||||||
|
|
||||||
reader.onload = e => {
|
reader.onload = (e) => {
|
||||||
window.crypto.subtle.generateKey(keyDefinition, true, [ "encrypt", "decrypt" ])
|
window.crypto.subtle
|
||||||
.then(key => {
|
.generateKey(keyDefinition, true, ["encrypt", "decrypt"])
|
||||||
|
.then((key) => {
|
||||||
jsKey = key;
|
jsKey = key;
|
||||||
|
|
||||||
// we register the key somwhere
|
// we register the key somwhere
|
||||||
return window.crypto.subtle.exportKey('jwk', key);
|
return window.crypto.subtle.exportKey("jwk", key);
|
||||||
}).then(exportedKey => {
|
})
|
||||||
|
.then((exportedKey) => {
|
||||||
rawKey = exportedKey;
|
rawKey = exportedKey;
|
||||||
|
|
||||||
// we start encryption
|
// we start encryption
|
||||||
return window.crypto.subtle.encrypt({ name: algo, iv: iv}, jsKey, e.target.result);
|
return window.crypto.subtle.encrypt(
|
||||||
|
{ name: algo, iv: iv },
|
||||||
|
jsKey,
|
||||||
|
e.target.result,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.then(encrypted => {
|
.then((encrypted) => {
|
||||||
zoneData.crypto = {
|
zoneData.crypto = {
|
||||||
jsKey: jsKey,
|
jsKey: jsKey,
|
||||||
rawKey: rawKey,
|
rawKey: rawKey,
|
||||||
iv: iv
|
iv: iv,
|
||||||
};
|
};
|
||||||
|
|
||||||
done(new File( [ encrypted ], zoneData.suffix));
|
done(new File([encrypted], zoneData.suffix));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,12 +101,11 @@ var encryptFile = function(originalFile, zoneData, done) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var addBelowButton = (btn, zone, zoneData) => {
|
var addBelowButton = (btn, zone, zoneData) => {
|
||||||
let
|
let belowZone = zone.querySelector(".chill-dropzone__below-zone");
|
||||||
belowZone = zone.querySelector('.chill-dropzone__below-zone');
|
|
||||||
|
|
||||||
if (belowZone === null) {
|
if (belowZone === null) {
|
||||||
belowZone = document.createElement('div');
|
belowZone = document.createElement("div");
|
||||||
belowZone.classList.add('chill-dropzone__below-zone');
|
belowZone.classList.add("chill-dropzone__below-zone");
|
||||||
zone.appendChild(belowZone);
|
zone.appendChild(belowZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,18 +113,17 @@ var addBelowButton = (btn, zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var createZone = (zone, zoneData) => {
|
var createZone = (zone, zoneData) => {
|
||||||
var
|
var created = document.createElement("div"),
|
||||||
created = document.createElement('div'),
|
initMessage = document.createElement("div"),
|
||||||
initMessage = document.createElement('div'),
|
|
||||||
initContent = zone.dataset.labelInitMessage,
|
initContent = zone.dataset.labelInitMessage,
|
||||||
dropzoneI;
|
dropzoneI;
|
||||||
|
|
||||||
created.classList.add('dropzone');
|
created.classList.add("dropzone");
|
||||||
initMessage.classList.add('dz-message');
|
initMessage.classList.add("dz-message");
|
||||||
initMessage.appendChild(document.createTextNode(initContent));
|
initMessage.appendChild(document.createTextNode(initContent));
|
||||||
console.log(Dropzone);
|
console.log(Dropzone);
|
||||||
dropzoneI = new Dropzone(created, {
|
dropzoneI = new Dropzone(created, {
|
||||||
url: function(files) {
|
url: function (files) {
|
||||||
return getUploadUrl(zoneData, files);
|
return getUploadUrl(zoneData, files);
|
||||||
},
|
},
|
||||||
dictDefaultMessage: zone.dataset.dictDefaultMessage,
|
dictDefaultMessage: zone.dataset.dictDefaultMessage,
|
||||||
@@ -134,15 +135,15 @@ var createZone = (zone, zoneData) => {
|
|||||||
dictUploadCanceled: zone.dataset.dictUploadCanceled,
|
dictUploadCanceled: zone.dataset.dictUploadCanceled,
|
||||||
maxFiles: 1,
|
maxFiles: 1,
|
||||||
addRemoveLinks: true,
|
addRemoveLinks: true,
|
||||||
transformFile: function(file, done) {
|
transformFile: function (file, done) {
|
||||||
encryptFile(file, zoneData, done);
|
encryptFile(file, zoneData, done);
|
||||||
},
|
},
|
||||||
renameFile: function(file) {
|
renameFile: function (file) {
|
||||||
return zoneData.suffix;
|
return zoneData.suffix;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("sending", function(file, xhr, formData) {
|
dropzoneI.on("sending", function (file, xhr, formData) {
|
||||||
formData.append("redirect", zoneData.params.redirect);
|
formData.append("redirect", zoneData.params.redirect);
|
||||||
formData.append("max_file_size", zoneData.params.max_file_size);
|
formData.append("max_file_size", zoneData.params.max_file_size);
|
||||||
formData.append("max_file_count", zoneData.params.max_file_count);
|
formData.append("max_file_count", zoneData.params.max_file_count);
|
||||||
@@ -150,18 +151,18 @@ var createZone = (zone, zoneData) => {
|
|||||||
formData.append("signature", zoneData.params.signature);
|
formData.append("signature", zoneData.params.signature);
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("success", function(file, response) {
|
dropzoneI.on("success", function (file, response) {
|
||||||
zoneData.currentFile = file;
|
zoneData.currentFile = file;
|
||||||
storeDataInForm(zone, zoneData);
|
storeDataInForm(zone, zoneData);
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("addedfile", function(file) {
|
dropzoneI.on("addedfile", function (file) {
|
||||||
if (zoneData.hasOwnProperty('currentFile')) {
|
if (zoneData.hasOwnProperty("currentFile")) {
|
||||||
dropzoneI.removeFile(zoneData.currentFile);
|
dropzoneI.removeFile(zoneData.currentFile);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("removedfile", function(file) {
|
dropzoneI.on("removedfile", function (file) {
|
||||||
removeDataInForm(zone, zoneData);
|
removeDataInForm(zone, zoneData);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -170,19 +171,20 @@ var createZone = (zone, zoneData) => {
|
|||||||
let event = new CustomEvent("chill_dropzone_initialized", {
|
let event = new CustomEvent("chill_dropzone_initialized", {
|
||||||
detail: {
|
detail: {
|
||||||
dropzone: dropzoneI,
|
dropzone: dropzoneI,
|
||||||
zoneData: zoneData
|
zoneData: zoneData,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var initialize = function (zone) {
|
||||||
var initialize = function(zone) {
|
var allowRemove = zone.dataset.allowRemove,
|
||||||
var
|
zoneData = {
|
||||||
allowRemove = zone.dataset.allowRemove,
|
zone: zone,
|
||||||
zoneData = { zone: zone, suffix: createFilename(), allowRemove: allowRemove, old: null }
|
suffix: createFilename(),
|
||||||
;
|
allowRemove: allowRemove,
|
||||||
|
old: null,
|
||||||
|
};
|
||||||
if (hasDataInForm(zone, zoneData)) {
|
if (hasDataInForm(zone, zoneData)) {
|
||||||
insertRemoveButton(zone, zoneData);
|
insertRemoveButton(zone, zoneData);
|
||||||
insertDownloadButton(zone, zoneData);
|
insertDownloadButton(zone, zoneData);
|
||||||
@@ -193,7 +195,8 @@ var initialize = function(zone) {
|
|||||||
|
|
||||||
var createFilename = () => {
|
var createFilename = () => {
|
||||||
var text = "";
|
var text = "";
|
||||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
var possible =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
@@ -203,13 +206,10 @@ var createFilename = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var storeDataInForm = (zone, zoneData) => {
|
var storeDataInForm = (zone, zoneData) => {
|
||||||
var
|
var inputKey = zone.querySelector("input[data-stored-object-key]"),
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputIv = zone.querySelector("input[data-stored-object-iv]"),
|
||||||
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
inputObject = zone.querySelector("input[data-async-file-upload]"),
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputType = zone.querySelector("input[data-async-file-type]");
|
||||||
inputType = zone.querySelector('input[data-async-file-type]')
|
|
||||||
;
|
|
||||||
|
|
||||||
inputKey.value = JSON.stringify(zoneData.crypto.rawKey);
|
inputKey.value = JSON.stringify(zoneData.crypto.rawKey);
|
||||||
inputIv.value = JSON.stringify(Array.from(zoneData.crypto.iv));
|
inputIv.value = JSON.stringify(Array.from(zoneData.crypto.iv));
|
||||||
inputType.value = zoneData.originalType;
|
inputType.value = zoneData.originalType;
|
||||||
@@ -219,15 +219,12 @@ var storeDataInForm = (zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const restoreDataInForm = (zone, zoneData) => {
|
const restoreDataInForm = (zone, zoneData) => {
|
||||||
var
|
var inputKey = zone.querySelector("input[data-stored-object-key]"),
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputIv = zone.querySelector("input[data-stored-object-iv]"),
|
||||||
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
inputObject = zone.querySelector("input[data-async-file-upload]"),
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputType = zone.querySelector("input[data-async-file-type]");
|
||||||
inputType = zone.querySelector('input[data-async-file-type]')
|
|
||||||
;
|
|
||||||
|
|
||||||
if (zoneData.old === null) {
|
if (zoneData.old === null) {
|
||||||
console.log('should not have restored data');
|
console.log("should not have restored data");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,27 +237,21 @@ const restoreDataInForm = (zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hasDataInForm = (zone, zoneData) => {
|
const hasDataInForm = (zone, zoneData) => {
|
||||||
var
|
var inputObject = zone.querySelector("input[data-async-file-upload]");
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]')
|
|
||||||
;
|
|
||||||
|
|
||||||
return inputObject.value.length > 0;
|
return inputObject.value.length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
var removeDataInForm = (zone, zoneData) => {
|
var removeDataInForm = (zone, zoneData) => {
|
||||||
var
|
var inputKey = zone.querySelector("input[data-stored-object-key]"),
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputIv = zone.querySelector("input[data-stored-object-iv]"),
|
||||||
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
inputObject = zone.querySelector("input[data-async-file-upload]"),
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputType = zone.querySelector("input[data-async-file-type]");
|
||||||
inputType = zone.querySelector('input[data-async-file-type]')
|
|
||||||
;
|
|
||||||
|
|
||||||
// store data for future usage
|
// store data for future usage
|
||||||
zoneData.old = {
|
zoneData.old = {
|
||||||
key: inputKey.value,
|
key: inputKey.value,
|
||||||
iv: inputIv.value,
|
iv: inputIv.value,
|
||||||
obj: inputObject.value,
|
obj: inputObject.value,
|
||||||
type: inputType.value
|
type: inputType.value,
|
||||||
};
|
};
|
||||||
// set blank values
|
// set blank values
|
||||||
inputKey.value = "";
|
inputKey.value = "";
|
||||||
@@ -272,30 +263,27 @@ var removeDataInForm = (zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var insertRemoveButton = (zone, zoneData) => {
|
var insertRemoveButton = (zone, zoneData) => {
|
||||||
var
|
var removeButton = document.createElement("a"),
|
||||||
removeButton = document.createElement('a'),
|
cancelButton = document.createElement("a"),
|
||||||
cancelButton = document.createElement('a'),
|
|
||||||
labelRemove = zone.dataset.dictRemove,
|
labelRemove = zone.dataset.dictRemove,
|
||||||
labelCancel = 'Restaurer'
|
labelCancel = "Restaurer";
|
||||||
;
|
removeButton.classList.add("btn", "btn-delete");
|
||||||
|
|
||||||
removeButton.classList.add('btn', 'btn-delete');
|
|
||||||
removeButton.textContent = labelRemove;
|
removeButton.textContent = labelRemove;
|
||||||
|
|
||||||
cancelButton.classList.add('btn', 'btn-cancel');
|
cancelButton.classList.add("btn", "btn-cancel");
|
||||||
cancelButton.textContent = labelCancel;
|
cancelButton.textContent = labelCancel;
|
||||||
|
|
||||||
removeButton.addEventListener('click', (e) => {
|
removeButton.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (zoneData.allowRemove === 'true') {
|
if (zoneData.allowRemove === "true") {
|
||||||
removeDataInForm(zone, zoneData);
|
removeDataInForm(zone, zoneData);
|
||||||
cancelButton.addEventListener('click', (e) => {
|
cancelButton.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
restoreDataInForm(zone, zoneData);
|
restoreDataInForm(zone, zoneData);
|
||||||
|
|
||||||
cancelButton.remove();
|
cancelButton.remove();
|
||||||
zone.querySelector('.dropzone').remove();
|
zone.querySelector(".dropzone").remove();
|
||||||
|
|
||||||
initialize(zone);
|
initialize(zone);
|
||||||
});
|
});
|
||||||
@@ -311,41 +299,35 @@ var insertRemoveButton = (zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeDownloadButton = (zone, zoneData) => {
|
const removeDownloadButton = (zone, zoneData) => {
|
||||||
var
|
var existingButtons = zone.querySelectorAll("a[data-download-button]");
|
||||||
existingButtons = zone.querySelectorAll('a[data-download-button]')
|
|
||||||
;
|
|
||||||
|
|
||||||
// remove existing
|
// remove existing
|
||||||
existingButtons.forEach(function(b) {
|
existingButtons.forEach(function (b) {
|
||||||
b.remove();
|
b.remove();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var insertDownloadButton = (zone, zoneData) => {
|
var insertDownloadButton = (zone, zoneData) => {
|
||||||
var
|
var existingButtons = zone.querySelectorAll("a[data-download-button]"),
|
||||||
existingButtons = zone.querySelectorAll('a[data-download-button]'),
|
newButton = document.createElement("a"),
|
||||||
newButton = document.createElement('a'),
|
inputKey = zone.querySelector("input[data-stored-object-key]"),
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputIv = zone.querySelector("input[data-stored-object-iv]"),
|
||||||
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
inputObject = zone.querySelector("input[data-async-file-upload]"),
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputType = zone.querySelector("input[data-async-file-type]"),
|
||||||
inputType = zone.querySelector('input[data-async-file-type]'),
|
|
||||||
labelPreparing = zone.dataset.labelPreparing,
|
labelPreparing = zone.dataset.labelPreparing,
|
||||||
labelQuietButton = zone.dataset.labelQuietButton,
|
labelQuietButton = zone.dataset.labelQuietButton,
|
||||||
labelReady = zone.dataset.labelReady,
|
labelReady = zone.dataset.labelReady,
|
||||||
tempUrlGenerator = zone.dataset.tempUrlGenerator,
|
tempUrlGenerator = zone.dataset.tempUrlGenerator,
|
||||||
tempUrlGeneratorParams = new URLSearchParams()
|
tempUrlGeneratorParams = new URLSearchParams();
|
||||||
;
|
|
||||||
|
|
||||||
// remove existing
|
// remove existing
|
||||||
existingButtons.forEach(function(b) {
|
existingButtons.forEach(function (b) {
|
||||||
b.remove();
|
b.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (inputObject.value === '') {
|
if (inputObject.value === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tempUrlGeneratorParams.append('object_name', inputObject.value);
|
tempUrlGeneratorParams.append("object_name", inputObject.value);
|
||||||
|
|
||||||
newButton.dataset.downloadButton = true;
|
newButton.dataset.downloadButton = true;
|
||||||
newButton.dataset.key = inputKey.value;
|
newButton.dataset.key = inputKey.value;
|
||||||
@@ -353,8 +335,9 @@ var insertDownloadButton = (zone, zoneData) => {
|
|||||||
newButton.dataset.mimeType = inputType.value;
|
newButton.dataset.mimeType = inputType.value;
|
||||||
newButton.dataset.labelPreparing = labelPreparing;
|
newButton.dataset.labelPreparing = labelPreparing;
|
||||||
newButton.dataset.labelReady = labelReady;
|
newButton.dataset.labelReady = labelReady;
|
||||||
newButton.dataset.tempUrlGetGenerator = tempUrlGenerator + '?' + tempUrlGeneratorParams.toString();
|
newButton.dataset.tempUrlGetGenerator =
|
||||||
newButton.classList.add('btn', 'btn-download', 'dz-bt-below-dropzone');
|
tempUrlGenerator + "?" + tempUrlGeneratorParams.toString();
|
||||||
|
newButton.classList.add("btn", "btn-download", "dz-bt-below-dropzone");
|
||||||
newButton.textContent = labelQuietButton;
|
newButton.textContent = labelQuietButton;
|
||||||
|
|
||||||
addBelowButton(newButton, zone, zoneData);
|
addBelowButton(newButton, zone, zoneData);
|
||||||
@@ -362,11 +345,11 @@ var insertDownloadButton = (zone, zoneData) => {
|
|||||||
initializeButtons(zone);
|
initializeButtons(zone);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('load', function(e) {
|
window.addEventListener("load", function (e) {
|
||||||
searchForZones(document);
|
searchForZones(document);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('collection-add-entry', function(e) {
|
window.addEventListener("collection-add-entry", function (e) {
|
||||||
searchForZones(e.detail.entry);
|
searchForZones(e.detail.entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,25 +1,32 @@
|
|||||||
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||||
import {createApp} from "vue";
|
import { createApp } from "vue";
|
||||||
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
||||||
import {StoredObject, StoredObjectStatusChange} from "../../types";
|
import { StoredObject, StoredObjectStatusChange } from "../../types";
|
||||||
import {defineComponent} from "vue";
|
import { defineComponent } from "vue";
|
||||||
import DownloadButton from "../../vuejs/StoredObjectButton/DownloadButton.vue";
|
import DownloadButton from "../../vuejs/StoredObjectButton/DownloadButton.vue";
|
||||||
import ToastPlugin from "vue-toast-notification";
|
import ToastPlugin from "vue-toast-notification";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', function (e) {
|
window.addEventListener("DOMContentLoaded", function (e) {
|
||||||
document.querySelectorAll<HTMLDivElement>('div[data-download-button-single]').forEach((el) => {
|
document
|
||||||
const storedObject = JSON.parse(el.dataset.storedObject as string) as StoredObject;
|
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
||||||
|
.forEach((el) => {
|
||||||
|
const storedObject = JSON.parse(
|
||||||
|
el.dataset.storedObject as string,
|
||||||
|
) as StoredObject;
|
||||||
const title = el.dataset.title as string;
|
const title = el.dataset.title as string;
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: {DownloadButton},
|
components: { DownloadButton },
|
||||||
data() {
|
data() {
|
||||||
return {storedObject, title, classes: {btn: true, "btn-outline-primary": true}};
|
return {
|
||||||
|
storedObject,
|
||||||
|
title,
|
||||||
|
classes: { btn: true, "btn-outline-primary": true },
|
||||||
|
};
|
||||||
},
|
},
|
||||||
template: '<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
template:
|
||||||
|
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).use(ToastPlugin).mount(el);
|
app.use(i18n).use(ToastPlugin).mount(el);
|
||||||
|
@@ -1,54 +1,73 @@
|
|||||||
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||||
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
||||||
import {createApp} from "vue";
|
import { createApp } from "vue";
|
||||||
import {StoredObject, StoredObjectStatusChange} from "../../types";
|
import { StoredObject, StoredObjectStatusChange } from "../../types";
|
||||||
import {is_object_ready} from "../../vuejs/StoredObjectButton/helpers";
|
import { is_object_ready } from "../../vuejs/StoredObjectButton/helpers";
|
||||||
import ToastPlugin from "vue-toast-notification";
|
import ToastPlugin from "vue-toast-notification";
|
||||||
|
|
||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', function (e) {
|
window.addEventListener("DOMContentLoaded", function (e) {
|
||||||
document.querySelectorAll<HTMLDivElement>('div[data-download-buttons]').forEach((el) => {
|
document
|
||||||
|
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
||||||
|
.forEach((el) => {
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: {DocumentActionButtonsGroup},
|
components: { DocumentActionButtonsGroup },
|
||||||
data() {
|
data() {
|
||||||
|
|
||||||
const datasets = el.dataset as {
|
const datasets = el.dataset as {
|
||||||
filename: string,
|
filename: string;
|
||||||
canEdit: string,
|
canEdit: string;
|
||||||
storedObject: string,
|
storedObject: string;
|
||||||
buttonSmall: string,
|
buttonSmall: string;
|
||||||
davLink: string,
|
davLink: string;
|
||||||
davLinkExpiration: string,
|
davLinkExpiration: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const
|
const storedObject = JSON.parse(
|
||||||
storedObject = JSON.parse(datasets.storedObject) as StoredObject,
|
datasets.storedObject,
|
||||||
|
) as StoredObject,
|
||||||
filename = datasets.filename,
|
filename = datasets.filename,
|
||||||
canEdit = datasets.canEdit === '1',
|
canEdit = datasets.canEdit === "1",
|
||||||
small = datasets.buttonSmall === '1',
|
small = datasets.buttonSmall === "1",
|
||||||
davLink = 'davLink' in datasets && datasets.davLink !== '' ? datasets.davLink : null,
|
davLink =
|
||||||
davLinkExpiration = 'davLinkExpiration' in datasets ? Number.parseInt(datasets.davLinkExpiration) : null
|
"davLink" in datasets && datasets.davLink !== ""
|
||||||
;
|
? datasets.davLink
|
||||||
|
: null,
|
||||||
return { storedObject, filename, canEdit, small, davLink, davLinkExpiration };
|
davLinkExpiration =
|
||||||
|
"davLinkExpiration" in datasets
|
||||||
|
? Number.parseInt(datasets.davLinkExpiration)
|
||||||
|
: null;
|
||||||
|
return {
|
||||||
|
storedObject,
|
||||||
|
filename,
|
||||||
|
canEdit,
|
||||||
|
small,
|
||||||
|
davLink,
|
||||||
|
davLinkExpiration,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
template: '<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
template:
|
||||||
|
'<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
||||||
methods: {
|
methods: {
|
||||||
onStoredObjectStatusChange: function(newStatus: StoredObjectStatusChange): void {
|
onStoredObjectStatusChange: function (
|
||||||
|
newStatus: StoredObjectStatusChange,
|
||||||
|
): void {
|
||||||
this.$data.storedObject.status = newStatus.status;
|
this.$data.storedObject.status = newStatus.status;
|
||||||
this.$data.storedObject.filename = newStatus.filename;
|
this.$data.storedObject.filename = newStatus.filename;
|
||||||
this.$data.storedObject.type = newStatus.type;
|
this.$data.storedObject.type = newStatus.type;
|
||||||
|
|
||||||
// remove eventual div which inform pending status
|
// remove eventual div which inform pending status
|
||||||
document.querySelectorAll(`[data-docgen-is-pending="${this.$data.storedObject.id}"]`)
|
document
|
||||||
.forEach(function(el) {
|
.querySelectorAll(
|
||||||
|
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
||||||
|
)
|
||||||
|
.forEach(function (el) {
|
||||||
el.remove();
|
el.remove();
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).use(ToastPlugin).mount(el);
|
app.use(i18n).use(ToastPlugin).mount(el);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,7 +2,7 @@ import {
|
|||||||
DateTime,
|
DateTime,
|
||||||
User,
|
User,
|
||||||
} from "../../../ChillMainBundle/Resources/public/types";
|
} from "../../../ChillMainBundle/Resources/public/types";
|
||||||
import {SignedUrlGet} from "./vuejs/StoredObjectButton/helpers";
|
import { SignedUrlGet } from "./vuejs/StoredObjectButton/helpers";
|
||||||
|
|
||||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
||||||
|
|
||||||
@@ -64,23 +64,22 @@ export interface StoredObjectStatusChange {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersionWithPointInTime extends StoredObjectVersionPersisted {
|
export interface StoredObjectVersionWithPointInTime
|
||||||
|
extends StoredObjectVersionPersisted {
|
||||||
"point-in-times": StoredObjectPointInTime[];
|
"point-in-times": StoredObjectPointInTime[];
|
||||||
"from-restored": StoredObjectVersionPersisted|null;
|
"from-restored": StoredObjectVersionPersisted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectPointInTime {
|
export interface StoredObjectPointInTime {
|
||||||
id: number;
|
id: number;
|
||||||
byUser: User | null;
|
byUser: User | null;
|
||||||
reason: 'keep-before-conversion'|'keep-by-user';
|
reason: "keep-before-conversion" | "keep-by-user";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function executed by the WopiEditButton component.
|
* Function executed by the WopiEditButton component.
|
||||||
*/
|
*/
|
||||||
export type WopiEditButtonExecutableBeforeLeaveFunction = {
|
export type WopiEditButtonExecutableBeforeLeaveFunction = () => Promise<void>;
|
||||||
(): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object containing information for performering a POST request to a swift object store
|
* Object containing information for performering a POST request to a swift object store
|
||||||
@@ -135,7 +134,7 @@ export interface ZoomLevel {
|
|||||||
id: number;
|
id: number;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
label: {
|
label: {
|
||||||
fr?: string,
|
fr?: string;
|
||||||
nl?: string
|
nl?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user