mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-07 13:59:43 +00:00
Compare commits
115 Commits
ticket/rea
...
405-aside-
Author | SHA1 | Date | |
---|---|---|---|
ac6336d197 | |||
a46b301e44 | |||
05f0443011 | |||
7f8d8f891e | |||
ddb932a4fa | |||
3a02f15bcd | |||
bc2fbee5c6 | |||
ebd10ca522 | |||
d3a31be412
|
|||
d159a82f88
|
|||
c2d9c73fd4
|
|||
0d6d15fcf7 | |||
f9ad96c78b
|
|||
fcc9529a20
|
|||
955cb817c4
|
|||
823f9546b9 | |||
be39fa16e7 | |||
c8bb7575e7 | |||
|
80a3734171 | ||
ab98f3a102
|
|||
7516e68d77 | |||
7b60b7a8af | |||
d984dec7db
|
|||
46a4dedab8 | |||
db98519e65 | |||
c39637180a
|
|||
15f9409bc8 | |||
5b90d23367 | |||
c48625d1cd | |||
1195b54a68 | |||
2a280b814f | |||
230c758255 | |||
eafda987ae | |||
7db8a371fc | |||
0d0649dd31 | |||
ac12b8cdcf | |||
9c1611d052 | |||
90e3043c3d | |||
af13bf9088 | |||
4aa65d69c7 | |||
9e33aec594 | |||
f88bc7e9f0 | |||
8e78c41549
|
|||
6e36771349
|
|||
7a82cae155
|
|||
dfab223391 | |||
539752485c | |||
d204df0316 | |||
|
82c02f442b | ||
f32a9dc7bc | |||
ea06a96f91 | |||
76433e2512 | |||
1fa464b87a | |||
3b75f43e80 | |||
a40eb95c43 | |||
8429c6e693 | |||
6db7f6827c | |||
3c60c57985 | |||
10aa36aae0 | |||
eed9913a49 | |||
1a847d36a0 | |||
d916962d9b | |||
1092fc64ae | |||
4e99b6ecbd | |||
60d107b541 | |||
4c3befe489 | |||
e176319775 | |||
5d810b4230 | |||
52b8eea069 | |||
4bebeaeaaa | |||
3969e12633 | |||
d60312d4a2 | |||
d2454ae134 | |||
17c2cb1fdc | |||
94d7a2a0bb | |||
aef1efc6cd | |||
dd0c662c9e | |||
6b1696b62e | |||
c4b760c452 | |||
69fe2a8256 | |||
8c98242896 | |||
7eecfd3882 | |||
6713658569 | |||
342b786106 | |||
80a7437769 | |||
8a38ce1a5c | |||
5d94bf0556 | |||
bb71e084b8 | |||
27f0bf28e9 | |||
383f588795 | |||
e7a1ff1ac8 | |||
adc9c47d0a | |||
e594b65d1e | |||
c0826bc65c | |||
904f4e5ed9 | |||
481f82b4c7 | |||
f5668592ca | |||
aa085a1562 | |||
2754251fdc | |||
2f6cef4238 | |||
2309636eae | |||
56ec8fb516 | |||
fe6e6e54c1 | |||
2a09594b4a | |||
7c798e1f63 | |||
ab8da4ab7a | |||
5bdb2df929 | |||
e3a6b60fa2 | |||
5f01673404 | |||
63d0a52ea1 | |||
837089ff5d | |||
f383fab578 | |||
f3cc4a89af | |||
703f5dc32d | |||
b870e71f77 |
@@ -1,6 +0,0 @@
|
|||||||
kind: Feature
|
|
||||||
body: |
|
|
||||||
Upgrade import of address list to the last version of compiled addresses of belgian-best-address
|
|
||||||
time: 2024-05-30T16:00:03.440767606+02:00
|
|
||||||
custom:
|
|
||||||
Issue: ""
|
|
@@ -1,6 +0,0 @@
|
|||||||
kind: Feature
|
|
||||||
body: |
|
|
||||||
Upgrade CKEditor and refactor configuration with use of typescript
|
|
||||||
time: 2024-05-31T19:02:42.776662753+02:00
|
|
||||||
custom:
|
|
||||||
Issue: ""
|
|
6
.changes/unreleased/Fixed-20251003-224044.yaml
Normal file
6
.changes/unreleased/Fixed-20251003-224044.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Fixed
|
||||||
|
body: Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted
|
||||||
|
time: 2025-10-03T22:40:44.685474863+02:00
|
||||||
|
custom:
|
||||||
|
Issue: ""
|
||||||
|
SchemaChange: No schema change
|
6
.changes/unreleased/Fixed-20251006-121315.yaml
Normal file
6
.changes/unreleased/Fixed-20251006-121315.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Fixed
|
||||||
|
body: 'Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists'
|
||||||
|
time: 2025-10-06T12:13:15.45905994+02:00
|
||||||
|
custom:
|
||||||
|
Issue: "434"
|
||||||
|
SchemaChange: No schema change
|
4
.changes/v4.0.2.md
Normal file
4
.changes/v4.0.2.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## v4.0.2 - 2025-07-09
|
||||||
|
### Fixed
|
||||||
|
* Fix add missing translation
|
||||||
|
* Fix the transfer of evaluations and documents during of accompanyingperiodwork
|
12
.changes/v4.1.0.md
Normal file
12
.changes/v4.1.0.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
## v4.1.0 - 2025-08-26
|
||||||
|
### Feature
|
||||||
|
* ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes
|
||||||
|
* ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default
|
||||||
|
* Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports
|
||||||
|
|
||||||
|
**Schema Change**: Add columns or tables
|
||||||
|
### Fixed
|
||||||
|
* ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed.
|
||||||
|
* ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType
|
||||||
|
### UX
|
||||||
|
* Limit display of participations in event list
|
10
.changes/v4.2.0.md
Normal file
10
.changes/v4.2.0.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## v4.2.0 - 2025-09-02
|
||||||
|
### Feature
|
||||||
|
* ([#64](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/64)) Add external identifier for a Person
|
||||||
|
|
||||||
|
**Schema Change**: Add columns or tables
|
||||||
|
* ([#330](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/330) Allow users to choose for which notifications they want to receive an email
|
||||||
|
### Fixed
|
||||||
|
* ([#422](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/422)) Fixed html layout of pages for recovering password
|
||||||
|
* Fix typo in 'uncheckAll' script for centers selection
|
||||||
|
* Fix incorrect parameter name in event details link
|
6
.changes/v4.2.1.md
Normal file
6
.changes/v4.2.1.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## v4.2.1 - 2025-09-03
|
||||||
|
### Fixed
|
||||||
|
* Fix exports to work with DirectExportInterface
|
||||||
|
### DX
|
||||||
|
* Improve error message when a stored object cannot be written on local disk
|
||||||
|
|
10
.changes/v4.3.0.md
Normal file
10
.changes/v4.3.0.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## v4.3.0 - 2025-09-08
|
||||||
|
### Feature
|
||||||
|
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
|
||||||
|
* Add a command to generate a list of permissions
|
||||||
|
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date
|
||||||
|
|
||||||
|
**Schema Change**: Add columns or tables
|
||||||
|
### Fixed
|
||||||
|
* fix date formatting in calendar range display
|
||||||
|
* Change route URL to avoid clash with person duplicate controller method
|
8
.changes/v4.4.0.md
Normal file
8
.changes/v4.4.0.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
## v4.4.0 - 2025-09-11
|
||||||
|
### Feature
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
||||||
|
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works
|
||||||
|
### Fixed
|
||||||
|
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates
|
||||||
|
* Fix saving notification preferences in user's profile
|
3
.changes/v4.4.1.md
Normal file
3
.changes/v4.4.1.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## v4.4.1 - 2025-09-11
|
||||||
|
### Fixed
|
||||||
|
* fix translations in duplicate evaluation document modal and realign close modal button
|
3
.changes/v4.4.2.md
Normal file
3
.changes/v4.4.2.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## v4.4.2 - 2025-09-12
|
||||||
|
### Fixed
|
||||||
|
* Fix document generation and workflow generation do not work on accompanying period work documents
|
13
.changes/v4.5.0.md
Normal file
13
.changes/v4.5.0.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
## v4.5.0 - 2025-10-03
|
||||||
|
### Feature
|
||||||
|
* Only allow delete of attachment on workflows that are not final
|
||||||
|
* Move up signature buttons on index workflow page for easier access
|
||||||
|
* Filter out document from attachment list if it is the same as the workflow document
|
||||||
|
* Block edition on attached document on workflow, if the workflow is finalized or sent external
|
||||||
|
* Convert workflow's attached document to pdf while sending them external
|
||||||
|
* After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition
|
||||||
|
### Fixed
|
||||||
|
* ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance
|
||||||
|
* Fix permissions on storedObject which are subject by a workflow
|
||||||
|
### DX
|
||||||
|
* Introduce a WaitingScreen component to display a waiting screen
|
4
.changes/v4.5.1.md
Normal file
4
.changes/v4.5.1.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## v4.5.1 - 2025-10-03
|
||||||
|
### Fixed
|
||||||
|
* Add missing javascript dependency
|
||||||
|
* Add exception handling for conversion of attachment on sending external, when documens are already in pdf
|
@@ -19,11 +19,11 @@ max_line_length = 80
|
|||||||
[COMMIT_EDITMSG]
|
[COMMIT_EDITMSG]
|
||||||
max_line_length = 0
|
max_line_length = 0
|
||||||
|
|
||||||
[*.{js,vue,ts}]
|
[*.{js, vue, ts}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
[*.rst]
|
[.rst]
|
||||||
indent_size = 3
|
ident_size = 3
|
||||||
indent_style = space
|
ident_style = space
|
||||||
|
|
||||||
|
13
.env
13
.env
@@ -16,6 +16,9 @@ APP_ENV=prod
|
|||||||
APP_SECRET=!ChangeMeInAppEnv!
|
APP_SECRET=!ChangeMeInAppEnv!
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
## Wopi server for editing documents online
|
||||||
|
EDITOR_SERVER=http://collabora:9980
|
||||||
|
|
||||||
# must be manually set in .env.local
|
# must be manually set in .env.local
|
||||||
# ADMIN_PASSWORD=
|
# ADMIN_PASSWORD=
|
||||||
|
|
||||||
@@ -89,13 +92,3 @@ REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
|||||||
###> symfony/ovh-cloud-notifier ###
|
###> symfony/ovh-cloud-notifier ###
|
||||||
# OVHCLOUD_DSN=ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME
|
# OVHCLOUD_DSN=ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME
|
||||||
###< symfony/ovh-cloud-notifier ###
|
###< symfony/ovh-cloud-notifier ###
|
||||||
|
|
||||||
###> symfony/mercure-bundle ###
|
|
||||||
# See https://symfony.com/doc/current/mercure.html#configuration
|
|
||||||
# The URL of the Mercure hub, used by the app to publish updates (can be a local URL)
|
|
||||||
MERCURE_URL=https://example.com/.well-known/mercure
|
|
||||||
# The public URL of the Mercure hub, used by the browser to connect
|
|
||||||
MERCURE_PUBLIC_URL=https://example.com/.well-known/mercure
|
|
||||||
# The secret used to sign the JWTs
|
|
||||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
|
||||||
###< symfony/mercure-bundle ###
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,6 +18,9 @@ migrations/*
|
|||||||
templates/*
|
templates/*
|
||||||
translations/*
|
translations/*
|
||||||
|
|
||||||
|
# we allow developers to add customization on their installation, without commiting it
|
||||||
|
config/packages/dev/*
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
/.env.local
|
/.env.local
|
||||||
/.env.local.php
|
/.env.local.php
|
||||||
|
@@ -27,11 +27,11 @@ Chill is a comprehensive web application built as a set of Symfony bundles. It i
|
|||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
Note: This is a project which exists from a long time ago, and we found multiple structure inside each bundle. When having the choice, the developers should choose the new structure.
|
Note: This is a project that's existed for a long time, and throughout the years we've used multiple structures inside each bundle. When having the choice, the developers should choose the new structure.
|
||||||
|
|
||||||
The project follows a standard Symfony bundle structure:
|
The project follows a standard Symfony bundle structure:
|
||||||
- `/src/Bundle/`: Contains all the Chill bundles. The code is either at the root of the bundle directory, or within a `src/` directory (preferred). See psr4 mapping at the root's `composer.json`.
|
- `/src/Bundle/`: Contains all the Chill bundles. The code is either at the root of the bundle directory, or within a `src/` directory (preferred). See psr4 mapping at the root's `composer.json`.
|
||||||
- each bundle come with his own tests, either in the `Tests` directory (when the code is directly within the bundle directory (for instance `src/Bundle/ChillMainBundle/Tests`, `src/Bundle/ChillPersonBundle/Tests`)), or inside the `tests` directory, alongside to the `src/` sub-directory (example: `src/Bundle/ChillWopiBundle/tests`) (this is the preferred way).
|
- each bundle comes with its own tests, either in the `Tests` directory (when the code is directly within the bundle directory (for instance `src/Bundle/ChillMainBundle/Tests`, `src/Bundle/ChillPersonBundle/Tests`)), or inside the `tests` directory, alongside the `src/` sub-directory (example: `src/Bundle/ChillWopiBundle/tests`) (this is the preferred way).
|
||||||
- `/docs/`: Contains project documentation
|
- `/docs/`: Contains project documentation
|
||||||
|
|
||||||
Each bundle typically has the following structure:
|
Each bundle typically has the following structure:
|
||||||
@@ -46,13 +46,13 @@ Each bundle typically has the following structure:
|
|||||||
|
|
||||||
### A special word about TicketBundle
|
### A special word about TicketBundle
|
||||||
|
|
||||||
The ticket bundle is developed using a kind of "Command" pattern. The controller fill a "Command", and a "CommandHandler" handle this command. They are savec in the `src/Bundle/ChillTicketBundle/src/Action` directory.
|
The ticket bundle is developed using a kind of "Command" pattern. The controller fills a "Command," and a "CommandHandler" handles this command. They are saved in the `src/Bundle/ChillTicketBundle/src/Action` directory.
|
||||||
|
|
||||||
## Development Guidelines
|
## Development Guidelines
|
||||||
|
|
||||||
### Building and Configuration Instructions
|
### Building and Configuration Instructions
|
||||||
|
|
||||||
All the command should be run through the `symfony` command, which will configure the required variables.
|
All the commands should be run through the `symfony` command, which will configure the required variables.
|
||||||
|
|
||||||
For assets, we must ensure that we use node at version `^20.0.0`. This is done using `nvm use 20`.
|
For assets, we must ensure that we use node at version `^20.0.0`. This is done using `nvm use 20`.
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ For assets, we must ensure that we use node at version `^20.0.0`. This is done u
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Set Up the Database**:
|
6. **Set Up the Database**:
|
||||||
```bash
|
```bash
|
||||||
# Create the database
|
# Create the database
|
||||||
symfony console doctrine:database:create
|
symfony console doctrine:database:create
|
||||||
@@ -99,20 +99,20 @@ For assets, we must ensure that we use node at version `^20.0.0`. This is done u
|
|||||||
symfony console doctrine:fixtures:load
|
symfony console doctrine:fixtures:load
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Build Assets**:
|
7. **Build Assets**:
|
||||||
```bash
|
```bash
|
||||||
nvm use 20
|
nvm use 20
|
||||||
yarn run encore dev
|
yarn run encore dev
|
||||||
```
|
```
|
||||||
|
|
||||||
7. **Start the Development Server**:
|
8. **Start the Development Server**:
|
||||||
```bash
|
```bash
|
||||||
symfony server:start -d
|
symfony server:start -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Docker Setup
|
#### Docker Setup
|
||||||
|
|
||||||
The project includes Docker configuration for easier development:
|
The project includes a Docker configuration for easier development:
|
||||||
|
|
||||||
1. **Start Docker Services**:
|
1. **Start Docker Services**:
|
||||||
```bash
|
```bash
|
||||||
@@ -153,9 +153,9 @@ Key configuration files:
|
|||||||
|
|
||||||
Each time a doctrine entity is created, we generate migration to adapt the database.
|
Each time a doctrine entity is created, we generate migration to adapt the database.
|
||||||
|
|
||||||
The migration are created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace <namespace>`, where the namespace is the relevant namespace for migration. As this is a bash script, do not forget to quote the `\` (`\` must become `\\` in your command).
|
The migration is created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace <namespace>`, where the namespace is the relevant namespace for migration. As this is a bash script, remember to quote the `\` (`\` must become `\\` in your command).
|
||||||
|
|
||||||
Each bundle has his own namespace for migration (always ask me to confirm that command, with a list of updated / created entities so that I can confirm you that it is ok):
|
Each bundle has his own namespace for migration (always ask me to confirm that command with a list of updated / created entities so that I can confirm to you that it is ok):
|
||||||
|
|
||||||
- `Chill\Bundle\ActivityBundle` writes migrations to `Chill\Migrations\Activity`;
|
- `Chill\Bundle\ActivityBundle` writes migrations to `Chill\Migrations\Activity`;
|
||||||
- `Chill\Bundle\BudgetBundle` writes migrations to `Chill\Migrations\Budget`;
|
- `Chill\Bundle\BudgetBundle` writes migrations to `Chill\Migrations\Budget`;
|
||||||
@@ -183,16 +183,59 @@ Once created the, comment's classes should be removed and a description of the c
|
|||||||
|
|
||||||
When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of
|
When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of
|
||||||
`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities,
|
`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities,
|
||||||
where injection does not work when restoring an entity from database, but usually possible in services.
|
where injection does not work when restoring an entity from a database, but usually possible in services.
|
||||||
|
|
||||||
|
In test, we use `\Symfony\Component\Clock\MockClock` which is an implementation of `Symfony\Component\Clock\ClockInterface`
|
||||||
|
where we have full and easy control of the date.
|
||||||
|
|
||||||
### Testing Information
|
### Testing Information
|
||||||
|
|
||||||
The project uses PHPUnit for testing. Each bundle has its own test suite, and there's also a global test suite at the root level.
|
The project uses PHPUnit for testing. Each bundle has its own test suite, and there's also a global test suite at the root level.
|
||||||
|
|
||||||
|
#### Use of mock in tests
|
||||||
|
|
||||||
|
##### General mocking
|
||||||
|
|
||||||
For creating mock, we prefer using prophecy (library phpspec/prophecy).
|
For creating mock, we prefer using prophecy (library phpspec/prophecy).
|
||||||
|
|
||||||
|
##### Useful helpers and tips that avoid creating a mock
|
||||||
|
|
||||||
|
Some notable implementations that are test helpers and avoid creating a mock:
|
||||||
|
|
||||||
|
- `\Psr\Log\NullLogger`, an implementation of `\Psr\Log\LoggerInterface`;
|
||||||
|
- `\Symfony\Component\Clock\MockClock`, an implementation of `Symfony\Component\Clock\ClockInterface` (already mentioned above);
|
||||||
|
- `\Symfony\Component\HttpClient\MockHttpClient`, an implementation of `\Symfony\Contracts\HttpClient\HttpClientInterface`;
|
||||||
|
- When using `\Symfony\Component\Mailer\MailerInterface`, we can create the mock with "InMemoryTransport":
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Symfony\Component\Mailer\Transport\InMemoryTransport;
|
||||||
|
use \Symfony\Component\Mailer\Mailer;
|
||||||
|
|
||||||
|
$transport = new InMemoryTransport();
|
||||||
|
$mailer = new Mailer($transport);
|
||||||
|
|
||||||
|
// After sending:
|
||||||
|
$messages = $transport->getSent(); // array of SentMessage
|
||||||
|
```
|
||||||
|
- When using `\Symfony\Contracts\EventDispatcher\EventDispatcherInterface`, we can use directly an instance of `\Symfony\Component\EventDispatcher\EventDispatcher`;
|
||||||
|
|
||||||
|
##### When we prefer not creating a mock
|
||||||
|
|
||||||
|
- When we use Doctrine Entities related to the project, we prefer not to use a mock: we instantiate them directly (unless it requires too much code to write);
|
||||||
|
|
||||||
|
##### Mocking final and readonly classes
|
||||||
|
|
||||||
|
Classes marked as final can't be mocked. To avoid that, either:
|
||||||
|
|
||||||
|
- we remove the `final` keyword from the class;
|
||||||
|
- we extract an interface from the final class.
|
||||||
|
|
||||||
|
This must be a decision made by a human, not by an AI. Every AI task must abort with an explicit message in that case.
|
||||||
|
|
||||||
#### Running Tests
|
#### Running Tests
|
||||||
|
|
||||||
|
The tests are run from the project's root (not from the bundle's root).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all tests
|
# Run all tests
|
||||||
vendor/bin/phpunit
|
vendor/bin/phpunit
|
||||||
@@ -254,7 +297,7 @@ class TicketTest extends TestCase
|
|||||||
|
|
||||||
#### Test Database
|
#### Test Database
|
||||||
|
|
||||||
For tests that require a database, the project uses postgresql database filled by fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file.
|
For tests that require a database, the project uses a postgresql database filled with fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file.
|
||||||
|
|
||||||
### Code Quality Tools
|
### Code Quality Tools
|
||||||
|
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false
|
|
||||||
}
|
|
30
.vscode/launch.json
vendored
30
.vscode/launch.json
vendored
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Chill Debug",
|
|
||||||
"type": "php",
|
|
||||||
"request": "launch",
|
|
||||||
"port": 9000,
|
|
||||||
"pathMappings": {
|
|
||||||
"/var/www/html": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
"preLaunchTask": "symfony"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Yarn Encore Dev (Watch)",
|
|
||||||
"type": "node-terminal",
|
|
||||||
"request": "launch",
|
|
||||||
"command": "yarn encore dev --watch",
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"compounds": [
|
|
||||||
{
|
|
||||||
"name": "Chill Debug + Yarn Encore Dev (Watch)",
|
|
||||||
"configurations": ["Chill Debug", "Yarn Encore Dev (Watch)"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
23
.vscode/tasks.json
vendored
23
.vscode/tasks.json
vendored
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"type": "shell",
|
|
||||||
"command": "symfony",
|
|
||||||
"args": [
|
|
||||||
"server:start",
|
|
||||||
"--allow-http",
|
|
||||||
"--no-tls",
|
|
||||||
"--port=8000",
|
|
||||||
"--allow-all-ip",
|
|
||||||
"-d"
|
|
||||||
],
|
|
||||||
"label": "symfony"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "shell",
|
|
||||||
"command": "yarn",
|
|
||||||
"args": ["encore", "dev", "--watch"],
|
|
||||||
"label": "webpack"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
83
CHANGELOG.md
83
CHANGELOG.md
@@ -6,6 +6,89 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
|||||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||||
|
|
||||||
|
|
||||||
|
## v4.5.1 - 2025-10-03
|
||||||
|
### Fixed
|
||||||
|
* Add missing javascript dependency
|
||||||
|
* Add exception handling for conversion of attachment on sending external, when documens are already in pdf
|
||||||
|
|
||||||
|
## v4.5.0 - 2025-10-03
|
||||||
|
### Feature
|
||||||
|
* Only allow delete of attachment on workflows that are not final
|
||||||
|
* Move up signature buttons on index workflow page for easier access
|
||||||
|
* Filter out document from attachment list if it is the same as the workflow document
|
||||||
|
* Block edition on attached document on workflow, if the workflow is finalized or sent external
|
||||||
|
* Convert workflow's attached document to pdf while sending them external
|
||||||
|
* After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition
|
||||||
|
### Fixed
|
||||||
|
* ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance
|
||||||
|
* Fix permissions on storedObject which are subject by a workflow
|
||||||
|
### DX
|
||||||
|
* Introduce a WaitingScreen component to display a waiting screen
|
||||||
|
|
||||||
|
## v4.4.2 - 2025-09-12
|
||||||
|
### Fixed
|
||||||
|
* Fix document generation and workflow generation do not work on accompanying period work documents
|
||||||
|
|
||||||
|
## v4.4.1 - 2025-09-11
|
||||||
|
### Fixed
|
||||||
|
* fix translations in duplicate evaluation document modal and realign close modal button
|
||||||
|
|
||||||
|
## v4.4.0 - 2025-09-11
|
||||||
|
### Feature
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
||||||
|
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works
|
||||||
|
### Fixed
|
||||||
|
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates
|
||||||
|
* Fix saving notification preferences in user's profile
|
||||||
|
|
||||||
|
## v4.3.0 - 2025-09-08
|
||||||
|
### Feature
|
||||||
|
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
|
||||||
|
* Add a command to generate a list of permissions
|
||||||
|
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date
|
||||||
|
|
||||||
|
**Schema Change**: Add columns or tables
|
||||||
|
### Fixed
|
||||||
|
* fix date formatting in calendar range display
|
||||||
|
* Change route URL to avoid clash with person duplicate controller method
|
||||||
|
|
||||||
|
## v4.2.1 - 2025-09-03
|
||||||
|
### Fixed
|
||||||
|
* Fix exports to work with DirectExportInterface
|
||||||
|
### DX
|
||||||
|
* Improve error message when a stored object cannot be written on local disk
|
||||||
|
|
||||||
|
|
||||||
|
## v4.2.0 - 2025-09-02
|
||||||
|
### Feature
|
||||||
|
* ([#64](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/64)) Add external identifier for a Person
|
||||||
|
|
||||||
|
**Schema Change**: Add columns or tables
|
||||||
|
* ([#330](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/330) Allow users to choose for which notifications they want to receive an email
|
||||||
|
### Fixed
|
||||||
|
* ([#422](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/422)) Fixed html layout of pages for recovering password
|
||||||
|
* Fix typo in 'uncheckAll' script for centers selection
|
||||||
|
* Fix incorrect parameter name in event details link
|
||||||
|
|
||||||
|
## v4.1.0 - 2025-08-26
|
||||||
|
### Feature
|
||||||
|
* ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes
|
||||||
|
* ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default
|
||||||
|
* Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports
|
||||||
|
|
||||||
|
**Schema Change**: Add columns or tables
|
||||||
|
### Fixed
|
||||||
|
* ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed.
|
||||||
|
* ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType
|
||||||
|
### UX
|
||||||
|
* Limit display of participations in event list
|
||||||
|
|
||||||
|
## v4.0.2 - 2025-07-09
|
||||||
|
### Fixed
|
||||||
|
* Fix add missing translation
|
||||||
|
* Fix the transfer of evaluations and documents during of accompanyingperiodwork
|
||||||
|
|
||||||
## v4.0.1 - 2025-07-08
|
## v4.0.1 - 2025-07-08
|
||||||
### Fixed
|
### Fixed
|
||||||
* Fix package.json for compilation
|
* Fix package.json for compilation
|
||||||
|
@@ -54,7 +54,7 @@ Arborescence:
|
|||||||
- person
|
- person
|
||||||
- personvendee
|
- personvendee
|
||||||
- household_edit_metadata
|
- household_edit_metadata
|
||||||
- index.ts
|
- index.js
|
||||||
```
|
```
|
||||||
|
|
||||||
## Organisation des feuilles de styles
|
## Organisation des feuilles de styles
|
||||||
|
@@ -32,9 +32,3 @@ services:
|
|||||||
hostname: my-rabbit
|
hostname: my-rabbit
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker/rabbitmq/data:/var/lib/rabbitmq
|
- ./docker/rabbitmq/data:/var/lib/rabbitmq
|
||||||
|
|
||||||
###> symfony/mercure-bundle ###
|
|
||||||
mercure:
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:8043:443"
|
|
||||||
###< symfony/mercure-bundle ###
|
|
||||||
|
31
compose.yaml
31
compose.yaml
@@ -50,36 +50,7 @@ services:
|
|||||||
timeout: 30s
|
timeout: 30s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
###> symfony/mercure-bundle ###
|
|
||||||
mercure:
|
|
||||||
image: dunglas/mercure
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
# Uncomment the following line to disable HTTPS,
|
|
||||||
#SERVER_NAME: ':80'
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
|
|
||||||
# Set the URL of your Symfony project (without trailing slash!) as value of the cors_origins directive
|
|
||||||
MERCURE_EXTRA_DIRECTIVES: |
|
|
||||||
cors_origins http://chill-bundles.wip https://chill-bundles.wip
|
|
||||||
# Comment the following line to disable the development mode
|
|
||||||
command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "curl", "-f", "https://localhost/healthz" ]
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 60s
|
|
||||||
volumes:
|
|
||||||
- mercure_data:/data
|
|
||||||
- mercure_config:/config
|
|
||||||
###< symfony/mercure-bundle ###
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
database_data:
|
database_data:
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
###> symfony/mercure-bundle ###
|
|
||||||
mercure_data:
|
|
||||||
mercure_config:
|
|
||||||
###< symfony/mercure-bundle ###
|
|
||||||
|
@@ -55,7 +55,6 @@
|
|||||||
"symfony/http-foundation": "^5.4",
|
"symfony/http-foundation": "^5.4",
|
||||||
"symfony/intl": "^5.4",
|
"symfony/intl": "^5.4",
|
||||||
"symfony/mailer": "^5.4",
|
"symfony/mailer": "^5.4",
|
||||||
"symfony/mercure-bundle": "^0.3.9",
|
|
||||||
"symfony/messenger": "^5.4",
|
"symfony/messenger": "^5.4",
|
||||||
"symfony/mime": "^5.4",
|
"symfony/mime": "^5.4",
|
||||||
"symfony/monolog-bundle": "^3.5",
|
"symfony/monolog-bundle": "^3.5",
|
||||||
@@ -134,7 +133,6 @@
|
|||||||
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
|
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
|
||||||
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
|
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
|
||||||
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
|
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
|
||||||
"Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
|
|
||||||
"Chill\\Utils\\Rector\\": "utils/rector/src"
|
"Chill\\Utils\\Rector\\": "utils/rector/src"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -35,8 +35,6 @@ return [
|
|||||||
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
|
Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
|
||||||
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
|
||||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||||
Chill\TicketBundle\ChillTicketBundle::class => ['all' => true],
|
|
||||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||||
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
|
|
||||||
];
|
];
|
||||||
|
@@ -120,3 +120,6 @@ chill_activity:
|
|||||||
-
|
-
|
||||||
label: '5 hours'
|
label: '5 hours'
|
||||||
seconds: 18000
|
seconds: 18000
|
||||||
|
|
||||||
|
chill_aside_activity:
|
||||||
|
show_concerned_persons_count: true
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
chill_ticket:
|
|
||||||
ticket:
|
|
||||||
person_per_ticket: one # One of "one"; "many"
|
|
||||||
|
|
@@ -14,7 +14,6 @@ doctrine_migrations:
|
|||||||
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
|
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
|
||||||
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
|
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
|
||||||
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
|
'Chill\Migrations\Report': '@ChillReportBundle/migrations'
|
||||||
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
|
|
||||||
all_or_nothing:
|
all_or_nothing:
|
||||||
true
|
true
|
||||||
|
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
mercure:
|
|
||||||
hubs:
|
|
||||||
default:
|
|
||||||
url: '%env(MERCURE_URL)%'
|
|
||||||
public_url: '%env(MERCURE_PUBLIC_URL)%'
|
|
||||||
jwt:
|
|
||||||
secret: '%env(MERCURE_JWT_SECRET)%'
|
|
||||||
publish: '*'
|
|
@@ -62,8 +62,10 @@ framework:
|
|||||||
'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority
|
'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority
|
||||||
'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async
|
'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async
|
||||||
'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async
|
'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async
|
||||||
|
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage': async
|
||||||
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||||
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
|
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
|
||||||
|
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
|
||||||
# end of routes added by chill-bundles recipes
|
# end of routes added by chill-bundles recipes
|
||||||
# Route your messages to the transports
|
# Route your messages to the transports
|
||||||
# 'App\Message\YourMessage': async
|
# 'App\Message\YourMessage': async
|
||||||
|
@@ -17,8 +17,3 @@ when@dev:
|
|||||||
defaults:
|
defaults:
|
||||||
template: '@ChillMain/Dev/dev.assets.test2.html.twig'
|
template: '@ChillMain/Dev/dev.assets.test2.html.twig'
|
||||||
|
|
||||||
dev_mercure:
|
|
||||||
path: /_dev/mercure
|
|
||||||
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
|
||||||
defaults:
|
|
||||||
template: '@ChillMain/Dev/dev.mercure.html.twig'
|
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
chill_ticket_bundle:
|
|
||||||
resource: '@ChillTicketBundle/config/routes.yaml'
|
|
@@ -11,94 +11,24 @@
|
|||||||
Create a new bundle
|
Create a new bundle
|
||||||
*******************
|
*******************
|
||||||
|
|
||||||
|
Create your own bundle is not a trivial task.
|
||||||
|
|
||||||
|
The easiest way to achieve this is seems to be :
|
||||||
|
|
||||||
|
1. Prepare a fresh installation of the chill project, in a new directory
|
||||||
|
2. Create a new bundle in this project, in the src directory
|
||||||
|
3. Initialize a git repository **at the root bundle**, and create your initial commit.
|
||||||
|
4. Register the bundle with composer/packagist. If you do not plan to distribute your bundle with packagist, you may use a custom repository for achieve this [#f1]_
|
||||||
|
5. Move to a development installation, made as described in the :ref:`installation-for-development` section, and add your new repository to the composer.json file
|
||||||
|
6. Work as :ref:`usual <editing-code-and-commiting>`
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
This part of the doc is not yet tested
|
This part of the doc is not yet tested
|
||||||
|
|
||||||
Create a new directory with Bundle class
|
TODO
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
mkdir -p src/Bundle/ChillSomeBundle/src/config
|
|
||||||
mkdir -p src/Bundle/ChillSomeBundle/src/Controller
|
|
||||||
|
|
||||||
Add a bundle file
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chill is a software for social workers
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view
|
|
||||||
* the LICENSE file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Chill\SomeBundle;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
|
||||||
|
|
||||||
class ChillSomeBundle extends Bundle {}
|
|
||||||
|
|
||||||
And a route file:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
chill_ticket_controller:
|
|
||||||
resource: '@ChillTicketBundle/Controller/'
|
|
||||||
type: annotation
|
|
||||||
|
|
||||||
Register the new psr-4 namespace
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
In composer.json, add the new psr4 namespace
|
|
||||||
|
|
||||||
.. code-block:: diff
|
|
||||||
|
|
||||||
{
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
+ "Chill\\SomeBundle\\": "src/Bundle/ChillSomeBundle/src",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Register the bundle
|
.. rubric:: Footnotes
|
||||||
-------------------
|
|
||||||
|
|
||||||
Register in the file :code:`config/bundles.php`:
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
Vendor\Bundle\YourBundle\YourBundle::class => ['all' => true],
|
|
||||||
|
|
||||||
And import routes in :code:`config/routes/chill_some_bundle.yaml`:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
chill_ticket_bundle:
|
|
||||||
resource: '@ChillSomeBundle/config/routes.yaml'
|
|
||||||
|
|
||||||
Add the doctrine_migration namespace
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
Add the namespace to :code:`config/packages/doctrine_migrations_chill.yaml`
|
|
||||||
|
|
||||||
.. code-block:: diff
|
|
||||||
|
|
||||||
doctrine_migrations:
|
|
||||||
migrations_paths:
|
|
||||||
+ 'Chill\Some\Ticket': '@ChillSomeBundle/migrations'
|
|
||||||
|
|
||||||
Dump autoloading
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
symfony composer dump-autoload
|
|
||||||
|
|
||||||
|
.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software.
|
||||||
|
@@ -11,7 +11,6 @@
|
|||||||
"@hotwired/stimulus": "^3.0.0",
|
"@hotwired/stimulus": "^3.0.0",
|
||||||
"@luminateone/eslint-baseline": "^1.0.9",
|
"@luminateone/eslint-baseline": "^1.0.9",
|
||||||
"@symfony/stimulus-bridge": "^3.2.0",
|
"@symfony/stimulus-bridge": "^3.2.0",
|
||||||
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
|
||||||
"@symfony/webpack-encore": "^4.1.0",
|
"@symfony/webpack-encore": "^4.1.0",
|
||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
@@ -56,6 +55,7 @@
|
|||||||
"@tsconfig/node20": "^20.1.4",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/leaflet": "^1.9.3",
|
"@types/leaflet": "^1.9.3",
|
||||||
|
"@vueuse/core": "^13.9.0",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
"dropzone": "^5.7.6",
|
"dropzone": "^5.7.6",
|
||||||
"es6-promise": "^4.2.8",
|
"es6-promise": "^4.2.8",
|
||||||
@@ -80,12 +80,12 @@
|
|||||||
"dev": "encore dev",
|
"dev": "encore dev",
|
||||||
"watch": "encore dev --watch",
|
"watch": "encore dev --watch",
|
||||||
"build": "encore production --progress",
|
"build": "encore production --progress",
|
||||||
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||||
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
||||||
"specs-create-dir": "mkdir -p templates/api",
|
"specs-create-dir": "mkdir -p templates/api",
|
||||||
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||||
"version": "node --version",
|
"version": "node --version",
|
||||||
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
@@ -58,10 +58,6 @@
|
|||||||
<!-- temporarily removed, the time to find a fix -->
|
<!-- temporarily removed, the time to find a fix -->
|
||||||
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
|
|
||||||
<testsuite name="TicketBundle">
|
|
||||||
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
|
|
||||||
</testsuite>
|
|
||||||
<!--
|
<!--
|
||||||
<testsuite name="ReportBundle">
|
<testsuite name="ReportBundle">
|
||||||
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
# Désactive les redirections automatiques HTTP -> HTTPS
|
|
||||||
# auto_https off
|
|
||||||
# Désactive le port 80 par défaut
|
|
||||||
# default_bind :8080
|
|
||||||
}
|
|
||||||
|
|
||||||
localhost:8043 {
|
|
||||||
mercure {
|
|
||||||
# Publisher JWT key
|
|
||||||
publisher_jwt !ChangeThisMercureHubJWTSecretKey!
|
|
||||||
# Subscriber JWT key
|
|
||||||
subscriber_jwt !ChangeThisMercureHubJWTSecretKey!
|
|
||||||
cors_origins http://chill-bundles.wip https://chill-bundles.wip
|
|
||||||
ui
|
|
||||||
demo
|
|
||||||
}
|
|
||||||
|
|
||||||
respond "Not Found" 404
|
|
||||||
}
|
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<concerned-groups v-if="hasPerson" />
|
<concerned-groups v-if="hasPerson" />
|
||||||
<social-issues-acc v-if="hasSocialIssues" />
|
<social-issues-acc v-if="hasSocialIssues" />
|
||||||
<location v-if="hasLocation" />
|
<location v-if="hasLocation" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -10,12 +10,12 @@ import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
|
|||||||
import Location from "./components/Location.vue";
|
import Location from "./components/Location.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
props: ["hasSocialIssues", "hasLocation", "hasPerson"],
|
||||||
components: {
|
components: {
|
||||||
ConcernedGroups,
|
ConcernedGroups,
|
||||||
SocialIssuesAcc,
|
SocialIssuesAcc,
|
||||||
Location,
|
Location,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,43 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#add-persons" v-if="isComponentVisible">
|
<teleport to="#add-persons" v-if="isComponentVisible">
|
||||||
<div class="flex-bloc concerned-groups" :class="getContext">
|
<div class="flex-bloc concerned-groups" :class="getContext">
|
||||||
<persons-bloc
|
<persons-bloc
|
||||||
v-for="bloc in contextPersonsBlocs"
|
v-for="bloc in contextPersonsBlocs"
|
||||||
:key="bloc.key"
|
:key="bloc.key"
|
||||||
:bloc="bloc"
|
:bloc="bloc"
|
||||||
:bloc-width="getBlocWidth"
|
:bloc-width="getBlocWidth"
|
||||||
:set-persons-in-bloc="setPersonsInBloc"
|
:set-persons-in-bloc="setPersonsInBloc"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"
|
v-if="
|
||||||
>
|
getContext === 'accompanyingCourse' &&
|
||||||
<ul class="list-suggest add-items inline">
|
suggestedEntities.length > 0
|
||||||
<li
|
"
|
||||||
v-for="(p, i) in suggestedEntities"
|
|
||||||
@click="addSuggestedEntity(p)"
|
|
||||||
:key="`suggestedEntities-${i}`"
|
|
||||||
>
|
>
|
||||||
<person-text v-if="p.type === 'person'" :person="p" />
|
<ul class="list-suggest add-items inline">
|
||||||
<span v-else>{{ p.text }}</span>
|
<li
|
||||||
</li>
|
v-for="(p, i) in suggestedEntities"
|
||||||
</ul>
|
@click="addSuggestedEntity(p)"
|
||||||
</div>
|
:key="`suggestedEntities-${i}`"
|
||||||
|
>
|
||||||
|
<person-text v-if="p.type === 'person'" :person="p" />
|
||||||
|
<span v-else>{{ p.text }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li class="add-persons">
|
<li class="add-persons">
|
||||||
<add-persons
|
<add-persons
|
||||||
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
:buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
:modalTitle="trans(ACTIVITY_ADD_PERSONS)"
|
||||||
v-bind:key="addPersons.key"
|
v-bind:key="addPersons.key"
|
||||||
v-bind:options="addPersonsOptions"
|
v-bind:options="addPersonsOptions"
|
||||||
@addNewPersons="addNewPersons"
|
@addNewPersons="addNewPersons"
|
||||||
ref="addPersons"
|
ref="addPersons"
|
||||||
>
|
>
|
||||||
</add-persons>
|
</add-persons>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -46,208 +49,208 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
|||||||
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
|
||||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||||
import {
|
import {
|
||||||
ACTIVITY_BLOC_PERSONS,
|
ACTIVITY_BLOC_PERSONS,
|
||||||
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
ACTIVITY_BLOC_PERSONS_ASSOCIATED,
|
||||||
ACTIVITY_BLOC_THIRDPARTY,
|
ACTIVITY_BLOC_THIRDPARTY,
|
||||||
ACTIVITY_BLOC_USERS,
|
ACTIVITY_BLOC_USERS,
|
||||||
ACTIVITY_ADD_PERSONS,
|
ACTIVITY_ADD_PERSONS,
|
||||||
trans,
|
trans,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ConcernedGroups",
|
name: "ConcernedGroups",
|
||||||
components: {
|
components: {
|
||||||
AddPersons,
|
AddPersons,
|
||||||
PersonsBloc,
|
PersonsBloc,
|
||||||
PersonText,
|
PersonText,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
trans,
|
trans,
|
||||||
ACTIVITY_ADD_PERSONS,
|
ACTIVITY_ADD_PERSONS,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
personsBlocs: [
|
personsBlocs: [
|
||||||
{
|
{
|
||||||
key: "persons",
|
key: "persons",
|
||||||
title: trans(ACTIVITY_BLOC_PERSONS),
|
title: trans(ACTIVITY_BLOC_PERSONS),
|
||||||
persons: [],
|
persons: [],
|
||||||
included: false,
|
included: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "personsAssociated",
|
||||||
|
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "personsNotAssociated",
|
||||||
|
title: "activity.bloc_persons_not_associated",
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "thirdparty",
|
||||||
|
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.thirdPartiesVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "users",
|
||||||
|
title: trans(ACTIVITY_BLOC_USERS),
|
||||||
|
persons: [],
|
||||||
|
included: window.activity
|
||||||
|
? window.activity.activityType.usersVisible !== 0
|
||||||
|
: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addPersons: {
|
||||||
|
key: "activity",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isComponentVisible() {
|
||||||
|
return window.activity
|
||||||
|
? window.activity.activityType.personsVisible !== 0 ||
|
||||||
|
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
||||||
|
window.activity.activityType.usersVisible !== 0
|
||||||
|
: true;
|
||||||
},
|
},
|
||||||
{
|
...mapState({
|
||||||
key: "personsAssociated",
|
persons: (state) => state.activity.persons,
|
||||||
title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
|
thirdParties: (state) => state.activity.thirdParties,
|
||||||
persons: [],
|
users: (state) => state.activity.users,
|
||||||
included: window.activity
|
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
||||||
? window.activity.activityType.personsVisible !== 0
|
}),
|
||||||
: true,
|
...mapGetters(["suggestedEntities"]),
|
||||||
|
getContext() {
|
||||||
|
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
||||||
},
|
},
|
||||||
{
|
contextPersonsBlocs() {
|
||||||
key: "personsNotAssociated",
|
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
||||||
title: "activity.bloc_persons_not_associated",
|
|
||||||
persons: [],
|
|
||||||
included: window.activity
|
|
||||||
? window.activity.activityType.personsVisible !== 0
|
|
||||||
: true,
|
|
||||||
},
|
},
|
||||||
{
|
addPersonsOptions() {
|
||||||
key: "thirdparty",
|
let optionsType = [];
|
||||||
title: trans(ACTIVITY_BLOC_THIRDPARTY),
|
if (window.activity) {
|
||||||
persons: [],
|
if (window.activity.activityType.personsVisible !== 0) {
|
||||||
included: window.activity
|
optionsType.push("person");
|
||||||
? window.activity.activityType.thirdPartiesVisible !== 0
|
}
|
||||||
: true,
|
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
||||||
|
optionsType.push("thirdparty");
|
||||||
|
}
|
||||||
|
if (window.activity.activityType.usersVisible !== 0) {
|
||||||
|
optionsType.push("user");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
optionsType = ["person", "thirdparty", "user"];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: optionsType,
|
||||||
|
priority: null,
|
||||||
|
uniq: false,
|
||||||
|
button: {
|
||||||
|
size: "btn-sm",
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
{
|
getBlocWidth() {
|
||||||
key: "users",
|
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
||||||
title: trans(ACTIVITY_BLOC_USERS),
|
|
||||||
persons: [],
|
|
||||||
included: window.activity
|
|
||||||
? window.activity.activityType.usersVisible !== 0
|
|
||||||
: true,
|
|
||||||
},
|
},
|
||||||
],
|
|
||||||
addPersons: {
|
|
||||||
key: "activity",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isComponentVisible() {
|
|
||||||
return window.activity
|
|
||||||
? window.activity.activityType.personsVisible !== 0 ||
|
|
||||||
window.activity.activityType.thirdPartiesVisible !== 0 ||
|
|
||||||
window.activity.activityType.usersVisible !== 0
|
|
||||||
: true;
|
|
||||||
},
|
},
|
||||||
...mapState({
|
mounted() {
|
||||||
persons: (state) => state.activity.persons,
|
this.setPersonsInBloc();
|
||||||
thirdParties: (state) => state.activity.thirdParties,
|
|
||||||
users: (state) => state.activity.users,
|
|
||||||
accompanyingCourse: (state) => state.activity.accompanyingPeriod,
|
|
||||||
}),
|
|
||||||
...mapGetters(["suggestedEntities"]),
|
|
||||||
getContext() {
|
|
||||||
return this.accompanyingCourse ? "accompanyingCourse" : "person";
|
|
||||||
},
|
},
|
||||||
contextPersonsBlocs() {
|
methods: {
|
||||||
return this.personsBlocs.filter((bloc) => bloc.included !== false);
|
setPersonsInBloc() {
|
||||||
},
|
let groups;
|
||||||
addPersonsOptions() {
|
if (this.accompanyingCourse) {
|
||||||
let optionsType = [];
|
groups = this.splitPersonsInGroups();
|
||||||
if (window.activity) {
|
}
|
||||||
if (window.activity.activityType.personsVisible !== 0) {
|
this.personsBlocs.forEach((bloc) => {
|
||||||
optionsType.push("person");
|
if (this.accompanyingCourse) {
|
||||||
}
|
switch (bloc.key) {
|
||||||
if (window.activity.activityType.thirdPartiesVisible !== 0) {
|
case "personsAssociated":
|
||||||
optionsType.push("thirdparty");
|
bloc.persons = groups.personsAssociated;
|
||||||
}
|
bloc.included = true;
|
||||||
if (window.activity.activityType.usersVisible !== 0) {
|
break;
|
||||||
optionsType.push("user");
|
case "personsNotAssociated":
|
||||||
}
|
bloc.persons = groups.personsNotAssociated;
|
||||||
} else {
|
bloc.included = true;
|
||||||
optionsType = ["person", "thirdparty", "user"];
|
break;
|
||||||
}
|
}
|
||||||
return {
|
} else {
|
||||||
type: optionsType,
|
switch (bloc.key) {
|
||||||
priority: null,
|
case "persons":
|
||||||
uniq: false,
|
bloc.persons = this.persons;
|
||||||
button: {
|
bloc.included = true;
|
||||||
size: "btn-sm",
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (bloc.key) {
|
||||||
|
case "thirdparty":
|
||||||
|
bloc.persons = this.thirdParties;
|
||||||
|
break;
|
||||||
|
case "users":
|
||||||
|
bloc.persons = this.users;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, groups);
|
||||||
|
},
|
||||||
|
splitPersonsInGroups() {
|
||||||
|
let personsAssociated = [];
|
||||||
|
let personsNotAssociated = this.persons;
|
||||||
|
let participations = this.getCourseParticipations();
|
||||||
|
this.persons.forEach((person) => {
|
||||||
|
participations.forEach((participation) => {
|
||||||
|
if (person.id === participation.id) {
|
||||||
|
//console.log(person.id);
|
||||||
|
personsAssociated.push(person);
|
||||||
|
personsNotAssociated = personsNotAssociated.filter(
|
||||||
|
(p) => p !== person,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
personsAssociated: personsAssociated,
|
||||||
|
personsNotAssociated: personsNotAssociated,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getCourseParticipations() {
|
||||||
|
let participations = [];
|
||||||
|
this.accompanyingCourse.participations.forEach((participation) => {
|
||||||
|
if (!participation.endDate) {
|
||||||
|
participations.push(participation.person);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return participations;
|
||||||
|
},
|
||||||
|
addNewPersons({ selected, modal }) {
|
||||||
|
console.log("@@@ CLICK button addNewPersons", selected);
|
||||||
|
selected.forEach((item) => {
|
||||||
|
this.$store.dispatch("addPersonsInvolved", item);
|
||||||
|
}, this);
|
||||||
|
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||||
|
modal.showModal = false;
|
||||||
|
this.setPersonsInBloc();
|
||||||
|
},
|
||||||
|
addSuggestedEntity(person) {
|
||||||
|
this.$store.dispatch("addPersonsInvolved", {
|
||||||
|
result: person,
|
||||||
|
type: "person",
|
||||||
|
});
|
||||||
|
this.setPersonsInBloc();
|
||||||
},
|
},
|
||||||
};
|
|
||||||
},
|
},
|
||||||
getBlocWidth() {
|
|
||||||
return Math.round(100 / this.contextPersonsBlocs.length) + "%";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.setPersonsInBloc();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setPersonsInBloc() {
|
|
||||||
let groups;
|
|
||||||
if (this.accompanyingCourse) {
|
|
||||||
groups = this.splitPersonsInGroups();
|
|
||||||
}
|
|
||||||
this.personsBlocs.forEach((bloc) => {
|
|
||||||
if (this.accompanyingCourse) {
|
|
||||||
switch (bloc.key) {
|
|
||||||
case "personsAssociated":
|
|
||||||
bloc.persons = groups.personsAssociated;
|
|
||||||
bloc.included = true;
|
|
||||||
break;
|
|
||||||
case "personsNotAssociated":
|
|
||||||
bloc.persons = groups.personsNotAssociated;
|
|
||||||
bloc.included = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (bloc.key) {
|
|
||||||
case "persons":
|
|
||||||
bloc.persons = this.persons;
|
|
||||||
bloc.included = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (bloc.key) {
|
|
||||||
case "thirdparty":
|
|
||||||
bloc.persons = this.thirdParties;
|
|
||||||
break;
|
|
||||||
case "users":
|
|
||||||
bloc.persons = this.users;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}, groups);
|
|
||||||
},
|
|
||||||
splitPersonsInGroups() {
|
|
||||||
let personsAssociated = [];
|
|
||||||
let personsNotAssociated = this.persons;
|
|
||||||
let participations = this.getCourseParticipations();
|
|
||||||
this.persons.forEach((person) => {
|
|
||||||
participations.forEach((participation) => {
|
|
||||||
if (person.id === participation.id) {
|
|
||||||
//console.log(person.id);
|
|
||||||
personsAssociated.push(person);
|
|
||||||
personsNotAssociated = personsNotAssociated.filter(
|
|
||||||
(p) => p !== person,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
personsAssociated: personsAssociated,
|
|
||||||
personsNotAssociated: personsNotAssociated,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getCourseParticipations() {
|
|
||||||
let participations = [];
|
|
||||||
this.accompanyingCourse.participations.forEach((participation) => {
|
|
||||||
if (!participation.endDate) {
|
|
||||||
participations.push(participation.person);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return participations;
|
|
||||||
},
|
|
||||||
addNewPersons({ selected, modal }) {
|
|
||||||
console.log("@@@ CLICK button addNewPersons", selected);
|
|
||||||
selected.forEach((item) => {
|
|
||||||
this.$store.dispatch("addPersonsInvolved", item);
|
|
||||||
}, this);
|
|
||||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
|
||||||
modal.showModal = false;
|
|
||||||
this.setPersonsInBloc();
|
|
||||||
},
|
|
||||||
addSuggestedEntity(person) {
|
|
||||||
this.$store.dispatch("addPersonsInvolved", {
|
|
||||||
result: person,
|
|
||||||
type: "person",
|
|
||||||
});
|
|
||||||
this.setPersonsInBloc();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -1,29 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<li>
|
<li>
|
||||||
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
<span :title="person.text" @click.prevent="$emit('remove', person)">
|
||||||
<span class="chill_denomination">
|
<span class="chill_denomination">
|
||||||
<person-text :person="person" :is-cut="true" />
|
<person-text :person="person" :is-cut="true" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PersonBadge",
|
name: "PersonBadge",
|
||||||
props: ["person"],
|
props: ["person"],
|
||||||
components: {
|
components: {
|
||||||
PersonText,
|
PersonText,
|
||||||
},
|
},
|
||||||
// computed: {
|
// computed: {
|
||||||
// textCutted() {
|
// textCutted() {
|
||||||
// let more = (this.person.text.length > 15) ?'…' : '';
|
// let more = (this.person.text.length > 15) ?'…' : '';
|
||||||
// return this.person.text.slice(0,15) + more;
|
// return this.person.text.slice(0,15) + more;
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
emits: ["remove"],
|
emits: ["remove"],
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -1,38 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
<div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<h4>{{ $t(bloc.title) }}</h4>
|
<h4>{{ $t(bloc.title) }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-col">
|
<div class="item-col">
|
||||||
<ul class="list-suggest remove-items">
|
<ul class="list-suggest remove-items">
|
||||||
<person-badge
|
<person-badge
|
||||||
v-for="person in bloc.persons"
|
v-for="person in bloc.persons"
|
||||||
:key="person.id"
|
:key="person.id"
|
||||||
:person="person"
|
:person="person"
|
||||||
@remove="removePerson"
|
@remove="removePerson"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PersonBadge from "./PersonBadge.vue";
|
import PersonBadge from "./PersonBadge.vue";
|
||||||
export default {
|
export default {
|
||||||
name: "PersonsBloc",
|
name: "PersonsBloc",
|
||||||
components: {
|
components: {
|
||||||
PersonBadge,
|
PersonBadge,
|
||||||
},
|
},
|
||||||
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
props: ["bloc", "setPersonsInBloc", "blocWidth"],
|
||||||
methods: {
|
methods: {
|
||||||
removePerson(item) {
|
removePerson(item) {
|
||||||
console.log("@@ CLICK remove person: item", item);
|
console.log("@@ CLICK remove person: item", item);
|
||||||
this.$store.dispatch("removePersonInvolved", item);
|
this.$store.dispatch("removePersonInvolved", item);
|
||||||
this.setPersonsInBloc();
|
this.setPersonsInBloc();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -1,32 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#location">
|
<teleport to="#location">
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<label :class="locationClassList">
|
<label :class="locationClassList">
|
||||||
{{ trans(ACTIVITY_LOCATION) }}
|
{{ trans(ACTIVITY_LOCATION) }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="selectLocation"
|
name="selectLocation"
|
||||||
id="selectLocation"
|
id="selectLocation"
|
||||||
label="name"
|
label="name"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
open-direction="top"
|
open-direction="top"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
:placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
|
||||||
:custom-label="customLabel"
|
:custom-label="customLabel"
|
||||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||||
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||||
:options="availableLocations"
|
:options="availableLocations"
|
||||||
group-values="locations"
|
group-values="locations"
|
||||||
group-label="locationGroup"
|
group-label="locationGroup"
|
||||||
v-model="location"
|
v-model="location"
|
||||||
/>
|
/>
|
||||||
<new-location v-bind:available-locations="availableLocations" />
|
<new-location v-bind:available-locations="availableLocations" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -35,60 +35,60 @@ import VueMultiselect from "vue-multiselect";
|
|||||||
import NewLocation from "./Location/NewLocation.vue";
|
import NewLocation from "./Location/NewLocation.vue";
|
||||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||||
import {
|
import {
|
||||||
trans,
|
trans,
|
||||||
ACTIVITY_LOCATION,
|
ACTIVITY_LOCATION,
|
||||||
ACTIVITY_CHOOSE_LOCATION,
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
MULTISELECT_SELECT_LABEL,
|
MULTISELECT_SELECT_LABEL,
|
||||||
MULTISELECT_DESELECT_LABEL,
|
MULTISELECT_DESELECT_LABEL,
|
||||||
MULTISELECT_SELECTED_LABEL,
|
MULTISELECT_SELECTED_LABEL,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Location",
|
name: "Location",
|
||||||
components: {
|
components: {
|
||||||
NewLocation,
|
NewLocation,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
trans,
|
|
||||||
ACTIVITY_LOCATION,
|
|
||||||
ACTIVITY_CHOOSE_LOCATION,
|
|
||||||
MULTISELECT_SELECT_LABEL,
|
|
||||||
MULTISELECT_DESELECT_LABEL,
|
|
||||||
MULTISELECT_SELECTED_LABEL,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(["activity", "availableLocations"]),
|
|
||||||
...mapGetters(["suggestedEntities"]),
|
|
||||||
location: {
|
|
||||||
get() {
|
|
||||||
return this.activity.location;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$store.dispatch("updateLocation", value);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
setup() {
|
||||||
methods: {
|
return {
|
||||||
labelAccompanyingCourseLocation(value) {
|
trans,
|
||||||
return `${value.address.text} (${localizeString(value.locationType.title)})`;
|
ACTIVITY_LOCATION,
|
||||||
|
ACTIVITY_CHOOSE_LOCATION,
|
||||||
|
MULTISELECT_SELECT_LABEL,
|
||||||
|
MULTISELECT_DESELECT_LABEL,
|
||||||
|
MULTISELECT_SELECTED_LABEL,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
customLabel(value) {
|
data() {
|
||||||
return value.locationType
|
return {
|
||||||
? value.name
|
locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
|
||||||
? value.name === "__AccompanyingCourseLocation__"
|
};
|
||||||
? this.labelAccompanyingCourseLocation(value)
|
},
|
||||||
: `${value.name} (${localizeString(value.locationType.title)})`
|
computed: {
|
||||||
: localizeString(value.locationType.title)
|
...mapState(["activity", "availableLocations"]),
|
||||||
: "";
|
...mapGetters(["suggestedEntities"]),
|
||||||
|
location: {
|
||||||
|
get() {
|
||||||
|
return this.activity.location;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$store.dispatch("updateLocation", value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
labelAccompanyingCourseLocation(value) {
|
||||||
|
return `${value.address.text} (${localizeString(value.locationType.title)})`;
|
||||||
|
},
|
||||||
|
customLabel(value) {
|
||||||
|
return value.locationType
|
||||||
|
? value.name
|
||||||
|
? value.name === "__AccompanyingCourseLocation__"
|
||||||
|
? this.labelAccompanyingCourseLocation(value)
|
||||||
|
: `${value.name} (${localizeString(value.locationType.title)})`
|
||||||
|
: localizeString(value.locationType.title)
|
||||||
|
: "";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,114 +1,123 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-sm btn-create" @click="openModal">
|
<a class="btn btn-sm btn-create" @click="openModal">
|
||||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<modal
|
<modal
|
||||||
v-if="modal.showModal"
|
v-if="modal.showModal"
|
||||||
:modalDialogClass="modal.modalDialogClass"
|
:modalDialogClass="modal.modalDialogClass"
|
||||||
@close="modal.showModal = false"
|
@close="modal.showModal = false"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<h3 class="modal-title">
|
<h3 class="modal-title">
|
||||||
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
{{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<form>
|
<form>
|
||||||
<div class="alert alert-warning" v-if="errors.length">
|
<div class="alert alert-warning" v-if="errors.length">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(e, i) in errors" :key="i">
|
<li v-for="(e, i) in errors" :key="i">
|
||||||
{{ e }}
|
{{ e }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<select
|
<select
|
||||||
class="form-select form-select-lg"
|
class="form-select form-select-lg"
|
||||||
id="type"
|
id="type"
|
||||||
required
|
required
|
||||||
v-model="selectType"
|
v-model="selectType"
|
||||||
>
|
>
|
||||||
<option selected disabled value="">
|
<option selected disabled value="">
|
||||||
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
{{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
|
||||||
</option>
|
</option>
|
||||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
<option
|
||||||
{{ localizeString(t.title) }}
|
v-for="t in locationTypes"
|
||||||
</option>
|
:value="t"
|
||||||
</select>
|
:key="t.id"
|
||||||
<label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
|
>
|
||||||
</div>
|
{{ localizeString(t.title) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<label>{{
|
||||||
|
trans(ACTIVITY_LOCATION_FIELDS_TYPE)
|
||||||
|
}}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="name"
|
id="name"
|
||||||
v-model="inputName"
|
v-model="inputName"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="name">{{
|
<label for="name">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
trans(ACTIVITY_LOCATION_FIELDS_NAME)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<add-address
|
<add-address
|
||||||
:context="addAddress.context"
|
:context="addAddress.context"
|
||||||
:options="addAddress.options"
|
:options="addAddress.options"
|
||||||
:addressChangedCallback="submitNewAddress"
|
:addressChangedCallback="submitNewAddress"
|
||||||
v-if="showAddAddress"
|
v-if="showAddAddress"
|
||||||
ref="addAddress"
|
ref="addAddress"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="phonenumber1"
|
id="phonenumber1"
|
||||||
v-model="inputPhonenumber1"
|
v-model="inputPhonenumber1"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="phonenumber1">{{
|
<label for="phonenumber1">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="phonenumber2"
|
id="phonenumber2"
|
||||||
v-model="inputPhonenumber2"
|
v-model="inputPhonenumber2"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="phonenumber2">{{
|
<label for="phonenumber2">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3" v-if="showContactData">
|
<div class="form-floating mb-3" v-if="showContactData">
|
||||||
<input
|
<input
|
||||||
class="form-control form-control-lg"
|
class="form-control form-control-lg"
|
||||||
id="email"
|
id="email"
|
||||||
v-model="inputEmail"
|
v-model="inputEmail"
|
||||||
placeholder
|
placeholder
|
||||||
/>
|
/>
|
||||||
<label for="email">{{
|
<label for="email">{{
|
||||||
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button class="btn btn-save" @click.prevent="saveNewLocation">
|
<button
|
||||||
{{ trans(SAVE) }}
|
class="btn btn-save"
|
||||||
</button>
|
@click.prevent="saveNewLocation"
|
||||||
</template>
|
>
|
||||||
</modal>
|
{{ trans(SAVE) }}
|
||||||
</teleport>
|
</button>
|
||||||
</div>
|
</template>
|
||||||
|
</modal>
|
||||||
|
</teleport>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -119,236 +128,237 @@ import { getLocationTypes } from "../../api";
|
|||||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||||
import {
|
import {
|
||||||
SAVE,
|
SAVE,
|
||||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||||
ACTIVITY_CREATE_NEW_LOCATION,
|
ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
trans,
|
trans,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NewLocation",
|
name: "NewLocation",
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
AddAddress,
|
AddAddress,
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
trans,
|
|
||||||
SAVE,
|
|
||||||
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
|
||||||
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
|
||||||
ACTIVITY_LOCATION_FIELDS_NAME,
|
|
||||||
ACTIVITY_LOCATION_FIELDS_TYPE,
|
|
||||||
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
|
||||||
ACTIVITY_CREATE_NEW_LOCATION,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: ["availableLocations"],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
errors: [],
|
|
||||||
selected: {
|
|
||||||
type: null,
|
|
||||||
name: null,
|
|
||||||
addressId: null,
|
|
||||||
phonenumber1: null,
|
|
||||||
phonenumber2: null,
|
|
||||||
email: null,
|
|
||||||
},
|
|
||||||
locationTypes: [],
|
|
||||||
modal: {
|
|
||||||
showModal: false,
|
|
||||||
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
|
||||||
},
|
|
||||||
addAddress: {
|
|
||||||
options: {
|
|
||||||
button: {
|
|
||||||
text: {
|
|
||||||
create: "activity.create_address",
|
|
||||||
edit: "activity.edit_address",
|
|
||||||
},
|
|
||||||
size: "btn-sm",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
create: "activity.create_address",
|
|
||||||
edit: "activity.edit_address",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
target: {
|
|
||||||
//name, id
|
|
||||||
},
|
|
||||||
edit: false,
|
|
||||||
addressId: null,
|
|
||||||
defaults: window.addaddress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(["activity"]),
|
|
||||||
selectType: {
|
|
||||||
get() {
|
|
||||||
return this.selected.type;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.type = value;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
inputName: {
|
setup() {
|
||||||
get() {
|
return {
|
||||||
return this.selected.name;
|
trans,
|
||||||
},
|
SAVE,
|
||||||
set(value) {
|
ACTIVITY_LOCATION_FIELDS_EMAIL,
|
||||||
this.selected.name = value;
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
|
||||||
},
|
ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
|
||||||
},
|
ACTIVITY_LOCATION_FIELDS_NAME,
|
||||||
inputEmail: {
|
ACTIVITY_LOCATION_FIELDS_TYPE,
|
||||||
get() {
|
ACTIVITY_CHOOSE_LOCATION_TYPE,
|
||||||
return this.selected.email;
|
ACTIVITY_CREATE_NEW_LOCATION,
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.email = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputPhonenumber1: {
|
|
||||||
get() {
|
|
||||||
return this.selected.phonenumber1;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.phonenumber1 = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputPhonenumber2: {
|
|
||||||
get() {
|
|
||||||
return this.selected.phonenumber2;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.selected.phonenumber2 = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hasPhonenumber1() {
|
|
||||||
return (
|
|
||||||
this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showAddAddress() {
|
|
||||||
let cond = false;
|
|
||||||
if (this.selected.type) {
|
|
||||||
if (this.selected.type.addressRequired !== "never") {
|
|
||||||
cond = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cond;
|
|
||||||
},
|
|
||||||
showContactData() {
|
|
||||||
let cond = false;
|
|
||||||
if (this.selected.type) {
|
|
||||||
if (this.selected.type.contactData !== "never") {
|
|
||||||
cond = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cond;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getLocationTypesList();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
localizeString,
|
|
||||||
checkForm() {
|
|
||||||
let cond = true;
|
|
||||||
this.errors = [];
|
|
||||||
if (!this.selected.type) {
|
|
||||||
this.errors.push("Type de localisation requis");
|
|
||||||
cond = false;
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
this.selected.type.addressRequired === "required" &&
|
|
||||||
!this.selected.addressId
|
|
||||||
) {
|
|
||||||
this.errors.push("Adresse requise");
|
|
||||||
cond = false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.selected.type.contactData === "required" &&
|
|
||||||
!this.selected.phonenumber1
|
|
||||||
) {
|
|
||||||
this.errors.push("Numéro de téléphone requis");
|
|
||||||
cond = false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.selected.type.contactData === "required" &&
|
|
||||||
!this.selected.email
|
|
||||||
) {
|
|
||||||
this.errors.push("Adresse email requise");
|
|
||||||
cond = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cond;
|
|
||||||
},
|
|
||||||
getLocationTypesList() {
|
|
||||||
getLocationTypes().then((results) => {
|
|
||||||
this.locationTypes = results.filter(
|
|
||||||
(t) => t.availableForUsers === true,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
openModal() {
|
|
||||||
this.modal.showModal = true;
|
|
||||||
},
|
|
||||||
saveNewLocation() {
|
|
||||||
if (this.checkForm()) {
|
|
||||||
let body = {
|
|
||||||
type: "location",
|
|
||||||
name: this.selected.name,
|
|
||||||
locationType: {
|
|
||||||
id: this.selected.type.id,
|
|
||||||
type: "location-type",
|
|
||||||
},
|
|
||||||
phonenumber1: this.selected.phonenumber1,
|
|
||||||
phonenumber2: this.selected.phonenumber2,
|
|
||||||
email: this.selected.email,
|
|
||||||
};
|
};
|
||||||
if (this.selected.addressId) {
|
},
|
||||||
body = Object.assign(body, {
|
props: ["availableLocations"],
|
||||||
address: {
|
data() {
|
||||||
id: this.selected.addressId,
|
return {
|
||||||
|
errors: [],
|
||||||
|
selected: {
|
||||||
|
type: null,
|
||||||
|
name: null,
|
||||||
|
addressId: null,
|
||||||
|
phonenumber1: null,
|
||||||
|
phonenumber2: null,
|
||||||
|
email: null,
|
||||||
},
|
},
|
||||||
});
|
locationTypes: [],
|
||||||
}
|
modal: {
|
||||||
|
showModal: false,
|
||||||
makeFetch("POST", "/api/1.0/main/location.json", body)
|
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||||
.then((response) => {
|
},
|
||||||
this.$store.dispatch("addAvailableLocationGroup", {
|
addAddress: {
|
||||||
locationGroup: "Localisations nouvellement créées",
|
options: {
|
||||||
locations: [response],
|
button: {
|
||||||
});
|
text: {
|
||||||
this.$store.dispatch("updateLocation", response);
|
create: "activity.create_address",
|
||||||
this.modal.showModal = false;
|
edit: "activity.edit_address",
|
||||||
})
|
},
|
||||||
.catch((error) => {
|
size: "btn-sm",
|
||||||
if (error.name === "ValidationException") {
|
},
|
||||||
for (let v of error.violations) {
|
title: {
|
||||||
this.errors.push(v);
|
create: "activity.create_address",
|
||||||
}
|
edit: "activity.edit_address",
|
||||||
} else {
|
},
|
||||||
this.errors.push("An error occurred");
|
},
|
||||||
|
context: {
|
||||||
|
target: {
|
||||||
|
//name, id
|
||||||
|
},
|
||||||
|
edit: false,
|
||||||
|
addressId: null,
|
||||||
|
defaults: window.addaddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["activity"]),
|
||||||
|
selectType: {
|
||||||
|
get() {
|
||||||
|
return this.selected.type;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.type = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputName: {
|
||||||
|
get() {
|
||||||
|
return this.selected.name;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.name = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputEmail: {
|
||||||
|
get() {
|
||||||
|
return this.selected.email;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.email = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputPhonenumber1: {
|
||||||
|
get() {
|
||||||
|
return this.selected.phonenumber1;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.phonenumber1 = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputPhonenumber2: {
|
||||||
|
get() {
|
||||||
|
return this.selected.phonenumber2;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selected.phonenumber2 = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasPhonenumber1() {
|
||||||
|
return (
|
||||||
|
this.selected.phonenumber1 !== null &&
|
||||||
|
this.selected.phonenumber1 !== ""
|
||||||
|
);
|
||||||
|
},
|
||||||
|
showAddAddress() {
|
||||||
|
let cond = false;
|
||||||
|
if (this.selected.type) {
|
||||||
|
if (this.selected.type.addressRequired !== "never") {
|
||||||
|
cond = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
return cond;
|
||||||
}
|
},
|
||||||
|
showContactData() {
|
||||||
|
let cond = false;
|
||||||
|
if (this.selected.type) {
|
||||||
|
if (this.selected.type.contactData !== "never") {
|
||||||
|
cond = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cond;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
submitNewAddress(payload) {
|
mounted() {
|
||||||
this.selected.addressId = payload.addressId;
|
this.getLocationTypesList();
|
||||||
this.addAddress.context.addressId = payload.addressId;
|
},
|
||||||
this.addAddress.context.edit = true;
|
methods: {
|
||||||
|
localizeString,
|
||||||
|
checkForm() {
|
||||||
|
let cond = true;
|
||||||
|
this.errors = [];
|
||||||
|
if (!this.selected.type) {
|
||||||
|
this.errors.push("Type de localisation requis");
|
||||||
|
cond = false;
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
this.selected.type.addressRequired === "required" &&
|
||||||
|
!this.selected.addressId
|
||||||
|
) {
|
||||||
|
this.errors.push("Adresse requise");
|
||||||
|
cond = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.selected.type.contactData === "required" &&
|
||||||
|
!this.selected.phonenumber1
|
||||||
|
) {
|
||||||
|
this.errors.push("Numéro de téléphone requis");
|
||||||
|
cond = false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.selected.type.contactData === "required" &&
|
||||||
|
!this.selected.email
|
||||||
|
) {
|
||||||
|
this.errors.push("Adresse email requise");
|
||||||
|
cond = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cond;
|
||||||
|
},
|
||||||
|
getLocationTypesList() {
|
||||||
|
getLocationTypes().then((results) => {
|
||||||
|
this.locationTypes = results.filter(
|
||||||
|
(t) => t.availableForUsers === true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openModal() {
|
||||||
|
this.modal.showModal = true;
|
||||||
|
},
|
||||||
|
saveNewLocation() {
|
||||||
|
if (this.checkForm()) {
|
||||||
|
let body = {
|
||||||
|
type: "location",
|
||||||
|
name: this.selected.name,
|
||||||
|
locationType: {
|
||||||
|
id: this.selected.type.id,
|
||||||
|
type: "location-type",
|
||||||
|
},
|
||||||
|
phonenumber1: this.selected.phonenumber1,
|
||||||
|
phonenumber2: this.selected.phonenumber2,
|
||||||
|
email: this.selected.email,
|
||||||
|
};
|
||||||
|
if (this.selected.addressId) {
|
||||||
|
body = Object.assign(body, {
|
||||||
|
address: {
|
||||||
|
id: this.selected.addressId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
makeFetch("POST", "/api/1.0/main/location.json", body)
|
||||||
|
.then((response) => {
|
||||||
|
this.$store.dispatch("addAvailableLocationGroup", {
|
||||||
|
locationGroup: "Localisations nouvellement créées",
|
||||||
|
locations: [response],
|
||||||
|
});
|
||||||
|
this.$store.dispatch("updateLocation", response);
|
||||||
|
this.modal.showModal = false;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.name === "ValidationException") {
|
||||||
|
for (let v of error.violations) {
|
||||||
|
this.errors.push(v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.errors.push("An error occurred");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitNewAddress(payload) {
|
||||||
|
this.selected.addressId = payload.addressId;
|
||||||
|
this.addAddress.context.addressId = payload.addressId;
|
||||||
|
this.addAddress.context.edit = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,98 +1,103 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#social-issues-acc">
|
<teleport to="#social-issues-acc">
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label :class="socialIssuesClassList">{{
|
<label :class="socialIssuesClassList">{{
|
||||||
trans(ACTIVITY_SOCIAL_ISSUES)
|
trans(ACTIVITY_SOCIAL_ISSUES)
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<check-social-issue
|
<check-social-issue
|
||||||
v-for="issue in socialIssuesList"
|
v-for="issue in socialIssuesList"
|
||||||
:key="issue.id"
|
:key="issue.id"
|
||||||
:issue="issue"
|
:issue="issue"
|
||||||
:selection="socialIssuesSelected"
|
:selection="socialIssuesSelected"
|
||||||
@updateSelected="updateIssuesSelected"
|
@updateSelected="updateIssuesSelected"
|
||||||
>
|
>
|
||||||
</check-social-issue>
|
</check-social-issue>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="otherIssues"
|
name="otherIssues"
|
||||||
label="text"
|
label="text"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
open-direction="bottom"
|
open-direction="bottom"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
:preserve-search="false"
|
:preserve-search="false"
|
||||||
:reset-after="true"
|
:reset-after="true"
|
||||||
:hide-selected="true"
|
:hide-selected="true"
|
||||||
:taggable="false"
|
:taggable="false"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
:show-labels="false"
|
:show-labels="false"
|
||||||
:loading="issueIsLoading"
|
:loading="issueIsLoading"
|
||||||
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
:placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
|
||||||
:options="socialIssuesOther"
|
:options="socialIssuesOther"
|
||||||
@select="addIssueInList"
|
@select="addIssueInList"
|
||||||
>
|
>
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3 row">
|
|
||||||
<div class="col-4">
|
|
||||||
<label :class="socialActionsClassList">{{
|
|
||||||
trans(ACTIVITY_SOCIAL_ACTIONS)
|
|
||||||
}}</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<div v-if="actionIsLoading === true">
|
|
||||||
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span
|
<div class="mb-3 row">
|
||||||
v-else-if="socialIssuesSelected.length === 0"
|
<div class="col-4">
|
||||||
class="inline-choice chill-no-data-statement mt-3"
|
<label :class="socialActionsClassList">{{
|
||||||
>
|
trans(ACTIVITY_SOCIAL_ACTIONS)
|
||||||
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
}}</label>
|
||||||
</span>
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<div v-if="actionIsLoading === true">
|
||||||
|
<i
|
||||||
|
class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template
|
<span
|
||||||
v-else-if="
|
v-else-if="socialIssuesSelected.length === 0"
|
||||||
socialActionsList.length > 0 &&
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
(socialIssuesSelected.length || socialActionsSelected.length)
|
>
|
||||||
"
|
{{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
|
||||||
>
|
</span>
|
||||||
<div
|
|
||||||
id="actionsList"
|
|
||||||
v-for="group in socialActionsList"
|
|
||||||
:key="group.issue"
|
|
||||||
>
|
|
||||||
<span class="badge bg-chill-l-gray text-dark">{{
|
|
||||||
group.issue
|
|
||||||
}}</span>
|
|
||||||
<check-social-action
|
|
||||||
v-for="action in group.actions"
|
|
||||||
:key="action.id"
|
|
||||||
:action="action"
|
|
||||||
:selection="socialActionsSelected"
|
|
||||||
@updateSelected="updateActionsSelected"
|
|
||||||
>
|
|
||||||
</check-social-action>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<span
|
<template
|
||||||
v-else-if="actionAreLoaded && socialActionsList.length === 0"
|
v-else-if="
|
||||||
class="inline-choice chill-no-data-statement mt-3"
|
socialActionsList.length > 0 &&
|
||||||
>
|
(socialIssuesSelected.length ||
|
||||||
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
socialActionsSelected.length)
|
||||||
</span>
|
"
|
||||||
</div>
|
>
|
||||||
</div>
|
<div
|
||||||
</teleport>
|
id="actionsList"
|
||||||
|
v-for="group in socialActionsList"
|
||||||
|
:key="group.issue"
|
||||||
|
>
|
||||||
|
<span class="badge bg-chill-l-gray text-dark">{{
|
||||||
|
group.issue
|
||||||
|
}}</span>
|
||||||
|
<check-social-action
|
||||||
|
v-for="action in group.actions"
|
||||||
|
:key="action.id"
|
||||||
|
:action="action"
|
||||||
|
:selection="socialActionsSelected"
|
||||||
|
@updateSelected="updateActionsSelected"
|
||||||
|
>
|
||||||
|
</check-social-action>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-else-if="
|
||||||
|
actionAreLoaded && socialActionsList.length === 0
|
||||||
|
"
|
||||||
|
class="inline-choice chill-no-data-statement mt-3"
|
||||||
|
>
|
||||||
|
{{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -101,153 +106,154 @@ import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
|
|||||||
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
|
||||||
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
|
||||||
import {
|
import {
|
||||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||||
ACTIVITY_SOCIAL_ACTIONS,
|
ACTIVITY_SOCIAL_ACTIONS,
|
||||||
ACTIVITY_SOCIAL_ISSUES,
|
ACTIVITY_SOCIAL_ISSUES,
|
||||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||||
trans,
|
trans,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SocialIssuesAcc",
|
name: "SocialIssuesAcc",
|
||||||
components: {
|
components: {
|
||||||
CheckSocialIssue,
|
CheckSocialIssue,
|
||||||
CheckSocialAction,
|
CheckSocialAction,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
trans,
|
|
||||||
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
|
||||||
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
|
||||||
ACTIVITY_SOCIAL_ACTIONS,
|
|
||||||
ACTIVITY_SOCIAL_ISSUES,
|
|
||||||
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
issueIsLoading: false,
|
|
||||||
actionIsLoading: false,
|
|
||||||
actionAreLoaded: false,
|
|
||||||
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
|
||||||
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
socialIssuesList() {
|
|
||||||
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
|
||||||
},
|
},
|
||||||
socialIssuesSelected() {
|
setup() {
|
||||||
return this.$store.state.activity.socialIssues;
|
return {
|
||||||
|
trans,
|
||||||
|
ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
|
||||||
|
ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
|
||||||
|
ACTIVITY_SOCIAL_ACTIONS,
|
||||||
|
ACTIVITY_SOCIAL_ISSUES,
|
||||||
|
ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
socialIssuesOther() {
|
data() {
|
||||||
return this.$store.state.socialIssuesOther;
|
return {
|
||||||
|
issueIsLoading: false,
|
||||||
|
actionIsLoading: false,
|
||||||
|
actionAreLoaded: false,
|
||||||
|
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
||||||
|
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
socialActionsList() {
|
computed: {
|
||||||
return this.$store.getters.socialActionsListSorted;
|
socialIssuesList() {
|
||||||
|
return this.$store.state.activity.accompanyingPeriod.socialIssues;
|
||||||
|
},
|
||||||
|
socialIssuesSelected() {
|
||||||
|
return this.$store.state.activity.socialIssues;
|
||||||
|
},
|
||||||
|
socialIssuesOther() {
|
||||||
|
return this.$store.state.socialIssuesOther;
|
||||||
|
},
|
||||||
|
socialActionsList() {
|
||||||
|
return this.$store.getters.socialActionsListSorted;
|
||||||
|
},
|
||||||
|
socialActionsSelected() {
|
||||||
|
return this.$store.state.activity.socialActions;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
socialActionsSelected() {
|
mounted() {
|
||||||
return this.$store.state.activity.socialActions;
|
/* Load other issues in multiselect */
|
||||||
|
this.issueIsLoading = true;
|
||||||
|
this.actionAreLoaded = false;
|
||||||
|
|
||||||
|
getSocialIssues().then((response) => {
|
||||||
|
/* Add issues to the store */
|
||||||
|
this.$store.commit("updateIssuesOther", response);
|
||||||
|
|
||||||
|
/* Add in list the issues already associated (if not yet listed) */
|
||||||
|
this.socialIssuesSelected.forEach((issue) => {
|
||||||
|
if (
|
||||||
|
this.socialIssuesList.filter((i) => i.id === issue.id)
|
||||||
|
.length !== 1
|
||||||
|
) {
|
||||||
|
this.$store.commit("addIssueInList", issue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Remove from multiselect the issues that are not yet in the checkbox list */
|
||||||
|
this.socialIssuesList.forEach((issue) => {
|
||||||
|
this.$store.commit("removeIssueInOther", issue);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Filter issues */
|
||||||
|
this.$store.commit("filterList", "issues");
|
||||||
|
|
||||||
|
/* Add in list the actions already associated (if not yet listed) */
|
||||||
|
this.socialActionsSelected.forEach((action) => {
|
||||||
|
this.$store.commit("addActionInList", action);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Filter actions */
|
||||||
|
this.$store.commit("filterList", "actions");
|
||||||
|
|
||||||
|
this.issueIsLoading = false;
|
||||||
|
this.actionAreLoaded = true;
|
||||||
|
this.updateActionsList();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
methods: {
|
||||||
mounted() {
|
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||||
/* Load other issues in multiselect */
|
|
||||||
this.issueIsLoading = true;
|
|
||||||
this.actionAreLoaded = false;
|
|
||||||
|
|
||||||
getSocialIssues().then((response) => {
|
|
||||||
/* Add issues to the store */
|
|
||||||
this.$store.commit("updateIssuesOther", response);
|
|
||||||
|
|
||||||
/* Add in list the issues already associated (if not yet listed) */
|
|
||||||
this.socialIssuesSelected.forEach((issue) => {
|
|
||||||
if (
|
|
||||||
this.socialIssuesList.filter((i) => i.id === issue.id).length !== 1
|
|
||||||
) {
|
|
||||||
this.$store.commit("addIssueInList", issue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Remove from multiselect the issues that are not yet in the checkbox list */
|
|
||||||
this.socialIssuesList.forEach((issue) => {
|
|
||||||
this.$store.commit("removeIssueInOther", issue);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Filter issues */
|
|
||||||
this.$store.commit("filterList", "issues");
|
|
||||||
|
|
||||||
/* Add in list the actions already associated (if not yet listed) */
|
|
||||||
this.socialActionsSelected.forEach((action) => {
|
|
||||||
this.$store.commit("addActionInList", action);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Filter actions */
|
|
||||||
this.$store.commit("filterList", "actions");
|
|
||||||
|
|
||||||
this.issueIsLoading = false;
|
|
||||||
this.actionAreLoaded = true;
|
|
||||||
this.updateActionsList();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
|
||||||
remove it from multiselect, and add socialActions concerned
|
remove it from multiselect, and add socialActions concerned
|
||||||
*/
|
*/
|
||||||
addIssueInList(value) {
|
addIssueInList(value) {
|
||||||
//console.log('addIssueInList', value);
|
//console.log('addIssueInList', value);
|
||||||
this.$store.commit("addIssueInList", value);
|
this.$store.commit("addIssueInList", value);
|
||||||
this.$store.commit("removeIssueInOther", value);
|
this.$store.commit("removeIssueInOther", value);
|
||||||
this.$store.dispatch("addIssueSelected", value);
|
this.$store.dispatch("addIssueSelected", value);
|
||||||
this.updateActionsList();
|
this.updateActionsList();
|
||||||
},
|
},
|
||||||
/* Update value for selected issues checkboxes
|
/* Update value for selected issues checkboxes
|
||||||
*/
|
*/
|
||||||
updateIssuesSelected(issues) {
|
updateIssuesSelected(issues) {
|
||||||
//console.log('updateIssuesSelected', issues);
|
//console.log('updateIssuesSelected', issues);
|
||||||
this.$store.dispatch("updateIssuesSelected", issues);
|
this.$store.dispatch("updateIssuesSelected", issues);
|
||||||
this.updateActionsList();
|
this.updateActionsList();
|
||||||
},
|
},
|
||||||
/* Update value for selected actions checkboxes
|
/* Update value for selected actions checkboxes
|
||||||
*/
|
*/
|
||||||
updateActionsSelected(actions) {
|
updateActionsSelected(actions) {
|
||||||
//console.log('updateActionsSelected', actions);
|
//console.log('updateActionsSelected', actions);
|
||||||
this.$store.dispatch("updateActionsSelected", actions);
|
this.$store.dispatch("updateActionsSelected", actions);
|
||||||
},
|
},
|
||||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||||
to get social actions concerned
|
to get social actions concerned
|
||||||
*/
|
*/
|
||||||
updateActionsList() {
|
updateActionsList() {
|
||||||
this.resetActionsList();
|
this.resetActionsList();
|
||||||
this.socialIssuesSelected.forEach((item) => {
|
this.socialIssuesSelected.forEach((item) => {
|
||||||
this.actionIsLoading = true;
|
this.actionIsLoading = true;
|
||||||
getSocialActionByIssue(item.id).then(
|
getSocialActionByIssue(item.id).then(
|
||||||
(actions) =>
|
(actions) =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
actions.results.forEach((action) => {
|
actions.results.forEach((action) => {
|
||||||
this.$store.commit("addActionInList", action);
|
this.$store.commit("addActionInList", action);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.$store.commit("filterList", "actions");
|
this.$store.commit("filterList", "actions");
|
||||||
|
|
||||||
this.actionIsLoading = false;
|
this.actionIsLoading = false;
|
||||||
this.actionAreLoaded = true;
|
this.actionAreLoaded = true;
|
||||||
resolve();
|
resolve();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}, this);
|
}, this);
|
||||||
|
},
|
||||||
|
/* Reset socialActions List: flush list and restore selected actions
|
||||||
|
*/
|
||||||
|
resetActionsList() {
|
||||||
|
this.$store.commit("resetActionsList");
|
||||||
|
this.actionAreLoaded = false;
|
||||||
|
this.socialActionsSelected.forEach((item) => {
|
||||||
|
this.$store.commit("addActionInList", item);
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
/* Reset socialActions List: flush list and restore selected actions
|
|
||||||
*/
|
|
||||||
resetActionsList() {
|
|
||||||
this.$store.commit("resetActionsList");
|
|
||||||
this.actionAreLoaded = false;
|
|
||||||
this.socialActionsSelected.forEach((item) => {
|
|
||||||
this.$store.commit("addActionInList", item);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -257,18 +263,18 @@ export default {
|
|||||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
|
|
||||||
span.multiselect__single {
|
span.multiselect__single {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actionsList {
|
#actionsList {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
background-color: whitesmoke;
|
background-color: whitesmoke;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.badge {
|
span.badge {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
@include badge_social($social-issue-color);
|
@include badge_social($social-issue-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,38 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="inline-choice">
|
<span class="inline-choice">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
name="action"
|
name="action"
|
||||||
:id="action.id"
|
:id="action.id"
|
||||||
:value="action"
|
:value="action"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" :for="action.id">
|
<label class="form-check-label" :for="action.id">
|
||||||
<span class="badge bg-light text-dark" :title="action.text">{{
|
<span class="badge bg-light text-dark" :title="action.text">{{
|
||||||
action.text
|
action.text
|
||||||
}}</span>
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "CheckSocialAction",
|
name: "CheckSocialAction",
|
||||||
props: ["action", "selection"],
|
props: ["action", "selection"],
|
||||||
emits: ["updateSelected"],
|
emits: ["updateSelected"],
|
||||||
computed: {
|
computed: {
|
||||||
selected: {
|
selected: {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit("updateSelected", value);
|
this.$emit("updateSelected", value);
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.selection;
|
return this.selection;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -41,13 +41,13 @@ export default {
|
|||||||
@import "ChillPersonAssets/chill/scss/mixins";
|
@import "ChillPersonAssets/chill/scss/mixins";
|
||||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
span.badge {
|
span.badge {
|
||||||
@include badge_social($social-action-color);
|
@include badge_social($social-action-color);
|
||||||
font-size: 95%;
|
font-size: 95%;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
max-width: 100%; /* Adjust as needed */
|
max-width: 100%; /* Adjust as needed */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,36 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="inline-choice">
|
<span class="inline-choice">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
name="issue"
|
name="issue"
|
||||||
:id="issue.id"
|
:id="issue.id"
|
||||||
:value="issue"
|
:value="issue"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" :for="issue.id">
|
<label class="form-check-label" :for="issue.id">
|
||||||
<span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
|
<span class="badge bg-chill-l-gray text-dark">{{
|
||||||
</label>
|
issue.text
|
||||||
</div>
|
}}</span>
|
||||||
</span>
|
</label>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "CheckSocialIssue",
|
name: "CheckSocialIssue",
|
||||||
props: ["issue", "selection"],
|
props: ["issue", "selection"],
|
||||||
emits: ["updateSelected"],
|
emits: ["updateSelected"],
|
||||||
computed: {
|
computed: {
|
||||||
selected: {
|
selected: {
|
||||||
set(value) {
|
set(value) {
|
||||||
this.$emit("updateSelected", value);
|
this.$emit("updateSelected", value);
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
return this.selection;
|
return this.selection;
|
||||||
},
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -39,9 +41,9 @@ export default {
|
|||||||
@import "ChillPersonAssets/chill/scss/mixins";
|
@import "ChillPersonAssets/chill/scss/mixins";
|
||||||
@import "ChillMainAssets/chill/scss/chill_variables";
|
@import "ChillMainAssets/chill/scss/chill_variables";
|
||||||
span.badge {
|
span.badge {
|
||||||
@include badge_social($social-issue-color);
|
@include badge_social($social-issue-color);
|
||||||
font-size: 95%;
|
font-size: 95%;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -25,6 +25,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
|
|||||||
$config = $this->processConfiguration($configuration, $configs);
|
$config = $this->processConfiguration($configuration, $configs);
|
||||||
|
|
||||||
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
|
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
|
||||||
|
$container->setParameter('chill_aside_activity.show_concerned_persons_count', $config['show_concerned_persons_count']);
|
||||||
|
|
||||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
||||||
$loader->load('services.yaml');
|
$loader->load('services.yaml');
|
||||||
|
@@ -141,6 +141,11 @@ class Configuration implements ConfigurationInterface
|
|||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
|
->end()
|
||||||
|
->booleanNode('show_concerned_persons_count')
|
||||||
|
->defaultTrue()
|
||||||
|
->info('Show the concerned persons count field in aside activity forms and views')
|
||||||
|
->end()
|
||||||
->end();
|
->end();
|
||||||
|
|
||||||
return $treeBuilder;
|
return $treeBuilder;
|
||||||
|
@@ -62,6 +62,10 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||||
private User $updatedBy;
|
private User $updatedBy;
|
||||||
|
|
||||||
|
#[Assert\GreaterThanOrEqual(0)]
|
||||||
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)]
|
||||||
|
private ?int $concernedPersonsCount = null;
|
||||||
|
|
||||||
public function getAgent(): ?User
|
public function getAgent(): ?User
|
||||||
{
|
{
|
||||||
return $this->agent;
|
return $this->agent;
|
||||||
@@ -186,4 +190,16 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getConcernedPersonsCount(): ?int
|
||||||
|
{
|
||||||
|
return $this->concernedPersonsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setConcernedPersonsCount(?int $concernedPersonsCount): self
|
||||||
|
{
|
||||||
|
$this->concernedPersonsCount = $concernedPersonsCount;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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\AsideActivityBundle\Export\Aggregator;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\MainBundle\Export\AggregatorInterface;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class ByConcernedPersonsCountAggregator implements AggregatorInterface
|
||||||
|
{
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void
|
||||||
|
{
|
||||||
|
$qb->addSelect('aside.concernedPersonsCount AS by_concerned_persons_count_aggregator')
|
||||||
|
->addGroupBy('by_concerned_persons_count_aggregator');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn(): string
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder): void
|
||||||
|
{
|
||||||
|
// No form needed
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNormalizationVersion(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalizeFormData(array $formData): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormDefaultData(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels($key, array $values, $data): callable
|
||||||
|
{
|
||||||
|
return function ($value): string {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'export.aggregator.Concerned persons count';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value) {
|
||||||
|
return 'export.aggregator.No concerned persons count specified';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryKeys($data): array
|
||||||
|
{
|
||||||
|
return ['by_concerned_persons_count_aggregator'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.aggregator.Group by concerned persons count';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,116 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Export;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
|
||||||
|
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
||||||
|
use Chill\MainBundle\Export\ExportInterface;
|
||||||
|
use Chill\MainBundle\Export\FormatterInterface;
|
||||||
|
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class SumConcernedPersonsCountAsideActivity implements ExportInterface, GroupedExportInterface
|
||||||
|
{
|
||||||
|
public function __construct(private readonly AsideActivityRepository $repository) {}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder) {}
|
||||||
|
|
||||||
|
public function getNormalizationVersion(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalizeFormData(array $formData): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormDefaultData(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllowedFormattersTypes(): array
|
||||||
|
{
|
||||||
|
return [FormatterInterface::TYPE_TABULAR];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'export.Sum concerned persons count for aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroup(): string
|
||||||
|
{
|
||||||
|
return 'export.Exports of aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels($key, array $values, $data)
|
||||||
|
{
|
||||||
|
if ('export_sum_concerned_persons_count' !== $key) {
|
||||||
|
throw new \LogicException("the key {$key} is not used by this export");
|
||||||
|
}
|
||||||
|
|
||||||
|
$labels = array_combine($values, $values);
|
||||||
|
$labels['_header'] = $this->getTitle();
|
||||||
|
|
||||||
|
return static fn ($value) => $labels[$value];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryKeys($data): array
|
||||||
|
{
|
||||||
|
return ['export_sum_concerned_persons_count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array
|
||||||
|
{
|
||||||
|
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.Sum concerned persons count for aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder
|
||||||
|
{
|
||||||
|
$qb = $this->repository->createQueryBuilder('aside');
|
||||||
|
|
||||||
|
$qb->select('SUM(COALESCE(aside.concernedPersonsCount, 0)) as export_sum_concerned_persons_count');
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requiredRole(): string
|
||||||
|
{
|
||||||
|
return AsideActivityVoter::STATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsModifiers(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Declarations::ASIDE_ACTIVITY_TYPE,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
|||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\Form\FormEvent;
|
use Symfony\Component\Form\FormEvent;
|
||||||
use Symfony\Component\Form\FormEvents;
|
use Symfony\Component\Form\FormEvents;
|
||||||
@@ -29,11 +30,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
final class AsideActivityFormType extends AbstractType
|
final class AsideActivityFormType extends AbstractType
|
||||||
{
|
{
|
||||||
private readonly array $timeChoices;
|
private readonly array $timeChoices;
|
||||||
|
private readonly bool $showConcernedPersonsCount;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ParameterBagInterface $parameterBag,
|
ParameterBagInterface $parameterBag,
|
||||||
) {
|
) {
|
||||||
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
|
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
|
||||||
|
$this->showConcernedPersonsCount = $parameterBag->get('chill_aside_activity.show_concerned_persons_count');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
@@ -76,6 +79,16 @@ final class AsideActivityFormType extends AbstractType
|
|||||||
->add('location', PickUserLocationType::class)
|
->add('location', PickUserLocationType::class)
|
||||||
;
|
;
|
||||||
|
|
||||||
|
if ($this->showConcernedPersonsCount) {
|
||||||
|
$builder->add('concernedPersonsCount', IntegerType::class, [
|
||||||
|
'label' => 'Concerned persons count',
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
'min' => 0,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (['duration'] as $fieldName) {
|
foreach (['duration'] as $fieldName) {
|
||||||
$builder->get($fieldName)
|
$builder->get($fieldName)
|
||||||
->addModelTransformer($durationTimeTransformer);
|
->addModelTransformer($durationTimeTransformer);
|
||||||
|
@@ -42,6 +42,11 @@
|
|||||||
{%- if entity.location.name is defined -%}
|
{%- if entity.location.name is defined -%}
|
||||||
<div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div>
|
<div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if entity.concernedPersonsCount > 0 -%}
|
||||||
|
<div><i class="fa fa-fw fa-user"></i>{{ entity.concernedPersonsCount }}</div>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="item-col" style="justify-content: flex-end;">
|
<div class="item-col" style="justify-content: flex-end;">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
@@ -38,6 +38,9 @@
|
|||||||
<dt class="inline">{{ 'Duration'|trans }}</dt>
|
<dt class="inline">{{ 'Duration'|trans }}</dt>
|
||||||
<dd>{{ entity.duration|date('H:i') }}</dd>
|
<dd>{{ entity.duration|date('H:i') }}</dd>
|
||||||
|
|
||||||
|
<dt class="inline">{{ 'Concerned persons count'|trans }}</dt>
|
||||||
|
<dd>{{ entity.concernedPersonsCount }}</dd>
|
||||||
|
|
||||||
<dt class="inline">{{ 'Remark'|trans }}</dt>
|
<dt class="inline">{{ 'Remark'|trans }}</dt>
|
||||||
{%- if entity.note is empty -%}
|
{%- if entity.note is empty -%}
|
||||||
<dd>
|
<dd>
|
||||||
@@ -55,5 +58,6 @@
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -20,6 +20,10 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: chill.export, alias: 'avg_aside_activity_duration' }
|
- { name: chill.export, alias: 'avg_aside_activity_duration' }
|
||||||
|
|
||||||
|
Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity:
|
||||||
|
tags:
|
||||||
|
- { name: chill.export, alias: 'sum_aside_activity_concerned_persons_count' }
|
||||||
|
|
||||||
## Filters
|
## Filters
|
||||||
chill.aside_activity.export.date_filter:
|
chill.aside_activity.export.date_filter:
|
||||||
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
|
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
|
||||||
@@ -70,3 +74,7 @@ services:
|
|||||||
Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
|
Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
|
||||||
tags:
|
tags:
|
||||||
- { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
|
- { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
|
||||||
|
|
||||||
|
Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator:
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_aggregator, alias: 'aside_activity_concerned_persons_count_aggregator' }
|
||||||
|
@@ -9,25 +9,25 @@ 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\Migrations\Ticket;
|
namespace Chill\Migrations\AsideActivity;
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
final class Version20250711115128 extends AbstractMigration
|
final class Version20251006113048 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return 'Add deleted column to comment table to support soft deletion of comments';
|
return 'Add concernedPersonsCount property to AsideActivity entity';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
$this->addSql('ALTER TABLE chill_ticket.comment ADD deleted BOOLEAN DEFAULT false NOT NULL');
|
$this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD concernedPersonsCount INT DEFAULT NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
$this->addSql('ALTER TABLE chill_ticket.comment DROP deleted');
|
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP concernedPersonsCount');
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -27,6 +27,7 @@ Emergency: Urgent
|
|||||||
by: "Par "
|
by: "Par "
|
||||||
location: Lieu
|
location: Lieu
|
||||||
Asideactivity location: Localisation de l'activité
|
Asideactivity location: Localisation de l'activité
|
||||||
|
Concerned persons count: Nombre d'usager concernés
|
||||||
|
|
||||||
# Crud
|
# Crud
|
||||||
crud:
|
crud:
|
||||||
@@ -190,6 +191,7 @@ export:
|
|||||||
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
|
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
|
||||||
Average aside activities duration: Durée moyenne des activités annexes
|
Average aside activities duration: Durée moyenne des activités annexes
|
||||||
Sum aside activities duration: Durée des activités annexes
|
Sum aside activities duration: Durée des activités annexes
|
||||||
|
Sum concerned persons count for aside activities: Nombre d'usager concernés par les activités annexes
|
||||||
filter:
|
filter:
|
||||||
Filter by aside activity date: Filtrer les activités annexes par date
|
Filter by aside activity date: Filtrer les activités annexes par date
|
||||||
Filter by aside activity type: Filtrer les activités annexes par type d'activité
|
Filter by aside activity type: Filtrer les activités annexes par type d'activité
|
||||||
@@ -210,6 +212,8 @@ export:
|
|||||||
'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%"
|
'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%"
|
||||||
aggregator:
|
aggregator:
|
||||||
Group by aside activity type: Grouper les activités annexes par type d'activité
|
Group by aside activity type: Grouper les activités annexes par type d'activité
|
||||||
|
Group by concerned persons count: Grouper les activités annexes par nombre d'usagers conernés
|
||||||
|
Concerned persons count: Nombre d'usagers concernés
|
||||||
Aside activity type: Type d'activité annexe
|
Aside activity type: Type d'activité annexe
|
||||||
by_user_job:
|
by_user_job:
|
||||||
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs
|
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs
|
||||||
|
@@ -1,74 +1,76 @@
|
|||||||
import { EventInput } from "@fullcalendar/core";
|
import { EventInput } from "@fullcalendar/core";
|
||||||
import {
|
import {
|
||||||
DateTime,
|
DateTime,
|
||||||
Location,
|
Location,
|
||||||
User,
|
User,
|
||||||
UserAssociatedInterface,
|
UserAssociatedInterface,
|
||||||
} from "../../../ChillMainBundle/Resources/public/types";
|
} from "../../../ChillMainBundle/Resources/public/types";
|
||||||
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
|
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
|
||||||
|
|
||||||
export interface CalendarRange {
|
export interface CalendarRange {
|
||||||
id: number;
|
id: number;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
user: User;
|
user: User;
|
||||||
location: Location;
|
location: Location;
|
||||||
createdAt: DateTime;
|
createdAt: DateTime;
|
||||||
createdBy: User;
|
createdBy: User;
|
||||||
updatedAt: DateTime;
|
updatedAt: DateTime;
|
||||||
updatedBy: User;
|
updatedBy: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarRangeCreate {
|
export interface CalendarRangeCreate {
|
||||||
user: UserAssociatedInterface;
|
user: UserAssociatedInterface;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
location: Location;
|
location: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarRangeEdit {
|
export interface CalendarRangeEdit {
|
||||||
startDate?: DateTime;
|
startDate?: DateTime;
|
||||||
endDate?: DateTime;
|
endDate?: DateTime;
|
||||||
location?: Location;
|
location?: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Calendar {
|
export interface Calendar {
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarLight {
|
export interface CalendarLight {
|
||||||
id: number;
|
id: number;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
mainUser: User;
|
mainUser: User;
|
||||||
persons: Person[];
|
persons: Person[];
|
||||||
status: "valid" | "moved" | "canceled";
|
status: "valid" | "moved" | "canceled";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarRemote {
|
export interface CalendarRemote {
|
||||||
id: number;
|
id: number;
|
||||||
endDate: DateTime;
|
endDate: DateTime;
|
||||||
startDate: DateTime;
|
startDate: DateTime;
|
||||||
title: string;
|
title: string;
|
||||||
isAllDay: boolean;
|
isAllDay: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventInputCalendarRange = EventInput & {
|
export type EventInputCalendarRange = EventInput & {
|
||||||
id: string;
|
id: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
userLabel: string;
|
userLabel: string;
|
||||||
calendarRangeId: number;
|
calendarRangeId: number;
|
||||||
locationId: number;
|
locationId: number;
|
||||||
locationName: string;
|
locationName: string;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
is: "range";
|
is: "range";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isEventInputCalendarRange(
|
export function isEventInputCalendarRange(
|
||||||
toBeDetermined: EventInputCalendarRange | EventInput,
|
toBeDetermined: EventInputCalendarRange | EventInput,
|
||||||
): toBeDetermined is EventInputCalendarRange {
|
): toBeDetermined is EventInputCalendarRange {
|
||||||
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
|
return (
|
||||||
|
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
@@ -1,146 +1,166 @@
|
|||||||
<template>
|
<template>
|
||||||
<teleport to="#mainUser">
|
<teleport to="#mainUser">
|
||||||
<h2 class="chill-red">Utilisateur principal</h2>
|
<h2 class="chill-red">Utilisateur principal</h2>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="null !== this.$store.getters.getMainUser">
|
<div v-if="null !== this.$store.getters.getMainUser">
|
||||||
<calendar-active :user="this.$store.getters.getMainUser" />
|
<calendar-active :user="this.$store.getters.getMainUser" />
|
||||||
|
</div>
|
||||||
|
<pick-entity
|
||||||
|
:multiple="false"
|
||||||
|
:types="['user']"
|
||||||
|
:uniqid="'main_user_calendar'"
|
||||||
|
:picked="
|
||||||
|
null !== this.$store.getters.getMainUser
|
||||||
|
? [this.$store.getters.getMainUser]
|
||||||
|
: []
|
||||||
|
"
|
||||||
|
:removable-if-set="false"
|
||||||
|
:display-picked="false"
|
||||||
|
:suggested="this.suggestedUsers"
|
||||||
|
:label="'main_user'"
|
||||||
|
@add-new-entity="setMainUser"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pick-entity
|
</teleport>
|
||||||
:multiple="false"
|
|
||||||
:types="['user']"
|
|
||||||
:uniqid="'main_user_calendar'"
|
|
||||||
:picked="
|
|
||||||
null !== this.$store.getters.getMainUser
|
|
||||||
? [this.$store.getters.getMainUser]
|
|
||||||
: []
|
|
||||||
"
|
|
||||||
:removable-if-set="false"
|
|
||||||
:display-picked="false"
|
|
||||||
:suggested="this.suggestedUsers"
|
|
||||||
:label="'main_user'"
|
|
||||||
@add-new-entity="setMainUser"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</teleport>
|
|
||||||
|
|
||||||
<concerned-groups />
|
<concerned-groups />
|
||||||
|
|
||||||
<teleport to="#schedule">
|
<teleport to="#schedule">
|
||||||
<div class="row mb-3" v-if="activity.startDate !== null">
|
<div class="row mb-3" v-if="activity.startDate !== null">
|
||||||
<label class="col-form-label col-sm-4">Date</label>
|
<label class="col-form-label col-sm-4">Date</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
{{ $d(activity.startDate, "long") }} -
|
{{ $d(activity.startDate, "long") }} -
|
||||||
{{ $d(activity.endDate, "hoursOnly") }}
|
{{ $d(activity.endDate, "hoursOnly") }}
|
||||||
<span v-if="activity.calendarRange === null"
|
<span v-if="activity.calendarRange === null"
|
||||||
>(Pas de plage de disponibilité sélectionnée)</span
|
>(Pas de plage de disponibilité sélectionnée)</span
|
||||||
|
>
|
||||||
|
<span v-else>(Une plage de disponibilité sélectionnée)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
|
||||||
|
<location />
|
||||||
|
|
||||||
|
<teleport to="#fullCalendar">
|
||||||
|
<div class="calendar-actives">
|
||||||
|
<template v-for="u in getActiveUsers" :key="u.id">
|
||||||
|
<calendar-active
|
||||||
|
:user="u"
|
||||||
|
:invite="this.$store.getters.getInviteForUser(u)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="display-options row justify-content-between"
|
||||||
|
style="margin-top: 1rem"
|
||||||
>
|
>
|
||||||
<span v-else>(Une plage de disponibilité sélectionnée)</span>
|
<div class="col-sm-9 col-xs-12">
|
||||||
</div>
|
<div class="input-group mb-3">
|
||||||
</div>
|
<label class="input-group-text" for="slotDuration"
|
||||||
</teleport>
|
>Durée des créneaux</label
|
||||||
|
>
|
||||||
<location />
|
<select
|
||||||
|
v-model="slotDuration"
|
||||||
<teleport to="#fullCalendar">
|
id="slotDuration"
|
||||||
<div class="calendar-actives">
|
class="form-select"
|
||||||
<template v-for="u in getActiveUsers" :key="u.id">
|
>
|
||||||
<calendar-active
|
<option value="00:05:00">5 minutes</option>
|
||||||
:user="u"
|
<option value="00:10:00">10 minutes</option>
|
||||||
:invite="this.$store.getters.getInviteForUser(u)"
|
<option value="00:15:00">15 minutes</option>
|
||||||
/>
|
<option value="00:30:00">30 minutes</option>
|
||||||
</template>
|
<option value="00:45:00">45 minutes</option>
|
||||||
</div>
|
<option value="00:60:00">60 minutes</option>
|
||||||
<div
|
</select>
|
||||||
class="display-options row justify-content-between"
|
<label class="input-group-text" for="slotMinTime">De</label>
|
||||||
style="margin-top: 1rem"
|
<select
|
||||||
>
|
v-model="slotMinTime"
|
||||||
<div class="col-sm-9 col-xs-12">
|
id="slotMinTime"
|
||||||
<div class="input-group mb-3">
|
class="form-select"
|
||||||
<label class="input-group-text" for="slotDuration"
|
>
|
||||||
>Durée des créneaux</label
|
<option value="00:00:00">0h</option>
|
||||||
>
|
<option value="01:00:00">1h</option>
|
||||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
<option value="02:00:00">2h</option>
|
||||||
<option value="00:05:00">5 minutes</option>
|
<option value="03:00:00">3h</option>
|
||||||
<option value="00:10:00">10 minutes</option>
|
<option value="04:00:00">4h</option>
|
||||||
<option value="00:15:00">15 minutes</option>
|
<option value="05:00:00">5h</option>
|
||||||
<option value="00:30:00">30 minutes</option>
|
<option value="06:00:00">6h</option>
|
||||||
</select>
|
<option value="07:00:00">7h</option>
|
||||||
<label class="input-group-text" for="slotMinTime">De</label>
|
<option value="08:00:00">8h</option>
|
||||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
<option value="09:00:00">9h</option>
|
||||||
<option value="00:00:00">0h</option>
|
<option value="10:00:00">10h</option>
|
||||||
<option value="01:00:00">1h</option>
|
<option value="11:00:00">11h</option>
|
||||||
<option value="02:00:00">2h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="03:00:00">3h</option>
|
</select>
|
||||||
<option value="04:00:00">4h</option>
|
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||||
<option value="05:00:00">5h</option>
|
<select
|
||||||
<option value="06:00:00">6h</option>
|
v-model="slotMaxTime"
|
||||||
<option value="07:00:00">7h</option>
|
id="slotMaxTime"
|
||||||
<option value="08:00:00">8h</option>
|
class="form-select"
|
||||||
<option value="09:00:00">9h</option>
|
>
|
||||||
<option value="10:00:00">10h</option>
|
<option value="12:00:00">12h</option>
|
||||||
<option value="11:00:00">11h</option>
|
<option value="13:00:00">13h</option>
|
||||||
<option value="12:00:00">12h</option>
|
<option value="14:00:00">14h</option>
|
||||||
</select>
|
<option value="15:00:00">15h</option>
|
||||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
<option value="16:00:00">16h</option>
|
||||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
<option value="17:00:00">17h</option>
|
||||||
<option value="12:00:00">12h</option>
|
<option value="18:00:00">18h</option>
|
||||||
<option value="13:00:00">13h</option>
|
<option value="19:00:00">19h</option>
|
||||||
<option value="14:00:00">14h</option>
|
<option value="20:00:00">20h</option>
|
||||||
<option value="15:00:00">15h</option>
|
<option value="21:00:00">21h</option>
|
||||||
<option value="16:00:00">16h</option>
|
<option value="22:00:00">22h</option>
|
||||||
<option value="17:00:00">17h</option>
|
<option value="23:00:00">23h</option>
|
||||||
<option value="18:00:00">18h</option>
|
<option value="23:59:59">24h</option>
|
||||||
<option value="19:00:00">19h</option>
|
</select>
|
||||||
<option value="20:00:00">20h</option>
|
</div>
|
||||||
<option value="21:00:00">21h</option>
|
</div>
|
||||||
<option value="22:00:00">22h</option>
|
<div class="col-sm-3 col-xs-12">
|
||||||
<option value="23:00:00">23h</option>
|
<div class="float-end">
|
||||||
<option value="23:59:59">24h</option>
|
<div class="form-check input-group">
|
||||||
</select>
|
<span class="input-group-text">
|
||||||
|
<input
|
||||||
|
id="showHideWE"
|
||||||
|
class="mt-0"
|
||||||
|
type="checkbox"
|
||||||
|
v-model="hideWeekends"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<label
|
||||||
|
for="showHideWE"
|
||||||
|
class="form-check-label input-group-text"
|
||||||
|
>Week-ends</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||||
<div class="col-sm-3 col-xs-12">
|
<template #eventContent="arg">
|
||||||
<div class="float-end">
|
<span>
|
||||||
<div class="form-check input-group">
|
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
||||||
<span class="input-group-text">
|
arg.event.title
|
||||||
<input
|
}}</b>
|
||||||
id="showHideWE"
|
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
||||||
class="mt-0"
|
>{{ arg.timeText }}
|
||||||
type="checkbox"
|
{{ arg.event.extendedProps.locationName }}
|
||||||
v-model="hideWeekends"
|
<small>{{
|
||||||
/>
|
arg.event.extendedProps.userLabel
|
||||||
</span>
|
}}</small></b
|
||||||
<label for="showHideWE" class="form-check-label input-group-text"
|
>
|
||||||
>Week-ends</label
|
<b v-else-if="arg.event.extendedProps.is === 'current'"
|
||||||
>
|
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||||
</div>
|
</b>
|
||||||
</div>
|
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
||||||
</div>
|
arg.event.title
|
||||||
</div>
|
}}</b>
|
||||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
<b v-else
|
||||||
<template #eventContent="arg">
|
>{{ arg.timeText }} {{ $t("current_selected") }}
|
||||||
<span>
|
</b>
|
||||||
<b v-if="arg.event.extendedProps.is === 'remote'">{{
|
</span>
|
||||||
arg.event.title
|
</template>
|
||||||
}}</b>
|
</FullCalendar>
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'range'"
|
</teleport>
|
||||||
>{{ arg.timeText }}
|
|
||||||
{{ arg.event.extendedProps.locationName }}
|
|
||||||
<small>{{ arg.event.extendedProps.userLabel }}</small></b
|
|
||||||
>
|
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'current'"
|
|
||||||
>{{ arg.timeText }} {{ $t("current_selected") }}
|
|
||||||
</b>
|
|
||||||
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
|
|
||||||
arg.event.title
|
|
||||||
}}</b>
|
|
||||||
<b v-else>{{ arg.timeText }} {{ $t("current_selected") }} </b>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</FullCalendar>
|
|
||||||
</teleport>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -157,210 +177,219 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
|||||||
import { mapGetters, mapState } from "vuex";
|
import { mapGetters, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
ConcernedGroups,
|
ConcernedGroups,
|
||||||
Location,
|
Location,
|
||||||
FullCalendar,
|
FullCalendar,
|
||||||
CalendarActive,
|
CalendarActive,
|
||||||
PickEntity,
|
PickEntity,
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
errorMsg: [],
|
|
||||||
showMyCalendar: false,
|
|
||||||
slotDuration: "00:05:00",
|
|
||||||
slotMinTime: "09:00:00",
|
|
||||||
slotMaxTime: "18:00:00",
|
|
||||||
hideWeekEnds: true,
|
|
||||||
previousUser: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters(["getMainUser"]),
|
|
||||||
...mapState(["activity"]),
|
|
||||||
events() {
|
|
||||||
return this.$store.getters.getEventSources;
|
|
||||||
},
|
},
|
||||||
calendarOptions() {
|
data() {
|
||||||
return {
|
return {
|
||||||
locale: frLocale,
|
errorMsg: [],
|
||||||
plugins: [
|
showMyCalendar: false,
|
||||||
dayGridPlugin,
|
slotDuration: "00:05:00",
|
||||||
interactionPlugin,
|
slotMinTime: "09:00:00",
|
||||||
timeGridPlugin,
|
slotMaxTime: "18:00:00",
|
||||||
dayGridPlugin,
|
hideWeekEnds: true,
|
||||||
listPlugin,
|
previousUser: [],
|
||||||
],
|
};
|
||||||
initialView: "timeGridWeek",
|
},
|
||||||
initialDate: this.$store.getters.getInitialDate,
|
computed: {
|
||||||
eventSources: this.events,
|
...mapGetters(["getMainUser"]),
|
||||||
selectable: true,
|
...mapState(["activity"]),
|
||||||
slotMinTime: this.slotMinTime,
|
events() {
|
||||||
slotMaxTime: this.slotMaxTime,
|
return this.$store.getters.getEventSources;
|
||||||
scrollTimeReset: false,
|
|
||||||
datesSet: this.onDatesSet,
|
|
||||||
select: this.onDateSelect,
|
|
||||||
eventChange: this.onEventChange,
|
|
||||||
eventClick: this.onEventClick,
|
|
||||||
selectMirror: true,
|
|
||||||
editable: true,
|
|
||||||
weekends: !this.hideWeekEnds,
|
|
||||||
headerToolbar: {
|
|
||||||
left: "prev,next today",
|
|
||||||
center: "title",
|
|
||||||
right: "timeGridWeek,timeGridDay,listWeek",
|
|
||||||
},
|
},
|
||||||
views: {
|
calendarOptions() {
|
||||||
timeGrid: {
|
return {
|
||||||
slotEventOverlap: false,
|
locale: frLocale,
|
||||||
slotDuration: this.slotDuration,
|
plugins: [
|
||||||
},
|
dayGridPlugin,
|
||||||
|
interactionPlugin,
|
||||||
|
timeGridPlugin,
|
||||||
|
dayGridPlugin,
|
||||||
|
listPlugin,
|
||||||
|
],
|
||||||
|
initialView: "timeGridWeek",
|
||||||
|
initialDate: this.$store.getters.getInitialDate,
|
||||||
|
eventSources: this.events,
|
||||||
|
selectable: true,
|
||||||
|
slotMinTime: this.slotMinTime,
|
||||||
|
slotMaxTime: this.slotMaxTime,
|
||||||
|
scrollTimeReset: false,
|
||||||
|
datesSet: this.onDatesSet,
|
||||||
|
select: this.onDateSelect,
|
||||||
|
eventChange: this.onEventChange,
|
||||||
|
eventClick: this.onEventClick,
|
||||||
|
selectMirror: true,
|
||||||
|
editable: true,
|
||||||
|
weekends: !this.hideWeekEnds,
|
||||||
|
headerToolbar: {
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "timeGridWeek,timeGridDay,listWeek",
|
||||||
|
},
|
||||||
|
views: {
|
||||||
|
timeGrid: {
|
||||||
|
slotEventOverlap: false,
|
||||||
|
slotDuration: this.slotDuration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getActiveUsers() {
|
||||||
|
const users = [];
|
||||||
|
for (const id of this.$store.state.currentView.users.keys()) {
|
||||||
|
users.push(this.$store.getters.getUserDataById(id).user);
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
},
|
||||||
|
suggestedUsers() {
|
||||||
|
const suggested = [];
|
||||||
|
|
||||||
|
this.$data.previousUser.forEach((u) => {
|
||||||
|
if (u.id !== this.$store.getters.getMainUser.id) {
|
||||||
|
suggested.push(u);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return suggested;
|
||||||
},
|
},
|
||||||
};
|
|
||||||
},
|
},
|
||||||
getActiveUsers() {
|
methods: {
|
||||||
const users = [];
|
setMainUser({ entity }) {
|
||||||
for (const id of this.$store.state.currentView.users.keys()) {
|
const user = entity;
|
||||||
users.push(this.$store.getters.getUserDataById(id).user);
|
console.log("setMainUser APP", entity);
|
||||||
}
|
|
||||||
return users;
|
if (
|
||||||
|
user.id !== this.$store.getters.getMainUser &&
|
||||||
|
(this.$store.state.activity.calendarRange !== null ||
|
||||||
|
this.$store.state.activity.startDate !== null ||
|
||||||
|
this.$store.state.activity.endDate !== null)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!window.confirm(
|
||||||
|
this.$t("change_main_user_will_reset_event_data"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the previous user, if any, in the previous user list (in use for suggestion)
|
||||||
|
if (null !== this.$store.getters.getMainUser) {
|
||||||
|
const suggestedUids = new Set(
|
||||||
|
this.$data.previousUser.map((u) => u.id),
|
||||||
|
);
|
||||||
|
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
||||||
|
this.$data.previousUser.push(
|
||||||
|
this.$store.getters.getMainUser,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch("setMainUser", user);
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user,
|
||||||
|
ranges: true,
|
||||||
|
remotes: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeMainUser(user) {
|
||||||
|
console.log("removeMainUser APP", user);
|
||||||
|
|
||||||
|
window.alert(this.$t("main_user_is_mandatory"));
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onDatesSet(event) {
|
||||||
|
console.log("onDatesSet", event);
|
||||||
|
this.$store.dispatch("setCurrentDatesView", {
|
||||||
|
start: event.start,
|
||||||
|
end: event.end,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDateSelect(payload) {
|
||||||
|
console.log("onDateSelect", payload);
|
||||||
|
|
||||||
|
// show an alert if changing mainUser
|
||||||
|
if (
|
||||||
|
(this.$store.getters.getMainUser !== null &&
|
||||||
|
this.$store.state.me.id !==
|
||||||
|
this.$store.getters.getMainUser.id) ||
|
||||||
|
this.$store.getters.getMainUser === null
|
||||||
|
) {
|
||||||
|
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.$store.state.me,
|
||||||
|
remotes: true,
|
||||||
|
ranges: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch("setEventTimes", {
|
||||||
|
start: payload.start,
|
||||||
|
end: payload.end,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEventChange(payload) {
|
||||||
|
console.log("onEventChange", payload);
|
||||||
|
if (this.$store.state.activity.calendarRange !== null) {
|
||||||
|
throw new Error(
|
||||||
|
"not allowed to edit a calendar associated with a calendar range",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.$store.dispatch("setEventTimes", {
|
||||||
|
start: payload.event.start,
|
||||||
|
end: payload.event.end,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEventClick(payload) {
|
||||||
|
if (payload.event.extendedProps.is !== "range") {
|
||||||
|
// do nothing when clicking on remote
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// show an alert if changing mainUser
|
||||||
|
if (
|
||||||
|
this.$store.getters.getMainUser !== null &&
|
||||||
|
payload.event.extendedProps.userId !==
|
||||||
|
this.$store.getters.getMainUser.id
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!window.confirm(
|
||||||
|
this.$t("this_calendar_range_will_change_main_user"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch("associateCalendarToRange", {
|
||||||
|
range: payload.event,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
suggestedUsers() {
|
|
||||||
const suggested = [];
|
|
||||||
|
|
||||||
this.$data.previousUser.forEach((u) => {
|
|
||||||
if (u.id !== this.$store.getters.getMainUser.id) {
|
|
||||||
suggested.push(u);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return suggested;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setMainUser({ entity }) {
|
|
||||||
const user = entity;
|
|
||||||
console.log("setMainUser APP", entity);
|
|
||||||
|
|
||||||
if (
|
|
||||||
user.id !== this.$store.getters.getMainUser &&
|
|
||||||
(this.$store.state.activity.calendarRange !== null ||
|
|
||||||
this.$store.state.activity.startDate !== null ||
|
|
||||||
this.$store.state.activity.endDate !== null)
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
!window.confirm(this.$t("change_main_user_will_reset_event_data"))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the previous user, if any, in the previous user list (in use for suggestion)
|
|
||||||
if (null !== this.$store.getters.getMainUser) {
|
|
||||||
const suggestedUids = new Set(this.$data.previousUser.map((u) => u.id));
|
|
||||||
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
|
|
||||||
this.$data.previousUser.push(this.$store.getters.getMainUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch("setMainUser", user);
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user,
|
|
||||||
ranges: true,
|
|
||||||
remotes: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeMainUser(user) {
|
|
||||||
console.log("removeMainUser APP", user);
|
|
||||||
|
|
||||||
window.alert(this.$t("main_user_is_mandatory"));
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
onDatesSet(event) {
|
|
||||||
console.log("onDatesSet", event);
|
|
||||||
this.$store.dispatch("setCurrentDatesView", {
|
|
||||||
start: event.start,
|
|
||||||
end: event.end,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onDateSelect(payload) {
|
|
||||||
console.log("onDateSelect", payload);
|
|
||||||
|
|
||||||
// show an alert if changing mainUser
|
|
||||||
if (
|
|
||||||
(this.$store.getters.getMainUser !== null &&
|
|
||||||
this.$store.state.me.id !== this.$store.getters.getMainUser.id) ||
|
|
||||||
this.$store.getters.getMainUser === null
|
|
||||||
) {
|
|
||||||
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user: this.$store.state.me,
|
|
||||||
remotes: true,
|
|
||||||
ranges: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch("setEventTimes", {
|
|
||||||
start: payload.start,
|
|
||||||
end: payload.end,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onEventChange(payload) {
|
|
||||||
console.log("onEventChange", payload);
|
|
||||||
if (this.$store.state.activity.calendarRange !== null) {
|
|
||||||
throw new Error(
|
|
||||||
"not allowed to edit a calendar associated with a calendar range",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.$store.dispatch("setEventTimes", {
|
|
||||||
start: payload.event.start,
|
|
||||||
end: payload.event.end,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onEventClick(payload) {
|
|
||||||
if (payload.event.extendedProps.is !== "range") {
|
|
||||||
// do nothing when clicking on remote
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// show an alert if changing mainUser
|
|
||||||
if (
|
|
||||||
this.$store.getters.getMainUser !== null &&
|
|
||||||
payload.event.extendedProps.userId !==
|
|
||||||
this.$store.getters.getMainUser.id
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
!window.confirm(this.$t("this_calendar_range_will_change_main_user"))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch("associateCalendarToRange", {
|
|
||||||
range: payload.event,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.calendar-actives {
|
.calendar-actives {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-options {
|
.display-options {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for events which are range */
|
/* for events which are range */
|
||||||
.fc-event.isrange {
|
.fc-event.isrange {
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,105 +1,119 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :style="style" class="calendar-active">
|
<div :style="style" class="calendar-active">
|
||||||
<span class="badge-user">
|
<span class="badge-user">
|
||||||
{{ user.text }}
|
{{ user.text }}
|
||||||
<template v-if="invite !== null">
|
<template v-if="invite !== null">
|
||||||
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
|
||||||
<i v-else-if="invite.status === 'declined'" class="fa fa-times" />
|
<i
|
||||||
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
|
v-else-if="invite.status === 'declined'"
|
||||||
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
|
class="fa fa-times"
|
||||||
<span v-else="">{{ invite.status }}</span>
|
/>
|
||||||
</template>
|
<i
|
||||||
</span>
|
v-else-if="invite.status === 'pending'"
|
||||||
<span class="form-check-inline form-switch">
|
class="fa fa-question-o"
|
||||||
<input
|
/>
|
||||||
class="form-check-input"
|
<i
|
||||||
type="checkbox"
|
v-else-if="invite.status === 'tentative'"
|
||||||
id="flexSwitchCheckDefault"
|
class="fa fa-question"
|
||||||
v-model="rangeShow"
|
/>
|
||||||
/>
|
<span v-else="">{{ invite.status }}</span>
|
||||||
<label
|
</template>
|
||||||
class="form-check-label"
|
</span>
|
||||||
for="flexSwitchCheckDefault"
|
<span class="form-check-inline form-switch">
|
||||||
title="Disponibilités"
|
<input
|
||||||
><i class="fa fa-calendar-check-o"
|
class="form-check-input"
|
||||||
/></label>
|
type="checkbox"
|
||||||
</span>
|
id="flexSwitchCheckDefault"
|
||||||
<span class="form-check-inline form-switch">
|
v-model="rangeShow"
|
||||||
<input
|
/>
|
||||||
class="form-check-input"
|
<label
|
||||||
type="checkbox"
|
class="form-check-label"
|
||||||
id="flexSwitchCheckDefault"
|
for="flexSwitchCheckDefault"
|
||||||
v-model="remoteShow"
|
title="Disponibilités"
|
||||||
/>
|
><i class="fa fa-calendar-check-o"
|
||||||
<label
|
/></label>
|
||||||
class="form-check-label"
|
</span>
|
||||||
for="flexSwitchCheckDefault"
|
<span class="form-check-inline form-switch">
|
||||||
title="Agenda"
|
<input
|
||||||
><i class="fa fa-calendar"
|
class="form-check-input"
|
||||||
/></label>
|
type="checkbox"
|
||||||
</span>
|
id="flexSwitchCheckDefault"
|
||||||
</div>
|
v-model="remoteShow"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
class="form-check-label"
|
||||||
|
for="flexSwitchCheckDefault"
|
||||||
|
title="Agenda"
|
||||||
|
><i class="fa fa-calendar"
|
||||||
|
/></label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CalendarActive",
|
name: "CalendarActive",
|
||||||
props: {
|
props: {
|
||||||
user: {
|
user: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
invite: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
invite: {
|
computed: {
|
||||||
type: Object,
|
style() {
|
||||||
required: false,
|
return {
|
||||||
default: null,
|
backgroundColor: this.$store.getters.getUserData(this.user)
|
||||||
|
.mainColor,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
rangeShow: {
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.user,
|
||||||
|
ranges: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return this.$store.getters.isRangeShownOnCalendarForUser(
|
||||||
|
this.user,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remoteShow: {
|
||||||
|
set(value) {
|
||||||
|
this.$store.commit("showUserOnCalendar", {
|
||||||
|
user: this.user,
|
||||||
|
remotes: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return this.$store.getters.isRemoteShownOnCalendarForUser(
|
||||||
|
this.user,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
style() {
|
|
||||||
return {
|
|
||||||
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
rangeShow: {
|
|
||||||
set(value) {
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user: this.user,
|
|
||||||
ranges: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
remoteShow: {
|
|
||||||
set(value) {
|
|
||||||
this.$store.commit("showUserOnCalendar", {
|
|
||||||
user: this.user,
|
|
||||||
remotes: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.calendar-active {
|
.calendar-active {
|
||||||
margin: 0 0.25rem 0.25rem 0;
|
margin: 0 0.25rem 0.25rem 0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
color: var(--bs-blue);
|
color: var(--bs-blue);
|
||||||
|
|
||||||
& > .badge-user {
|
& > .badge-user {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
|
|||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
export const fetchCalendarRangeForUser = (
|
export const fetchCalendarRangeForUser = (
|
||||||
user: User,
|
user: User,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
): Promise<CalendarRange[]> => {
|
): Promise<CalendarRange[]> => {
|
||||||
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchCalendarRemoteForUser = (
|
export const fetchCalendarRemoteForUser = (
|
||||||
user: User,
|
user: User,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
): Promise<CalendarRemote[]> => {
|
): Promise<CalendarRemote[]> => {
|
||||||
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchCalendarLocalForUser = (
|
export const fetchCalendarLocalForUser = (
|
||||||
user: User,
|
user: User,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date,
|
end: Date,
|
||||||
): Promise<CalendarLight[]> => {
|
): Promise<CalendarLight[]> => {
|
||||||
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
|
||||||
const dateFrom = datetimeToISO(start);
|
const dateFrom = datetimeToISO(start);
|
||||||
const dateTo = datetimeToISO(end);
|
const dateTo = datetimeToISO(end);
|
||||||
|
|
||||||
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
|
||||||
};
|
};
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
const COLORS = [
|
const COLORS = [
|
||||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||||
"#8dd3c7",
|
"#8dd3c7",
|
||||||
"#ffffb3",
|
"#ffffb3",
|
||||||
"#bebada",
|
"#bebada",
|
||||||
"#fb8072",
|
"#fb8072",
|
||||||
"#80b1d3",
|
"#80b1d3",
|
||||||
"#fdb462",
|
"#fdb462",
|
||||||
"#b3de69",
|
"#b3de69",
|
||||||
"#fccde5",
|
"#fccde5",
|
||||||
"#d9d9d9",
|
"#d9d9d9",
|
||||||
"#bc80bd",
|
"#bc80bd",
|
||||||
"#ccebc5",
|
"#ccebc5",
|
||||||
"#ffed6f",
|
"#ffed6f",
|
||||||
];
|
];
|
||||||
|
|
||||||
export { COLORS };
|
export { COLORS };
|
||||||
|
@@ -1,117 +1,117 @@
|
|||||||
import { COLORS } from "../const";
|
import { COLORS } from "../const";
|
||||||
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
import {
|
import {
|
||||||
DateTime,
|
DateTime,
|
||||||
User,
|
User,
|
||||||
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
|
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
|
||||||
import type { EventInputCalendarRange } from "../../../types";
|
import type { EventInputCalendarRange } from "../../../types";
|
||||||
import { EventInput } from "@fullcalendar/core";
|
import { EventInput } from "@fullcalendar/core";
|
||||||
|
|
||||||
export interface UserData {
|
export interface UserData {
|
||||||
user: User;
|
user: User;
|
||||||
calendarRanges: CalendarRange[];
|
calendarRanges: CalendarRange[];
|
||||||
calendarRangesLoaded: {}[];
|
calendarRangesLoaded: {}[];
|
||||||
remotes: CalendarRemote[];
|
remotes: CalendarRemote[];
|
||||||
remotesLoaded: {}[];
|
remotesLoaded: {}[];
|
||||||
locals: CalendarRemote[];
|
locals: CalendarRemote[];
|
||||||
localsLoaded: {}[];
|
localsLoaded: {}[];
|
||||||
mainColor: string;
|
mainColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addIdToValue = (string: string, id: number): string => {
|
export const addIdToValue = (string: string, id: number): string => {
|
||||||
const array = string ? string.split(",") : [];
|
const array = string ? string.split(",") : [];
|
||||||
array.push(id.toString());
|
array.push(id.toString());
|
||||||
const str = array.join();
|
const str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeIdFromValue = (string: string, id: number) => {
|
export const removeIdFromValue = (string: string, id: number) => {
|
||||||
let array = string.split(",");
|
let array = string.split(",");
|
||||||
array = array.filter((el) => el !== id.toString());
|
array = array.filter((el) => el !== id.toString());
|
||||||
const str = array.join();
|
const str = array.join();
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assign missing keys for the ConcernedGroups component
|
* Assign missing keys for the ConcernedGroups component
|
||||||
*/
|
*/
|
||||||
export const mapEntity = (entity: EventInput): EventInput => {
|
export const mapEntity = (entity: EventInput): EventInput => {
|
||||||
const calendar = { ...entity };
|
const calendar = { ...entity };
|
||||||
Object.assign(calendar, { thirdParties: entity.professionals });
|
Object.assign(calendar, { thirdParties: entity.professionals });
|
||||||
|
|
||||||
if (entity.startDate !== null) {
|
if (entity.startDate !== null) {
|
||||||
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
|
||||||
}
|
}
|
||||||
if (entity.endDate !== null) {
|
if (entity.endDate !== null) {
|
||||||
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.calendarRange !== null) {
|
if (entity.calendarRange !== null) {
|
||||||
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
|
||||||
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return calendar;
|
return calendar;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createUserData = (user: User, colorIndex: number): UserData => {
|
export const createUserData = (user: User, colorIndex: number): UserData => {
|
||||||
const colorId = colorIndex % COLORS.length;
|
const colorId = colorIndex % COLORS.length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: user,
|
user: user,
|
||||||
calendarRanges: [],
|
calendarRanges: [],
|
||||||
calendarRangesLoaded: [],
|
calendarRangesLoaded: [],
|
||||||
remotes: [],
|
remotes: [],
|
||||||
remotesLoaded: [],
|
remotesLoaded: [],
|
||||||
locals: [],
|
locals: [],
|
||||||
localsLoaded: [],
|
localsLoaded: [],
|
||||||
mainColor: COLORS[colorId],
|
mainColor: COLORS[colorId],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
|
||||||
export const calendarRangeToFullCalendarEvent = (
|
export const calendarRangeToFullCalendarEvent = (
|
||||||
entity: CalendarRange,
|
entity: CalendarRange,
|
||||||
): EventInputCalendarRange => {
|
): EventInputCalendarRange => {
|
||||||
return {
|
return {
|
||||||
id: `range_${entity.id}`,
|
id: `range_${entity.id}`,
|
||||||
title: "(" + entity.user.text + ")",
|
title: "(" + entity.user.text + ")",
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: false,
|
allDay: false,
|
||||||
userId: entity.user.id,
|
userId: entity.user.id,
|
||||||
userLabel: entity.user.label,
|
userLabel: entity.user.label,
|
||||||
calendarRangeId: entity.id,
|
calendarRangeId: entity.id,
|
||||||
locationId: entity.location.id,
|
locationId: entity.location.id,
|
||||||
locationName: entity.location.name,
|
locationName: entity.location.name,
|
||||||
is: "range",
|
is: "range",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remoteToFullCalendarEvent = (
|
export const remoteToFullCalendarEvent = (
|
||||||
entity: CalendarRemote,
|
entity: CalendarRemote,
|
||||||
): EventInput & { id: string } => {
|
): EventInput & { id: string } => {
|
||||||
return {
|
return {
|
||||||
id: `range_${entity.id}`,
|
id: `range_${entity.id}`,
|
||||||
title: entity.title,
|
title: entity.title,
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: entity.isAllDay,
|
allDay: entity.isAllDay,
|
||||||
is: "remote",
|
is: "remote",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const localsToFullCalendarEvent = (
|
export const localsToFullCalendarEvent = (
|
||||||
entity: CalendarLight,
|
entity: CalendarLight,
|
||||||
): EventInput & { id: string; originId: number } => {
|
): EventInput & { id: string; originId: number } => {
|
||||||
return {
|
return {
|
||||||
id: `local_${entity.id}`,
|
id: `local_${entity.id}`,
|
||||||
title: entity.persons.map((p) => p.text).join(", "),
|
title: entity.persons.map((p) => p.text).join(", "),
|
||||||
originId: entity.id,
|
originId: entity.id,
|
||||||
start: entity.startDate.datetime8601,
|
start: entity.startDate.datetime8601,
|
||||||
end: entity.endDate.datetime8601,
|
end: entity.endDate.datetime8601,
|
||||||
allDay: false,
|
allDay: false,
|
||||||
is: "local",
|
is: "local",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,50 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button
|
<button
|
||||||
id="btnGroupDrop1"
|
id="btnGroupDrop1"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-misc dropdown-toggle"
|
class="btn btn-misc dropdown-toggle"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
|
||||||
<template v-if="status === Statuses.PENDING">
|
|
||||||
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="status === Statuses.ACCEPTED">
|
|
||||||
<span class="fa fa-check"></span> {{ $t("Accepted") }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="status === Statuses.DECLINED">
|
|
||||||
<span class="fa fa-times"></span> {{ $t("Declined") }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
|
||||||
<span class="fa fa-question"></span> {{ $t("Tentative") }}
|
|
||||||
</template>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
|
||||||
<li v-if="status !== Statuses.ACCEPTED">
|
|
||||||
<a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)"
|
|
||||||
><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a
|
|
||||||
>
|
>
|
||||||
</li>
|
<template v-if="status === Statuses.PENDING">
|
||||||
<li v-if="status !== Statuses.DECLINED">
|
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
|
||||||
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)"
|
</template>
|
||||||
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a
|
<template v-else-if="status === Statuses.ACCEPTED">
|
||||||
>
|
<span class="fa fa-check"></span> {{ $t("Accepted") }}
|
||||||
</li>
|
</template>
|
||||||
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
|
<template v-else-if="status === Statuses.DECLINED">
|
||||||
<a
|
<span class="fa fa-times"></span> {{ $t("Declined") }}
|
||||||
class="dropdown-item"
|
</template>
|
||||||
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
|
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
|
||||||
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a
|
<span class="fa fa-question"></span> {{ $t("Tentative") }}
|
||||||
>
|
</template>
|
||||||
</li>
|
</button>
|
||||||
<li v-if="status !== Statuses.PENDING">
|
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||||
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
|
<li v-if="status !== Statuses.ACCEPTED">
|
||||||
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a
|
<a
|
||||||
>
|
class="dropdown-item"
|
||||||
</li>
|
@click="changeStatus(Statuses.ACCEPTED)"
|
||||||
</ul>
|
><i class="fa fa-check" aria-hidden="true"></i>
|
||||||
</div>
|
{{ $t("Accept") }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li v-if="status !== Statuses.DECLINED">
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="changeStatus(Statuses.DECLINED)"
|
||||||
|
><i class="fa fa-times" aria-hidden="true"></i>
|
||||||
|
{{ $t("Decline") }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
|
||||||
|
><i class="fa fa-question"></i>
|
||||||
|
{{ $t("Tentatively_accept") }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li v-if="status !== Statuses.PENDING">
|
||||||
|
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
|
||||||
|
><i class="fa fa-hourglass-o"></i>
|
||||||
|
{{ $t("Set_pending") }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -56,67 +64,69 @@ const PENDING = "pending";
|
|||||||
const TENTATIVELY_ACCEPTED = "tentative";
|
const TENTATIVELY_ACCEPTED = "tentative";
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
messages: {
|
messages: {
|
||||||
fr: {
|
fr: {
|
||||||
Give_an_answer: "Répondre",
|
Give_an_answer: "Répondre",
|
||||||
Accepted: "Accepté",
|
Accepted: "Accepté",
|
||||||
Declined: "Refusé",
|
Declined: "Refusé",
|
||||||
Tentative: "Accepté provisoirement",
|
Tentative: "Accepté provisoirement",
|
||||||
Accept: "Accepter",
|
Accept: "Accepter",
|
||||||
Decline: "Refuser",
|
Decline: "Refuser",
|
||||||
Tentatively_accept: "Accepter provisoirement",
|
Tentatively_accept: "Accepter provisoirement",
|
||||||
Set_pending: "Ne pas répondre",
|
Set_pending: "Ne pas répondre",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Answer",
|
name: "Answer",
|
||||||
i18n,
|
i18n,
|
||||||
props: {
|
props: {
|
||||||
calendarId: { type: Number, required: true },
|
calendarId: { type: Number, required: true },
|
||||||
status: {
|
status: {
|
||||||
type: String as PropType<
|
type: String as PropType<
|
||||||
"accepted" | "declined" | "pending" | "tentative"
|
"accepted" | "declined" | "pending" | "tentative"
|
||||||
>,
|
>,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
emits: {
|
||||||
emits: {
|
statusChanged(
|
||||||
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
|
payload: "accepted" | "declined" | "pending" | "tentative",
|
||||||
return true;
|
) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
data() {
|
||||||
data() {
|
return {
|
||||||
return {
|
Statuses: {
|
||||||
Statuses: {
|
ACCEPTED,
|
||||||
ACCEPTED,
|
DECLINED,
|
||||||
DECLINED,
|
PENDING,
|
||||||
PENDING,
|
TENTATIVELY_ACCEPTED,
|
||||||
TENTATIVELY_ACCEPTED,
|
},
|
||||||
},
|
};
|
||||||
};
|
},
|
||||||
},
|
methods: {
|
||||||
methods: {
|
changeStatus: function (
|
||||||
changeStatus: function (
|
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
||||||
newStatus: "accepted" | "declined" | "pending" | "tentative",
|
) {
|
||||||
) {
|
console.log("changeStatus", newStatus);
|
||||||
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
|
||||||
window
|
.fetch(url, {
|
||||||
.fetch(url, {
|
method: "POST",
|
||||||
method: "POST",
|
})
|
||||||
})
|
.then((r: Response) => {
|
||||||
.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>
|
||||||
|
|
||||||
|
@@ -1,177 +1,228 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
<label class="form-label">{{ $t("created_availabilities") }}</label>
|
||||||
<vue-multiselect
|
<vue-multiselect
|
||||||
v-model="pickedLocation"
|
v-model="pickedLocation"
|
||||||
:options="locations"
|
:options="locations"
|
||||||
:label="'name'"
|
:label="'name'"
|
||||||
:track-by="'id'"
|
:track-by="'id'"
|
||||||
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
:selectLabel="'Presser \'Entrée\' pour choisir'"
|
||||||
:selectedLabel="'Choisir'"
|
:selectedLabel="'Choisir'"
|
||||||
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
:deselectLabel="'Presser \'Entrée\' pour enlever'"
|
||||||
:placeholder="'Choisir'"
|
:placeholder="'Choisir'"
|
||||||
></vue-multiselect>
|
></vue-multiselect>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="display-options row justify-content-between"
|
|
||||||
style="margin-top: 1rem"
|
|
||||||
>
|
|
||||||
<div class="col-sm-9 col-xs-12">
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<label class="input-group-text" for="slotDuration"
|
|
||||||
>Durée des créneaux</label
|
|
||||||
>
|
|
||||||
<select v-model="slotDuration" id="slotDuration" class="form-select">
|
|
||||||
<option value="00:05:00">5 minutes</option>
|
|
||||||
<option value="00:10:00">10 minutes</option>
|
|
||||||
<option value="00:15:00">15 minutes</option>
|
|
||||||
<option value="00:30:00">30 minutes</option>
|
|
||||||
</select>
|
|
||||||
<label class="input-group-text" for="slotMinTime">De</label>
|
|
||||||
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
|
|
||||||
<option value="00:00:00">0h</option>
|
|
||||||
<option value="01:00:00">1h</option>
|
|
||||||
<option value="02:00:00">2h</option>
|
|
||||||
<option value="03:00:00">3h</option>
|
|
||||||
<option value="04:00:00">4h</option>
|
|
||||||
<option value="05:00:00">5h</option>
|
|
||||||
<option value="06:00:00">6h</option>
|
|
||||||
<option value="07:00:00">7h</option>
|
|
||||||
<option value="08:00:00">8h</option>
|
|
||||||
<option value="09:00:00">9h</option>
|
|
||||||
<option value="10:00:00">10h</option>
|
|
||||||
<option value="11:00:00">11h</option>
|
|
||||||
<option value="12:00:00">12h</option>
|
|
||||||
</select>
|
|
||||||
<label class="input-group-text" for="slotMaxTime">À</label>
|
|
||||||
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
|
|
||||||
<option value="12:00:00">12h</option>
|
|
||||||
<option value="13:00:00">13h</option>
|
|
||||||
<option value="14:00:00">14h</option>
|
|
||||||
<option value="15:00:00">15h</option>
|
|
||||||
<option value="16:00:00">16h</option>
|
|
||||||
<option value="17:00:00">17h</option>
|
|
||||||
<option value="18:00:00">18h</option>
|
|
||||||
<option value="19:00:00">19h</option>
|
|
||||||
<option value="20:00:00">20h</option>
|
|
||||||
<option value="21:00:00">21h</option>
|
|
||||||
<option value="22:00:00">22h</option>
|
|
||||||
<option value="23:00:00">23h</option>
|
|
||||||
<option value="23:59:59">24h</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-3">
|
|
||||||
<div class="float-end">
|
|
||||||
<div class="form-check input-group">
|
|
||||||
<span class="input-group-text">
|
|
||||||
<input
|
|
||||||
id="showHideWE"
|
|
||||||
class="mt-0"
|
|
||||||
type="checkbox"
|
|
||||||
v-model="showWeekends"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<label for="showHideWE" class="form-check-label input-group-text"
|
|
||||||
>Week-ends</label
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div
|
||||||
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
class="display-options row justify-content-between"
|
||||||
<template v-slot:eventContent="{ event }: { event: EventApi }">
|
style="margin-top: 1rem"
|
||||||
<span :class="eventClasses">
|
>
|
||||||
<b v-if="event.extendedProps.is === 'remote'">{{ event.title }}</b>
|
<div class="col-sm-9 col-xs-12">
|
||||||
<b v-else-if="event.extendedProps.is === 'range'"
|
<div class="input-group mb-3">
|
||||||
>{{ formatDate(event.startStr) }} -
|
<label class="input-group-text" for="slotDuration"
|
||||||
{{ event.extendedProps.locationName }}</b
|
>Durée des créneaux</label
|
||||||
>
|
>
|
||||||
<b v-else-if="event.extendedProps.is === 'local'">{{ event.title }}</b>
|
<select
|
||||||
<b v-else>no 'is'</b>
|
v-model="slotDuration"
|
||||||
<a
|
id="slotDuration"
|
||||||
v-if="event.extendedProps.is === 'range'"
|
class="form-select"
|
||||||
class="fa fa-fw fa-times delete"
|
>
|
||||||
@click.prevent="onClickDelete(event)"
|
<option value="00:05:00">5 minutes</option>
|
||||||
>
|
<option value="00:10:00">10 minutes</option>
|
||||||
</a>
|
<option value="00:15:00">15 minutes</option>
|
||||||
</span>
|
<option value="00:30:00">30 minutes</option>
|
||||||
</template>
|
<option value="00:45:00">45 minutes</option>
|
||||||
</FullCalendar>
|
<option value="00:60:00">60 minutes</option>
|
||||||
|
</select>
|
||||||
<div id="copy-widget">
|
<label class="input-group-text" for="slotMinTime">De</label>
|
||||||
<div class="container mt-2 mb-2">
|
<select
|
||||||
<div class="row justify-content-between align-items-center mb-4">
|
v-model="slotMinTime"
|
||||||
<div class="col-xs-12 col-sm-3 col-md-2">
|
id="slotMinTime"
|
||||||
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option value="00:00:00">0h</option>
|
||||||
|
<option value="01:00:00">1h</option>
|
||||||
|
<option value="02:00:00">2h</option>
|
||||||
|
<option value="03:00:00">3h</option>
|
||||||
|
<option value="04:00:00">4h</option>
|
||||||
|
<option value="05:00:00">5h</option>
|
||||||
|
<option value="06:00:00">6h</option>
|
||||||
|
<option value="07:00:00">7h</option>
|
||||||
|
<option value="08:00:00">8h</option>
|
||||||
|
<option value="09:00:00">9h</option>
|
||||||
|
<option value="10:00:00">10h</option>
|
||||||
|
<option value="11:00:00">11h</option>
|
||||||
|
<option value="12:00:00">12h</option>
|
||||||
|
</select>
|
||||||
|
<label class="input-group-text" for="slotMaxTime">À</label>
|
||||||
|
<select
|
||||||
|
v-model="slotMaxTime"
|
||||||
|
id="slotMaxTime"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option value="12:00:00">12h</option>
|
||||||
|
<option value="13:00:00">13h</option>
|
||||||
|
<option value="14:00:00">14h</option>
|
||||||
|
<option value="15:00:00">15h</option>
|
||||||
|
<option value="16:00:00">16h</option>
|
||||||
|
<option value="17:00:00">17h</option>
|
||||||
|
<option value="18:00:00">18h</option>
|
||||||
|
<option value="19:00:00">19h</option>
|
||||||
|
<option value="20:00:00">20h</option>
|
||||||
|
<option value="21:00:00">21h</option>
|
||||||
|
<option value="22:00:00">22h</option>
|
||||||
|
<option value="23:00:00">23h</option>
|
||||||
|
<option value="23:59:59">24h</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-9 col-md-2">
|
<div class="col-xs-12 col-sm-3">
|
||||||
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
|
<div class="float-end">
|
||||||
<option value="day">{{ $t("from_day_to_day") }}</option>
|
<div class="form-check input-group">
|
||||||
<option value="week">
|
<span class="input-group-text">
|
||||||
{{ $t("from_week_to_week") }}
|
<input
|
||||||
</option>
|
id="showHideWE"
|
||||||
</select>
|
class="mt-0"
|
||||||
|
type="checkbox"
|
||||||
|
v-model="showWeekends"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<label
|
||||||
|
for="showHideWE"
|
||||||
|
class="form-check-label input-group-text"
|
||||||
|
>Week-ends</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="dayOrWeek === 'day'">
|
</div>
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
<FullCalendar :options="calendarOptions" ref="calendarRef">
|
||||||
<input class="form-control" type="date" v-model="copyFrom" />
|
<template v-slot:eventContent="{ event }: { event: EventApi }">
|
||||||
</div>
|
<span :class="eventClasses">
|
||||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
<b v-if="event.extendedProps.is === 'remote'">{{
|
||||||
<i class="fa fa-angle-double-right"></i>
|
event.title
|
||||||
</div>
|
}}</b>
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
<b v-else-if="event.extendedProps.is === 'range'"
|
||||||
<input class="form-control" type="date" v-model="copyTo" />
|
>{{ formatDate(event.startStr, "time") }} -
|
||||||
</div>
|
{{ formatDate(event.endStr, "time") }}:
|
||||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
{{ event.extendedProps.locationName }}</b
|
||||||
<button class="btn btn-action float-end" @click="copyDay">
|
>
|
||||||
{{ $t("copy_range") }}
|
<b v-else-if="event.extendedProps.is === 'local'">{{
|
||||||
</button>
|
event.title
|
||||||
</div>
|
}}</b>
|
||||||
|
<b v-else>no 'is'</b>
|
||||||
|
<a
|
||||||
|
v-if="event.extendedProps.is === 'range'"
|
||||||
|
class="fa fa-fw fa-times delete"
|
||||||
|
@click.prevent="onClickDelete(event)"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
</FullCalendar>
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
|
||||||
<select
|
|
||||||
v-model="copyFromWeek"
|
|
||||||
id="copyFromWeek"
|
|
||||||
class="form-select"
|
|
||||||
>
|
|
||||||
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
|
|
||||||
{{ w.text }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
|
||||||
<i class="fa fa-angle-double-right"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-3 col-md-3">
|
|
||||||
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
|
|
||||||
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
|
|
||||||
{{ w.text }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-sm-5 col-md-1">
|
|
||||||
<button class="btn btn-action float-end" @click="copyWeek">
|
|
||||||
{{ $t("copy_range") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- not directly seen, but include in a modal -->
|
<div id="copy-widget">
|
||||||
<edit-location ref="editLocation"></edit-location>
|
<div class="container mt-2 mb-2">
|
||||||
|
<div class="row justify-content-between align-items-center mb-4">
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-2">
|
||||||
|
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-9 col-md-2">
|
||||||
|
<select
|
||||||
|
v-model="dayOrWeek"
|
||||||
|
id="dayOrWeek"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option value="day">{{ $t("from_day_to_day") }}</option>
|
||||||
|
<option value="week">
|
||||||
|
{{ $t("from_week_to_week") }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<template v-if="dayOrWeek === 'day'">
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="date"
|
||||||
|
v-model="copyFrom"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||||
|
<i class="fa fa-angle-double-right"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="date"
|
||||||
|
v-model="copyTo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||||
|
<button
|
||||||
|
class="btn btn-action float-end"
|
||||||
|
@click="copyDay"
|
||||||
|
>
|
||||||
|
{{ $t("copy_range") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
|
<select
|
||||||
|
v-model="copyFromWeek"
|
||||||
|
id="copyFromWeek"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="w in lastWeeks"
|
||||||
|
:value="w.value"
|
||||||
|
:key="w.value"
|
||||||
|
>
|
||||||
|
{{ w.text }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
|
||||||
|
<i class="fa fa-angle-double-right"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-3 col-md-3">
|
||||||
|
<select
|
||||||
|
v-model="copyToWeek"
|
||||||
|
id="copyToWeek"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="w in nextWeeks"
|
||||||
|
:value="w.value"
|
||||||
|
:key="w.value"
|
||||||
|
>
|
||||||
|
{{ w.text }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-sm-5 col-md-1">
|
||||||
|
<button
|
||||||
|
class="btn btn-action float-end"
|
||||||
|
@click="copyWeek"
|
||||||
|
>
|
||||||
|
{{ $t("copy_range") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- not directly seen, but include in a modal -->
|
||||||
|
<edit-location ref="editLocation"></edit-location>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {
|
import type {
|
||||||
CalendarOptions,
|
CalendarOptions,
|
||||||
DatesSetArg,
|
DatesSetArg,
|
||||||
EventInput,
|
EventInput,
|
||||||
} from "@fullcalendar/core";
|
} from "@fullcalendar/core";
|
||||||
import { computed, ref, onMounted } from "vue";
|
import { computed, ref, onMounted } from "vue";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
@@ -179,14 +230,14 @@ import { key } from "./store";
|
|||||||
import FullCalendar from "@fullcalendar/vue3";
|
import FullCalendar from "@fullcalendar/vue3";
|
||||||
import frLocale from "@fullcalendar/core/locales/fr";
|
import frLocale from "@fullcalendar/core/locales/fr";
|
||||||
import interactionPlugin, {
|
import interactionPlugin, {
|
||||||
EventResizeDoneArg,
|
EventResizeDoneArg,
|
||||||
} from "@fullcalendar/interaction";
|
} from "@fullcalendar/interaction";
|
||||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
import {
|
import {
|
||||||
EventApi,
|
EventApi,
|
||||||
DateSelectArg,
|
DateSelectArg,
|
||||||
EventDropArg,
|
EventDropArg,
|
||||||
EventClickArg,
|
EventClickArg,
|
||||||
} from "@fullcalendar/core";
|
} from "@fullcalendar/core";
|
||||||
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
|
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
@@ -207,96 +258,113 @@ const copyFromWeek = ref<string | null>(null);
|
|||||||
const copyToWeek = ref<string | null>(null);
|
const copyToWeek = ref<string | null>(null);
|
||||||
|
|
||||||
interface Weeks {
|
interface Weeks {
|
||||||
value: string | null;
|
value: string | null;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMonday = (week: number): Date => {
|
const getMonday = (week: number): Date => {
|
||||||
const lastMonday = new Date();
|
const lastMonday = new Date();
|
||||||
lastMonday.setDate(
|
lastMonday.setDate(
|
||||||
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
|
||||||
);
|
);
|
||||||
return lastMonday;
|
return lastMonday;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
};
|
};
|
||||||
|
|
||||||
const lastWeeks = computed((): Weeks[] =>
|
const lastWeeks = computed((): Weeks[] =>
|
||||||
Array.from(Array(30).keys()).map((w) => {
|
Array.from(Array(30).keys()).map((w) => {
|
||||||
const lastMonday = getMonday(15 - w);
|
const lastMonday = getMonday(15 - w);
|
||||||
return {
|
return {
|
||||||
value: dateToISO(lastMonday),
|
value: dateToISO(lastMonday),
|
||||||
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const nextWeeks = computed((): Weeks[] =>
|
const nextWeeks = computed((): Weeks[] =>
|
||||||
Array.from(Array(52).keys()).map((w) => {
|
Array.from(Array(52).keys()).map((w) => {
|
||||||
const nextMonday = getMonday(w + 1);
|
const nextMonday = getMonday(w + 1);
|
||||||
return {
|
return {
|
||||||
value: dateToISO(nextMonday),
|
value: dateToISO(nextMonday),
|
||||||
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatDate = (datetime: string) => {
|
const formatDate = (datetime: string, format: null | "time" = null) => {
|
||||||
console.log(typeof datetime);
|
const date = ISOToDate(datetime);
|
||||||
return ISOToDate(datetime);
|
if (!date) return "";
|
||||||
|
|
||||||
|
if (format === "time") {
|
||||||
|
return date.toLocaleTimeString("fr-FR", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// French date formatting
|
||||||
|
return date.toLocaleDateString("fr-FR", {
|
||||||
|
weekday: "short",
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseOptions = ref<CalendarOptions>({
|
const baseOptions = ref<CalendarOptions>({
|
||||||
locale: frLocale,
|
locale: frLocale,
|
||||||
plugins: [interactionPlugin, timeGridPlugin],
|
plugins: [interactionPlugin, timeGridPlugin],
|
||||||
initialView: "timeGridWeek",
|
initialView: "timeGridWeek",
|
||||||
initialDate: new Date(),
|
initialDate: new Date(),
|
||||||
scrollTimeReset: false,
|
scrollTimeReset: false,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
// when the dates are changes in the fullcalendar view OR when new events are added
|
// when the dates are changes in the fullcalendar view OR when new events are added
|
||||||
datesSet: onDatesSet,
|
datesSet: onDatesSet,
|
||||||
// when a date is selected
|
// when a date is selected
|
||||||
select: onDateSelect,
|
select: onDateSelect,
|
||||||
// when a event is resized
|
// when a event is resized
|
||||||
eventResize: onEventDropOrResize,
|
eventResize: onEventDropOrResize,
|
||||||
// when an event is moved
|
// when an event is moved
|
||||||
eventDrop: onEventDropOrResize,
|
eventDrop: onEventDropOrResize,
|
||||||
// when an event si clicked
|
// when an event si clicked
|
||||||
eventClick: onEventClick,
|
eventClick: onEventClick,
|
||||||
selectMirror: false,
|
selectMirror: false,
|
||||||
editable: true,
|
editable: true,
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
left: "prev,next today",
|
left: "prev,next today",
|
||||||
center: "title",
|
center: "title",
|
||||||
right: "timeGridWeek,timeGridDay",
|
right: "timeGridWeek,timeGridDay",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ranges = computed<EventInput[]>(() => {
|
const ranges = computed<EventInput[]>(() => {
|
||||||
return store.state.calendarRanges.ranges;
|
return store.state.calendarRanges.ranges;
|
||||||
});
|
});
|
||||||
|
|
||||||
const locations = computed<Location[]>(() => {
|
const locations = computed<Location[]>(() => {
|
||||||
return store.state.locations.locations;
|
return store.state.locations.locations;
|
||||||
});
|
});
|
||||||
|
|
||||||
const pickedLocation = computed<Location | null>({
|
const pickedLocation = computed<Location | null>({
|
||||||
get(): Location | null {
|
get(): Location | null {
|
||||||
return (
|
return (
|
||||||
store.state.locations.locationPicked ||
|
store.state.locations.locationPicked ||
|
||||||
store.state.locations.currentLocation
|
store.state.locations.currentLocation
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
set(newLocation: Location | null): void {
|
set(newLocation: Location | null): void {
|
||||||
store.commit("locations/setLocationPicked", newLocation, {
|
store.commit("locations/setLocationPicked", newLocation, {
|
||||||
root: true,
|
root: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,116 +393,116 @@ const sources = computed<EventSourceInput[]>(() => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const calendarOptions = computed((): CalendarOptions => {
|
const calendarOptions = computed((): CalendarOptions => {
|
||||||
return {
|
return {
|
||||||
...baseOptions.value,
|
...baseOptions.value,
|
||||||
weekends: showWeekends.value,
|
weekends: showWeekends.value,
|
||||||
slotDuration: slotDuration.value,
|
slotDuration: slotDuration.value,
|
||||||
events: ranges.value,
|
events: ranges.value,
|
||||||
slotMinTime: slotMinTime.value,
|
slotMinTime: slotMinTime.value,
|
||||||
slotMaxTime: slotMaxTime.value,
|
slotMaxTime: slotMaxTime.value,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* launched when the calendar range date change
|
* launched when the calendar range date change
|
||||||
*/
|
*/
|
||||||
function onDatesSet(event: DatesSetArg): void {
|
function onDatesSet(event: DatesSetArg): void {
|
||||||
store.dispatch("fullCalendar/setCurrentDatesView", {
|
store.dispatch("fullCalendar/setCurrentDatesView", {
|
||||||
start: event.start,
|
start: event.start,
|
||||||
end: event.end,
|
end: event.end,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDateSelect(event: DateSelectArg): void {
|
function onDateSelect(event: DateSelectArg): void {
|
||||||
if (null === pickedLocation.value) {
|
if (null === pickedLocation.value) {
|
||||||
window.alert(
|
window.alert(
|
||||||
"Indiquez une localisation avant de créer une période de disponibilité.",
|
"Indiquez une localisation avant de créer une période de disponibilité.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch("calendarRanges/createRange", {
|
store.dispatch("calendarRanges/createRange", {
|
||||||
start: event.start,
|
start: event.start,
|
||||||
end: event.end,
|
end: event.end,
|
||||||
location: pickedLocation.value,
|
location: pickedLocation.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a calendar range is deleted
|
* When a calendar range is deleted
|
||||||
*/
|
*/
|
||||||
function onClickDelete(event: EventApi): void {
|
function onClickDelete(event: EventApi): void {
|
||||||
if (event.extendedProps.is !== "range") {
|
if (event.extendedProps.is !== "range") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
"calendarRanges/deleteRange",
|
"calendarRanges/deleteRange",
|
||||||
event.extendedProps.calendarRangeId,
|
event.extendedProps.calendarRangeId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
|
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
|
||||||
if (payload.event.extendedProps.is !== "range") {
|
if (payload.event.extendedProps.is !== "range") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch("calendarRanges/patchRangeTime", {
|
store.dispatch("calendarRanges/patchRangeTime", {
|
||||||
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
calendarRangeId: payload.event.extendedProps.calendarRangeId,
|
||||||
start: payload.event.start,
|
start: payload.event.start,
|
||||||
end: payload.event.end,
|
end: payload.event.end,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEventClick(payload: EventClickArg): void {
|
function onEventClick(payload: EventClickArg): void {
|
||||||
// @ts-ignore TS does not recognize the target. But it does exists.
|
// @ts-ignore TS does not recognize the target. But it does exists.
|
||||||
if (payload.jsEvent.target.classList.contains("delete")) {
|
if (payload.jsEvent.target.classList.contains("delete")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (payload.event.extendedProps.is !== "range") {
|
if (payload.event.extendedProps.is !== "range") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editLocation.value?.startEdit(payload.event);
|
editLocation.value?.startEdit(payload.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyDay() {
|
function copyDay() {
|
||||||
if (null === copyFrom.value || null === copyTo.value) {
|
if (null === copyFrom.value || null === copyTo.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
|
||||||
from: ISOToDate(copyFrom.value),
|
from: ISOToDate(copyFrom.value),
|
||||||
to: ISOToDate(copyTo.value),
|
to: ISOToDate(copyTo.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyWeek() {
|
function copyWeek() {
|
||||||
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
if (null === copyFromWeek.value || null === copyToWeek.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
|
||||||
fromMonday: ISOToDate(copyFromWeek.value),
|
fromMonday: ISOToDate(copyFromWeek.value),
|
||||||
toMonday: ISOToDate(copyToWeek.value),
|
toMonday: ISOToDate(copyToWeek.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
copyFromWeek.value = dateToISO(getMonday(0));
|
copyFromWeek.value = dateToISO(getMonday(0));
|
||||||
copyToWeek.value = dateToISO(getMonday(1));
|
copyToWeek.value = dateToISO(getMonday(1));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#copy-widget {
|
#copy-widget {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
z-index: 9999999999;
|
z-index: 9999999999;
|
||||||
padding: 0.25rem 0 0.25rem;
|
padding: 0.25rem 0 0.25rem;
|
||||||
}
|
}
|
||||||
div.copy-chevron {
|
div.copy-chevron {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,28 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Teleport" to="body">
|
<component :is="Teleport" to="body">
|
||||||
<modal v-if="showModal" @close="closeModal">
|
<modal v-if="showModal" @close="closeModal">
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h3>{{ "Modifier le lieu" }}</h3>
|
<h3>{{ "Modifier le lieu" }}</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<div></div>
|
<div></div>
|
||||||
<label>Localisation</label>
|
<label>Localisation</label>
|
||||||
<vue-multiselect
|
<vue-multiselect
|
||||||
v-model="location"
|
v-model="location"
|
||||||
:options="locations"
|
:options="locations"
|
||||||
:label="'name'"
|
:label="'name'"
|
||||||
:track-by="'id'"
|
:track-by="'id'"
|
||||||
></vue-multiselect>
|
></vue-multiselect>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:footer>
|
<template v-slot:footer>
|
||||||
<button class="btn btn-save" @click="saveAndClose">
|
<button class="btn btn-save" @click="saveAndClose">
|
||||||
{{ "Enregistrer" }}
|
{{ "Enregistrer" }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</modal>
|
</modal>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
|
|||||||
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
|
||||||
|
|
||||||
const Teleport = teleport_ as new () => {
|
const Teleport = teleport_ as new () => {
|
||||||
$props: VNodeProps & TeleportProps;
|
$props: VNodeProps & TeleportProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = useStore(key);
|
const store = useStore(key);
|
||||||
@@ -50,37 +50,37 @@ const showModal = ref(false);
|
|||||||
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
|
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
|
||||||
|
|
||||||
const locations = computed<Location[]>(() => {
|
const locations = computed<Location[]>(() => {
|
||||||
return store.state.locations.locations;
|
return store.state.locations.locations;
|
||||||
});
|
});
|
||||||
|
|
||||||
const startEdit = function (event: EventApi): void {
|
const startEdit = function (event: EventApi): void {
|
||||||
console.log("startEditing", event);
|
console.log("startEditing", event);
|
||||||
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
calendarRangeId.value = event.extendedProps.calendarRangeId;
|
||||||
location.value =
|
location.value =
|
||||||
store.getters["locations/getLocationById"](
|
store.getters["locations/getLocationById"](
|
||||||
event.extendedProps.locationId,
|
event.extendedProps.locationId,
|
||||||
) || null;
|
) || null;
|
||||||
|
|
||||||
console.log("new location value", location.value);
|
console.log("new location value", location.value);
|
||||||
console.log("calendar range id", calendarRangeId.value);
|
console.log("calendar range id", calendarRangeId.value);
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAndClose = function (e: Event): void {
|
const saveAndClose = function (e: Event): void {
|
||||||
console.log("saveEditAndClose", e);
|
console.log("saveEditAndClose", e);
|
||||||
|
|
||||||
store
|
store
|
||||||
.dispatch("calendarRanges/patchRangeLocation", {
|
.dispatch("calendarRanges/patchRangeLocation", {
|
||||||
location: location.value,
|
location: location.value,
|
||||||
calendarRangeId: calendarRangeId.value,
|
calendarRangeId: calendarRangeId.value,
|
||||||
})
|
})
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = function (_: any): void {
|
const closeModal = function (_: any): void {
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ startEdit });
|
defineExpose({ startEdit });
|
||||||
|
@@ -1,27 +1,27 @@
|
|||||||
const appMessages = {
|
const appMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
created_availabilities: "Lieu des plages de disponibilités créées",
|
created_availabilities: "Lieu des plages de disponibilités créées",
|
||||||
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
|
||||||
show_my_calendar: "Afficher mon calendrier",
|
show_my_calendar: "Afficher mon calendrier",
|
||||||
show_weekends: "Afficher les week-ends",
|
show_weekends: "Afficher les week-ends",
|
||||||
copy_range: "Copier",
|
copy_range: "Copier",
|
||||||
copy_range_from_to: "Copier les plages",
|
copy_range_from_to: "Copier les plages",
|
||||||
from_day_to_day: "d'un jour à l'autre",
|
from_day_to_day: "d'un jour à l'autre",
|
||||||
from_week_to_week: "d'une semaine à l'autre",
|
from_week_to_week: "d'une semaine à l'autre",
|
||||||
copy_range_how_to:
|
copy_range_how_to:
|
||||||
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
|
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
|
||||||
new_range_to_save: "Nouvelles plages à enregistrer",
|
new_range_to_save: "Nouvelles plages à enregistrer",
|
||||||
update_range_to_save: "Plages à modifier",
|
update_range_to_save: "Plages à modifier",
|
||||||
delete_range_to_save: "Plages à supprimer",
|
delete_range_to_save: "Plages à supprimer",
|
||||||
by: "Par",
|
by: "Par",
|
||||||
main_user_concerned: "Utilisateur concerné",
|
main_user_concerned: "Utilisateur concerné",
|
||||||
dateFrom: "De",
|
dateFrom: "De",
|
||||||
dateTo: "à",
|
dateTo: "à",
|
||||||
day: "Jour",
|
day: "Jour",
|
||||||
week: "Semaine",
|
week: "Semaine",
|
||||||
month: "Mois",
|
month: "Mois",
|
||||||
today: "Aujourd'hui",
|
today: "Aujourd'hui",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export { appMessages };
|
export { appMessages };
|
||||||
|
@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
futureStore().then((store) => {
|
futureStore().then((store) => {
|
||||||
const i18n = _createI18n(appMessages, false);
|
const i18n = _createI18n(appMessages, false);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app></app>`,
|
template: `<app></app>`,
|
||||||
})
|
})
|
||||||
.use(store, key)
|
.use(store, key)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component("app", App2)
|
.component("app", App2)
|
||||||
.mount("#myCalendar");
|
.mount("#myCalendar");
|
||||||
});
|
});
|
||||||
|
@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
|
|||||||
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
|
||||||
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
|
||||||
import calendarRemotes, {
|
import calendarRemotes, {
|
||||||
CalendarRemotesState,
|
CalendarRemotesState,
|
||||||
} from "./modules/calendarRemotes";
|
} from "./modules/calendarRemotes";
|
||||||
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||||
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
|
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
@@ -15,40 +15,42 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
|
|||||||
const debug = process.env.NODE_ENV !== "production";
|
const debug = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
calendarRanges: CalendarRangesState;
|
calendarRanges: CalendarRangesState;
|
||||||
calendarRemotes: CalendarRemotesState;
|
calendarRemotes: CalendarRemotesState;
|
||||||
calendarLocals: CalendarLocalsState;
|
calendarLocals: CalendarLocalsState;
|
||||||
fullCalendar: FullCalendarState;
|
fullCalendar: FullCalendarState;
|
||||||
me: MeState;
|
me: MeState;
|
||||||
locations: LocationState;
|
locations: LocationState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const key: InjectionKey<Store<State>> = Symbol();
|
export const key: InjectionKey<Store<State>> = Symbol();
|
||||||
|
|
||||||
const futureStore = function (): Promise<Store<State>> {
|
const futureStore = function (): Promise<Store<State>> {
|
||||||
return whoami().then((user: User) => {
|
return whoami().then((user: User) => {
|
||||||
const store = createStore<State>({
|
const store = createStore<State>({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
modules: {
|
modules: {
|
||||||
me,
|
me,
|
||||||
fullCalendar,
|
fullCalendar,
|
||||||
calendarRanges,
|
calendarRanges,
|
||||||
calendarRemotes,
|
calendarRemotes,
|
||||||
calendarLocals,
|
calendarLocals,
|
||||||
locations,
|
locations,
|
||||||
},
|
},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
store.commit("me/setWhoAmi", user, { root: true });
|
store.commit("me/setWhoAmi", user, { root: true });
|
||||||
store.dispatch("locations/getLocations", null, { root: true }).then((_) => {
|
store
|
||||||
return store.dispatch("locations/getCurrentLocation", null, {
|
.dispatch("locations/getLocations", null, { root: true })
|
||||||
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;
|
||||||
|
@@ -8,99 +8,109 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
|||||||
import { COLORS } from "../../../Calendar/const";
|
import { COLORS } from "../../../Calendar/const";
|
||||||
|
|
||||||
export interface CalendarLocalsState {
|
export interface CalendarLocalsState {
|
||||||
locals: EventInput[];
|
locals: EventInput[];
|
||||||
localsLoaded: { start: number; end: number }[];
|
localsLoaded: { start: number; end: number }[];
|
||||||
localsIndex: Set<string>;
|
localsIndex: Set<string>;
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarLocalsState, State>;
|
type Context = ActionContext<CalendarLocalsState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarLocalsState => ({
|
state: (): CalendarLocalsState => ({
|
||||||
locals: [],
|
locals: [],
|
||||||
localsLoaded: [],
|
localsLoaded: [],
|
||||||
localsIndex: new Set<string>(),
|
localsIndex: new Set<string>(),
|
||||||
key: 0,
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isLocalsLoaded:
|
isLocalsLoaded:
|
||||||
(state: CalendarLocalsState) =>
|
(state: CalendarLocalsState) =>
|
||||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||||
for (const range of state.localsLoaded) {
|
for (const range of state.localsLoaded) {
|
||||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
if (
|
||||||
return true;
|
start.getTime() === range.start &&
|
||||||
}
|
end.getTime() === range.end
|
||||||
}
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
|
||||||
console.log("addLocals", ranges);
|
|
||||||
|
|
||||||
const toAdd = ranges
|
|
||||||
.map((cr) => localsToFullCalendarEvent(cr))
|
|
||||||
.filter((r) => !state.localsIndex.has(r.id));
|
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
|
||||||
state.localsIndex.add(r.id);
|
|
||||||
state.locals.push(r);
|
|
||||||
});
|
|
||||||
state.key = state.key + toAdd.length;
|
|
||||||
},
|
},
|
||||||
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
|
mutations: {
|
||||||
state.localsLoaded.push({
|
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
|
||||||
start: payload.start.getTime(),
|
console.log("addLocals", ranges);
|
||||||
end: payload.end.getTime(),
|
|
||||||
});
|
const toAdd = ranges
|
||||||
|
.map((cr) => localsToFullCalendarEvent(cr))
|
||||||
|
.filter((r) => !state.localsIndex.has(r.id));
|
||||||
|
|
||||||
|
toAdd.forEach((r) => {
|
||||||
|
state.localsIndex.add(r.id);
|
||||||
|
state.locals.push(r);
|
||||||
|
});
|
||||||
|
state.key = state.key + toAdd.length;
|
||||||
|
},
|
||||||
|
addLoaded(
|
||||||
|
state: CalendarLocalsState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
) {
|
||||||
|
state.localsLoaded.push({
|
||||||
|
start: payload.start.getTime(),
|
||||||
|
end: payload.end.getTime(),
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
actions: {
|
||||||
actions: {
|
fetchLocals(
|
||||||
fetchLocals(
|
ctx: Context,
|
||||||
ctx: Context,
|
payload: { start: Date; end: Date },
|
||||||
payload: { start: Date; end: Date },
|
): Promise<null> {
|
||||||
): 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(ctx.rootGetters["me/getMe"], start, end)
|
return fetchCalendarLocalForUser(
|
||||||
.then((remotes: CalendarLight[]) => {
|
ctx.rootGetters["me/getMe"],
|
||||||
// to be add when reactivity problem will be solve ?
|
start,
|
||||||
//ctx.commit('addRemotes', remotes);
|
end,
|
||||||
const inputs = remotes
|
)
|
||||||
.map((cr) => localsToFullCalendarEvent(cr))
|
.then((remotes: CalendarLight[]) => {
|
||||||
.map((cr) => ({
|
// to be add when reactivity problem will be solve ?
|
||||||
...cr,
|
//ctx.commit('addRemotes', remotes);
|
||||||
backgroundColor: COLORS[0],
|
const inputs = remotes
|
||||||
textColor: "black",
|
.map((cr) => localsToFullCalendarEvent(cr))
|
||||||
editable: false,
|
.map((cr) => ({
|
||||||
}));
|
...cr,
|
||||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
backgroundColor: COLORS[0],
|
||||||
root: true,
|
textColor: "black",
|
||||||
});
|
editable: false,
|
||||||
return Promise.resolve(null);
|
}));
|
||||||
})
|
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||||
.catch((e: TransportExceptionInterface) => {
|
root: true,
|
||||||
console.error(e);
|
});
|
||||||
|
return Promise.resolve(null);
|
||||||
|
})
|
||||||
|
.catch((e: TransportExceptionInterface) => {
|
||||||
|
console.error(e);
|
||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
} as Module<CalendarLocalsState, State>;
|
} as Module<CalendarLocalsState, State>;
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { State } from "./../index";
|
import { State } from "./../index";
|
||||||
import { ActionContext, Module } from "vuex";
|
import { ActionContext, Module } from "vuex";
|
||||||
import {
|
import {
|
||||||
CalendarRange,
|
CalendarRange,
|
||||||
CalendarRangeCreate,
|
CalendarRangeCreate,
|
||||||
CalendarRangeEdit,
|
CalendarRangeEdit,
|
||||||
isEventInputCalendarRange,
|
isEventInputCalendarRange,
|
||||||
} from "../../../../types";
|
} from "../../../../types";
|
||||||
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||||
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
|
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
|
||||||
@@ -12,332 +12,369 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
|
|||||||
import { EventInput } from "@fullcalendar/core";
|
import { EventInput } from "@fullcalendar/core";
|
||||||
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||||
import {
|
import {
|
||||||
datetimeToISO,
|
datetimeToISO,
|
||||||
dateToISO,
|
dateToISO,
|
||||||
ISOToDatetime,
|
ISOToDatetime,
|
||||||
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||||
import type { EventInputCalendarRange } from "../../../../types";
|
import type { EventInputCalendarRange } from "../../../../types";
|
||||||
|
|
||||||
export interface CalendarRangesState {
|
export interface CalendarRangesState {
|
||||||
ranges: (EventInput | EventInputCalendarRange)[];
|
ranges: (EventInput | EventInputCalendarRange)[];
|
||||||
rangesLoaded: { start: number; end: number }[];
|
rangesLoaded: { start: number; end: number }[];
|
||||||
rangesIndex: Set<string>;
|
rangesIndex: Set<string>;
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarRangesState, State>;
|
type Context = ActionContext<CalendarRangesState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarRangesState => ({
|
state: (): CalendarRangesState => ({
|
||||||
ranges: [],
|
ranges: [],
|
||||||
rangesLoaded: [],
|
rangesLoaded: [],
|
||||||
rangesIndex: new Set<string>(),
|
rangesIndex: new Set<string>(),
|
||||||
key: 0,
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isRangeLoaded:
|
isRangeLoaded:
|
||||||
(state: CalendarRangesState) =>
|
(state: CalendarRangesState) =>
|
||||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||||
for (const range of state.rangesLoaded) {
|
for (const range of state.rangesLoaded) {
|
||||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
if (
|
||||||
return true;
|
start.getTime() === range.start &&
|
||||||
}
|
end.getTime() === range.end
|
||||||
}
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
getRangesOnDate:
|
getRangesOnDate:
|
||||||
(state: CalendarRangesState) =>
|
(state: CalendarRangesState) =>
|
||||||
(date: Date): EventInputCalendarRange[] => {
|
(date: Date): EventInputCalendarRange[] => {
|
||||||
const founds = [];
|
const founds = [];
|
||||||
const dateStr = dateToISO(date) as string;
|
const dateStr = dateToISO(date) as string;
|
||||||
|
|
||||||
for (const range of state.ranges) {
|
for (const range of state.ranges) {
|
||||||
if (
|
if (
|
||||||
isEventInputCalendarRange(range) &&
|
isEventInputCalendarRange(range) &&
|
||||||
range.start.startsWith(dateStr)
|
range.start.startsWith(dateStr)
|
||||||
) {
|
) {
|
||||||
founds.push(range);
|
founds.push(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return founds;
|
return founds;
|
||||||
},
|
},
|
||||||
getRangesOnWeek:
|
getRangesOnWeek:
|
||||||
(state: CalendarRangesState) =>
|
(state: CalendarRangesState) =>
|
||||||
(mondayDate: Date): EventInputCalendarRange[] => {
|
(mondayDate: Date): EventInputCalendarRange[] => {
|
||||||
const founds = [];
|
const founds = [];
|
||||||
for (const d of Array.from(Array(7).keys())) {
|
for (const d of Array.from(Array(7).keys())) {
|
||||||
const dateOfWeek = new Date(mondayDate);
|
const dateOfWeek = new Date(mondayDate);
|
||||||
dateOfWeek.setDate(mondayDate.getDate() + d);
|
dateOfWeek.setDate(mondayDate.getDate() + d);
|
||||||
const dateStr = dateToISO(dateOfWeek) as string;
|
const dateStr = dateToISO(dateOfWeek) as string;
|
||||||
for (const range of state.ranges) {
|
for (const range of state.ranges) {
|
||||||
if (
|
if (
|
||||||
isEventInputCalendarRange(range) &&
|
isEventInputCalendarRange(range) &&
|
||||||
range.start.startsWith(dateStr)
|
range.start.startsWith(dateStr)
|
||||||
) {
|
) {
|
||||||
founds.push(range);
|
founds.push(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return founds;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
||||||
|
const toAdd = ranges
|
||||||
|
.map((cr) => calendarRangeToFullCalendarEvent(cr))
|
||||||
|
.map((cr) => ({
|
||||||
|
...cr,
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderColor: "#3788d8",
|
||||||
|
textColor: "black",
|
||||||
|
}))
|
||||||
|
.filter((r) => !state.rangesIndex.has(r.id));
|
||||||
|
|
||||||
|
toAdd.forEach((r) => {
|
||||||
|
state.rangesIndex.add(r.id);
|
||||||
|
state.ranges.push(r);
|
||||||
|
});
|
||||||
|
state.key = state.key + toAdd.length;
|
||||||
|
},
|
||||||
|
addExternals(
|
||||||
|
state: CalendarRangesState,
|
||||||
|
externalEvents: (EventInput & { id: string })[],
|
||||||
|
) {
|
||||||
|
const toAdd = externalEvents.filter(
|
||||||
|
(r) => !state.rangesIndex.has(r.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
toAdd.forEach((r) => {
|
||||||
|
state.rangesIndex.add(r.id);
|
||||||
|
state.ranges.push(r);
|
||||||
|
});
|
||||||
|
state.key = state.key + toAdd.length;
|
||||||
|
},
|
||||||
|
addLoaded(
|
||||||
|
state: CalendarRangesState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
) {
|
||||||
|
state.rangesLoaded.push({
|
||||||
|
start: payload.start.getTime(),
|
||||||
|
end: payload.end.getTime(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
||||||
|
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
||||||
|
state.ranges.push({
|
||||||
|
...asEvent,
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderColor: "#3788d8",
|
||||||
|
textColor: "black",
|
||||||
|
});
|
||||||
|
state.rangesIndex.add(asEvent.id);
|
||||||
|
state.key = state.key + 1;
|
||||||
|
},
|
||||||
|
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
||||||
|
const found = state.ranges.find(
|
||||||
|
(r) =>
|
||||||
|
r.calendarRangeId === calendarRangeId && r.is === "range",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (found !== undefined) {
|
||||||
|
state.ranges = state.ranges.filter(
|
||||||
|
(r) =>
|
||||||
|
!(
|
||||||
|
r.calendarRangeId === calendarRangeId &&
|
||||||
|
r.is === "range"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof found.id === "string") {
|
||||||
|
// should always be true
|
||||||
|
state.rangesIndex.delete(found.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.key = state.key + 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return founds;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
|
|
||||||
const toAdd = ranges
|
|
||||||
.map((cr) => calendarRangeToFullCalendarEvent(cr))
|
|
||||||
.map((cr) => ({
|
|
||||||
...cr,
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderColor: "#3788d8",
|
|
||||||
textColor: "black",
|
|
||||||
}))
|
|
||||||
.filter((r) => !state.rangesIndex.has(r.id));
|
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
|
||||||
state.rangesIndex.add(r.id);
|
|
||||||
state.ranges.push(r);
|
|
||||||
});
|
|
||||||
state.key = state.key + toAdd.length;
|
|
||||||
},
|
|
||||||
addExternals(
|
|
||||||
state: CalendarRangesState,
|
|
||||||
externalEvents: (EventInput & { id: string })[],
|
|
||||||
) {
|
|
||||||
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
|
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
|
||||||
state.rangesIndex.add(r.id);
|
|
||||||
state.ranges.push(r);
|
|
||||||
});
|
|
||||||
state.key = state.key + toAdd.length;
|
|
||||||
},
|
|
||||||
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
|
|
||||||
state.rangesLoaded.push({
|
|
||||||
start: payload.start.getTime(),
|
|
||||||
end: payload.end.getTime(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
addRange(state: CalendarRangesState, payload: CalendarRange) {
|
|
||||||
const asEvent = calendarRangeToFullCalendarEvent(payload);
|
|
||||||
state.ranges.push({
|
|
||||||
...asEvent,
|
|
||||||
backgroundColor: "white",
|
|
||||||
borderColor: "#3788d8",
|
|
||||||
textColor: "black",
|
|
||||||
});
|
|
||||||
state.rangesIndex.add(asEvent.id);
|
|
||||||
state.key = state.key + 1;
|
|
||||||
},
|
|
||||||
removeRange(state: CalendarRangesState, calendarRangeId: number) {
|
|
||||||
const found = state.ranges.find(
|
|
||||||
(r) => r.calendarRangeId === calendarRangeId && r.is === "range",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (found !== undefined) {
|
|
||||||
state.ranges = state.ranges.filter(
|
|
||||||
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (typeof found.id === "string") {
|
|
||||||
// should always be true
|
|
||||||
state.rangesIndex.delete(found.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.key = state.key + 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
|
||||||
const found = state.ranges.find(
|
|
||||||
(r) => r.calendarRangeId === range.id && r.is === "range",
|
|
||||||
);
|
|
||||||
const newEvent = calendarRangeToFullCalendarEvent(range);
|
|
||||||
|
|
||||||
if (found !== undefined) {
|
|
||||||
found.start = newEvent.start;
|
|
||||||
found.end = newEvent.end;
|
|
||||||
found.locationId = range.location.id;
|
|
||||||
found.locationName = range.location.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.key = state.key + 1;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
fetchRanges(
|
|
||||||
ctx: Context,
|
|
||||||
payload: { start: Date; end: Date },
|
|
||||||
): Promise<null> {
|
|
||||||
const start = payload.start;
|
|
||||||
const end = payload.end;
|
|
||||||
|
|
||||||
if (ctx.rootGetters["me/getMe"] === null) {
|
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.getters.isRangeLoaded({ start, end })) {
|
|
||||||
return Promise.resolve(ctx.getters.getRangeSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.commit("addLoaded", {
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
});
|
|
||||||
|
|
||||||
return fetchCalendarRangeForUser(
|
|
||||||
ctx.rootGetters["me/getMe"],
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
).then((ranges: CalendarRange[]) => {
|
|
||||||
ctx.commit("addRanges", ranges);
|
|
||||||
return Promise.resolve(null);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
createRange(
|
|
||||||
ctx: Context,
|
|
||||||
{ start, end, location }: { start: Date; end: Date; location: Location },
|
|
||||||
): Promise<null> {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range.json?`;
|
|
||||||
|
|
||||||
if (ctx.rootState.me.me === null) {
|
|
||||||
throw new Error("user is currently null");
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
user: {
|
|
||||||
id: ctx.rootState.me.me.id,
|
|
||||||
type: "user",
|
|
||||||
},
|
},
|
||||||
startDate: {
|
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
||||||
datetime: datetimeToISO(start),
|
const found = state.ranges.find(
|
||||||
|
(r) => r.calendarRangeId === range.id && r.is === "range",
|
||||||
|
);
|
||||||
|
const newEvent = calendarRangeToFullCalendarEvent(range);
|
||||||
|
|
||||||
|
if (found !== undefined) {
|
||||||
|
found.start = newEvent.start;
|
||||||
|
found.end = newEvent.end;
|
||||||
|
found.locationId = range.location.id;
|
||||||
|
found.locationName = range.location.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.key = state.key + 1;
|
||||||
},
|
},
|
||||||
endDate: {
|
},
|
||||||
datetime: datetimeToISO(end),
|
actions: {
|
||||||
|
fetchRanges(
|
||||||
|
ctx: Context,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const start = payload.start;
|
||||||
|
const end = payload.end;
|
||||||
|
|
||||||
|
if (ctx.rootGetters["me/getMe"] === null) {
|
||||||
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.getters.isRangeLoaded({ start, end })) {
|
||||||
|
return Promise.resolve(ctx.getters.getRangeSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.commit("addLoaded", {
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetchCalendarRangeForUser(
|
||||||
|
ctx.rootGetters["me/getMe"],
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
).then((ranges: CalendarRange[]) => {
|
||||||
|
ctx.commit("addRanges", ranges);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
location: {
|
createRange(
|
||||||
id: location.id,
|
ctx: Context,
|
||||||
type: "location",
|
{
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
location,
|
||||||
|
}: { start: Date; end: Date; location: Location },
|
||||||
|
): Promise<null> {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range.json?`;
|
||||||
|
|
||||||
|
if (ctx.rootState.me.me === null) {
|
||||||
|
throw new Error("user is currently null");
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
user: {
|
||||||
|
id: ctx.rootState.me.me.id,
|
||||||
|
type: "user",
|
||||||
|
},
|
||||||
|
startDate: {
|
||||||
|
datetime: datetimeToISO(start),
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
datetime: datetimeToISO(end),
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
id: location.id,
|
||||||
|
type: "location",
|
||||||
|
},
|
||||||
|
} as CalendarRangeCreate;
|
||||||
|
|
||||||
|
return makeFetch<CalendarRangeCreate, CalendarRange>(
|
||||||
|
"POST",
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
.then((newRange) => {
|
||||||
|
ctx.commit("addRange", newRange);
|
||||||
|
|
||||||
|
return Promise.resolve(null);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
} as CalendarRangeCreate;
|
deleteRange(ctx: Context, calendarRangeId: number) {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
|
||||||
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body)
|
makeFetch<undefined, never>("DELETE", url).then(() => {
|
||||||
.then((newRange) => {
|
ctx.commit("removeRange", calendarRangeId);
|
||||||
ctx.commit("addRange", newRange);
|
});
|
||||||
|
|
||||||
return Promise.resolve(null);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteRange(ctx: Context, calendarRangeId: number) {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
|
||||||
|
|
||||||
makeFetch<undefined, never>("DELETE", url).then(() => {
|
|
||||||
ctx.commit("removeRange", calendarRangeId);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
patchRangeTime(
|
|
||||||
ctx,
|
|
||||||
{
|
|
||||||
calendarRangeId,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
}: { calendarRangeId: number; start: Date; end: Date },
|
|
||||||
): Promise<null> {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
|
||||||
const body = {
|
|
||||||
startDate: {
|
|
||||||
datetime: datetimeToISO(start),
|
|
||||||
},
|
},
|
||||||
endDate: {
|
patchRangeTime(
|
||||||
datetime: datetimeToISO(end),
|
ctx,
|
||||||
|
{
|
||||||
|
calendarRangeId,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
}: { calendarRangeId: number; start: Date; end: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
const body = {
|
||||||
|
startDate: {
|
||||||
|
datetime: datetimeToISO(start),
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
datetime: datetimeToISO(end),
|
||||||
|
},
|
||||||
|
} as CalendarRangeEdit;
|
||||||
|
|
||||||
|
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||||
|
"PATCH",
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
.then((range) => {
|
||||||
|
ctx.commit("updateRange", range);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
} as CalendarRangeEdit;
|
patchRangeLocation(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
location,
|
||||||
|
calendarRangeId,
|
||||||
|
}: { location: Location; calendarRangeId: number },
|
||||||
|
): Promise<null> {
|
||||||
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
const body = {
|
||||||
|
location: {
|
||||||
|
id: location.id,
|
||||||
|
type: "location",
|
||||||
|
},
|
||||||
|
} as CalendarRangeEdit;
|
||||||
|
|
||||||
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
return makeFetch<CalendarRangeEdit, CalendarRange>(
|
||||||
.then((range) => {
|
"PATCH",
|
||||||
ctx.commit("updateRange", range);
|
url,
|
||||||
return Promise.resolve(null);
|
body,
|
||||||
})
|
)
|
||||||
.catch((error) => {
|
.then((range) => {
|
||||||
console.error(error);
|
ctx.commit("updateRange", range);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
})
|
||||||
},
|
.catch((error) => {
|
||||||
patchRangeLocation(
|
console.error(error);
|
||||||
ctx,
|
return Promise.resolve(null);
|
||||||
{
|
});
|
||||||
location,
|
|
||||||
calendarRangeId,
|
|
||||||
}: { location: Location; calendarRangeId: number },
|
|
||||||
): Promise<null> {
|
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
|
||||||
const body = {
|
|
||||||
location: {
|
|
||||||
id: location.id,
|
|
||||||
type: "location",
|
|
||||||
},
|
},
|
||||||
} as CalendarRangeEdit;
|
copyFromDayToAnotherDay(
|
||||||
|
ctx,
|
||||||
|
{ from, to }: { from: Date; to: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
|
ctx.getters["getRangesOnDate"](from);
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
|
for (const r of rangesToCopy) {
|
||||||
.then((range) => {
|
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||||
ctx.commit("updateRange", range);
|
start.setFullYear(
|
||||||
return Promise.resolve(null);
|
to.getFullYear(),
|
||||||
})
|
to.getMonth(),
|
||||||
.catch((error) => {
|
to.getDate(),
|
||||||
console.error(error);
|
);
|
||||||
return Promise.resolve(null);
|
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||||
});
|
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
||||||
|
const location = ctx.rootGetters["locations/getLocationById"](
|
||||||
|
r.locationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
promises.push(
|
||||||
|
ctx.dispatch("createRange", { start, end, location }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
|
},
|
||||||
|
copyFromWeekToAnotherWeek(
|
||||||
|
ctx: Context,
|
||||||
|
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
||||||
|
): Promise<null> {
|
||||||
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
|
ctx.getters["getRangesOnWeek"](fromMonday);
|
||||||
|
const promises = [];
|
||||||
|
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
||||||
|
for (const r of rangesToCopy) {
|
||||||
|
const start = new Date(ISOToDatetime(r.start) as Date);
|
||||||
|
const end = new Date(ISOToDatetime(r.end) as Date);
|
||||||
|
start.setTime(start.getTime() + diffTime);
|
||||||
|
end.setTime(end.getTime() + diffTime);
|
||||||
|
const location = ctx.rootGetters["locations/getLocationById"](
|
||||||
|
r.locationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
promises.push(
|
||||||
|
ctx.dispatch("createRange", { start, end, location }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
copyFromDayToAnotherDay(
|
|
||||||
ctx,
|
|
||||||
{ from, to }: { from: Date; to: Date },
|
|
||||||
): Promise<null> {
|
|
||||||
const rangesToCopy: EventInputCalendarRange[] =
|
|
||||||
ctx.getters["getRangesOnDate"](from);
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for (const r of rangesToCopy) {
|
|
||||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
|
||||||
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
|
||||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
|
||||||
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
|
|
||||||
const location = ctx.rootGetters["locations/getLocationById"](
|
|
||||||
r.locationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(ctx.dispatch("createRange", { start, end, location }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
|
||||||
},
|
|
||||||
copyFromWeekToAnotherWeek(
|
|
||||||
ctx: Context,
|
|
||||||
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
|
||||||
): Promise<null> {
|
|
||||||
const rangesToCopy: EventInputCalendarRange[] =
|
|
||||||
ctx.getters["getRangesOnWeek"](fromMonday);
|
|
||||||
const promises = [];
|
|
||||||
const diffTime = toMonday.getTime() - fromMonday.getTime();
|
|
||||||
for (const r of rangesToCopy) {
|
|
||||||
const start = new Date(ISOToDatetime(r.start) as Date);
|
|
||||||
const end = new Date(ISOToDatetime(r.end) as Date);
|
|
||||||
start.setTime(start.getTime() + diffTime);
|
|
||||||
end.setTime(end.getTime() + diffTime);
|
|
||||||
const location = ctx.rootGetters["locations/getLocationById"](
|
|
||||||
r.locationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(ctx.dispatch("createRange", { start, end, location }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises).then(() => Promise.resolve(null));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as Module<CalendarRangesState, State>;
|
} as Module<CalendarRangesState, State>;
|
||||||
|
@@ -8,102 +8,109 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
|
|||||||
import { COLORS } from "../../../Calendar/const";
|
import { COLORS } from "../../../Calendar/const";
|
||||||
|
|
||||||
export interface CalendarRemotesState {
|
export interface CalendarRemotesState {
|
||||||
remotes: EventInput[];
|
remotes: EventInput[];
|
||||||
remotesLoaded: { start: number; end: number }[];
|
remotesLoaded: { start: number; end: number }[];
|
||||||
remotesIndex: Set<string>;
|
remotesIndex: Set<string>;
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<CalendarRemotesState, State>;
|
type Context = ActionContext<CalendarRemotesState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): CalendarRemotesState => ({
|
state: (): CalendarRemotesState => ({
|
||||||
remotes: [],
|
remotes: [],
|
||||||
remotesLoaded: [],
|
remotesLoaded: [],
|
||||||
remotesIndex: new Set<string>(),
|
remotesIndex: new Set<string>(),
|
||||||
key: 0,
|
key: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isRemotesLoaded:
|
isRemotesLoaded:
|
||||||
(state: CalendarRemotesState) =>
|
(state: CalendarRemotesState) =>
|
||||||
({ start, end }: { start: Date; end: Date }): boolean => {
|
({ start, end }: { start: Date; end: Date }): boolean => {
|
||||||
for (const range of state.remotesLoaded) {
|
for (const range of state.remotesLoaded) {
|
||||||
if (start.getTime() === range.start && end.getTime() === range.end) {
|
if (
|
||||||
return true;
|
start.getTime() === range.start &&
|
||||||
}
|
end.getTime() === range.end
|
||||||
}
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
|
||||||
console.log("addRemotes", ranges);
|
|
||||||
|
|
||||||
const toAdd = ranges
|
|
||||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
|
||||||
.filter((r) => !state.remotesIndex.has(r.id));
|
|
||||||
|
|
||||||
toAdd.forEach((r) => {
|
|
||||||
state.remotesIndex.add(r.id);
|
|
||||||
state.remotes.push(r);
|
|
||||||
});
|
|
||||||
state.key = state.key + toAdd.length;
|
|
||||||
},
|
},
|
||||||
addLoaded(
|
mutations: {
|
||||||
state: CalendarRemotesState,
|
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
|
||||||
payload: { start: Date; end: Date },
|
console.log("addRemotes", ranges);
|
||||||
) {
|
|
||||||
state.remotesLoaded.push({
|
const toAdd = ranges
|
||||||
start: payload.start.getTime(),
|
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||||
end: payload.end.getTime(),
|
.filter((r) => !state.remotesIndex.has(r.id));
|
||||||
});
|
|
||||||
|
toAdd.forEach((r) => {
|
||||||
|
state.remotesIndex.add(r.id);
|
||||||
|
state.remotes.push(r);
|
||||||
|
});
|
||||||
|
state.key = state.key + toAdd.length;
|
||||||
|
},
|
||||||
|
addLoaded(
|
||||||
|
state: CalendarRemotesState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
) {
|
||||||
|
state.remotesLoaded.push({
|
||||||
|
start: payload.start.getTime(),
|
||||||
|
end: payload.end.getTime(),
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
actions: {
|
||||||
actions: {
|
fetchRemotes(
|
||||||
fetchRemotes(
|
ctx: Context,
|
||||||
ctx: Context,
|
payload: { start: Date; end: Date },
|
||||||
payload: { start: Date; end: Date },
|
): Promise<null> {
|
||||||
): 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(ctx.rootGetters["me/getMe"], start, end)
|
return fetchCalendarRemoteForUser(
|
||||||
.then((remotes: CalendarRemote[]) => {
|
ctx.rootGetters["me/getMe"],
|
||||||
// to be add when reactivity problem will be solve ?
|
start,
|
||||||
//ctx.commit('addRemotes', remotes);
|
end,
|
||||||
const inputs = remotes
|
)
|
||||||
.map((cr) => remoteToFullCalendarEvent(cr))
|
.then((remotes: CalendarRemote[]) => {
|
||||||
.map((cr) => ({
|
// to be add when reactivity problem will be solve ?
|
||||||
...cr,
|
//ctx.commit('addRemotes', remotes);
|
||||||
backgroundColor: COLORS[0],
|
const inputs = remotes
|
||||||
textColor: "black",
|
.map((cr) => remoteToFullCalendarEvent(cr))
|
||||||
editable: false,
|
.map((cr) => ({
|
||||||
}));
|
...cr,
|
||||||
ctx.commit("calendarRanges/addExternals", inputs, {
|
backgroundColor: COLORS[0],
|
||||||
root: true,
|
textColor: "black",
|
||||||
});
|
editable: false,
|
||||||
return Promise.resolve(null);
|
}));
|
||||||
})
|
ctx.commit("calendarRanges/addExternals", inputs, {
|
||||||
.catch((e: TransportExceptionInterface) => {
|
root: true,
|
||||||
console.error(e);
|
});
|
||||||
|
return Promise.resolve(null);
|
||||||
|
})
|
||||||
|
.catch((e: TransportExceptionInterface) => {
|
||||||
|
console.error(e);
|
||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
} as Module<CalendarRemotesState, State>;
|
} as Module<CalendarRemotesState, State>;
|
||||||
|
@@ -2,77 +2,77 @@ import { State } from "./../index";
|
|||||||
import { ActionContext } from "vuex";
|
import { ActionContext } from "vuex";
|
||||||
|
|
||||||
export interface FullCalendarState {
|
export interface FullCalendarState {
|
||||||
currentView: {
|
currentView: {
|
||||||
start: Date | null;
|
start: Date | null;
|
||||||
end: Date | null;
|
end: Date | null;
|
||||||
};
|
};
|
||||||
key: number;
|
key: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<FullCalendarState, State>;
|
type Context = ActionContext<FullCalendarState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): FullCalendarState => ({
|
state: (): FullCalendarState => ({
|
||||||
currentView: {
|
currentView: {
|
||||||
start: null,
|
start: null,
|
||||||
end: null,
|
end: null,
|
||||||
|
},
|
||||||
|
key: 0,
|
||||||
|
}),
|
||||||
|
mutations: {
|
||||||
|
setCurrentDatesView: function (
|
||||||
|
state: FullCalendarState,
|
||||||
|
payload: { start: Date; end: Date },
|
||||||
|
): void {
|
||||||
|
state.currentView.start = payload.start;
|
||||||
|
state.currentView.end = payload.end;
|
||||||
|
},
|
||||||
|
increaseKey: function (state: FullCalendarState): void {
|
||||||
|
state.key = state.key + 1;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
key: 0,
|
actions: {
|
||||||
}),
|
setCurrentDatesView(
|
||||||
mutations: {
|
ctx: Context,
|
||||||
setCurrentDatesView: function (
|
{ start, end }: { start: Date | null; end: Date | null },
|
||||||
state: FullCalendarState,
|
): Promise<null> {
|
||||||
payload: { start: Date; end: Date },
|
console.log("dispatch setCurrentDatesView", { start, end });
|
||||||
): void {
|
|
||||||
state.currentView.start = payload.start;
|
|
||||||
state.currentView.end = payload.end;
|
|
||||||
},
|
|
||||||
increaseKey: function (state: FullCalendarState): void {
|
|
||||||
state.key = state.key + 1;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
setCurrentDatesView(
|
|
||||||
ctx: Context,
|
|
||||||
{ start, end }: { start: Date | null; end: Date | null },
|
|
||||||
): Promise<null> {
|
|
||||||
console.log("dispatch setCurrentDatesView", { start, end });
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ctx.state.currentView.start !== start ||
|
ctx.state.currentView.start !== start ||
|
||||||
ctx.state.currentView.end !== end
|
ctx.state.currentView.end !== end
|
||||||
) {
|
) {
|
||||||
ctx.commit("setCurrentDatesView", { start, end });
|
ctx.commit("setCurrentDatesView", { start, end });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start !== null && end !== null) {
|
if (start !== null && end !== null) {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
ctx
|
ctx
|
||||||
.dispatch(
|
.dispatch(
|
||||||
"calendarRanges/fetchRanges",
|
"calendarRanges/fetchRanges",
|
||||||
{ start, end },
|
{ start, end },
|
||||||
{ root: true },
|
{ root: true },
|
||||||
)
|
)
|
||||||
.then((_) => Promise.resolve(null)),
|
.then((_) => Promise.resolve(null)),
|
||||||
ctx
|
ctx
|
||||||
.dispatch(
|
.dispatch(
|
||||||
"calendarRemotes/fetchRemotes",
|
"calendarRemotes/fetchRemotes",
|
||||||
{ start, end },
|
{ start, end },
|
||||||
{ root: true },
|
{ root: true },
|
||||||
)
|
)
|
||||||
.then((_) => Promise.resolve(null)),
|
.then((_) => Promise.resolve(null)),
|
||||||
ctx
|
ctx
|
||||||
.dispatch(
|
.dispatch(
|
||||||
"calendarLocals/fetchLocals",
|
"calendarLocals/fetchLocals",
|
||||||
{ start, end },
|
{ start, end },
|
||||||
{ root: true },
|
{ root: true },
|
||||||
)
|
)
|
||||||
.then((_) => Promise.resolve(null)),
|
.then((_) => Promise.resolve(null)),
|
||||||
]).then((_) => Promise.resolve(null));
|
]).then((_) => Promise.resolve(null));
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@@ -5,61 +5,61 @@ import { getLocations } from "../../../../../../../ChillMainBundle/Resources/pub
|
|||||||
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
|
||||||
|
|
||||||
export interface LocationState {
|
export interface LocationState {
|
||||||
locations: Location[];
|
locations: Location[];
|
||||||
locationPicked: Location | null;
|
locationPicked: Location | null;
|
||||||
currentLocation: Location | null;
|
currentLocation: Location | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): LocationState => {
|
state: (): LocationState => {
|
||||||
return {
|
return {
|
||||||
locations: [],
|
locations: [],
|
||||||
locationPicked: null,
|
locationPicked: null,
|
||||||
currentLocation: null,
|
currentLocation: null,
|
||||||
};
|
};
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
getLocationById:
|
|
||||||
(state) =>
|
|
||||||
(id: number): Location | undefined => {
|
|
||||||
return state.locations.find((l) => l.id === id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
setLocations(state, locations): void {
|
|
||||||
state.locations = locations;
|
|
||||||
},
|
},
|
||||||
setLocationPicked(state, location: Location | null): void {
|
getters: {
|
||||||
if (null === location) {
|
getLocationById:
|
||||||
state.locationPicked = null;
|
(state) =>
|
||||||
return;
|
(id: number): Location | undefined => {
|
||||||
}
|
return state.locations.find((l) => l.id === id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setLocations(state, locations): void {
|
||||||
|
state.locations = locations;
|
||||||
|
},
|
||||||
|
setLocationPicked(state, location: Location | null): void {
|
||||||
|
if (null === location) {
|
||||||
|
state.locationPicked = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
state.locationPicked =
|
state.locationPicked =
|
||||||
state.locations.find((l) => l.id === location.id) || null;
|
state.locations.find((l) => l.id === location.id) || null;
|
||||||
},
|
},
|
||||||
setCurrentLocation(state, location: Location | null): void {
|
setCurrentLocation(state, location: Location | null): void {
|
||||||
if (null === location) {
|
if (null === location) {
|
||||||
state.currentLocation = null;
|
state.currentLocation = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.currentLocation =
|
state.currentLocation =
|
||||||
state.locations.find((l) => l.id === location.id) || null;
|
state.locations.find((l) => l.id === location.id) || null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
actions: {
|
||||||
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> {
|
||||||
|
return whereami().then((location) => {
|
||||||
|
ctx.commit("setCurrentLocation", location);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
getCurrentLocation(ctx): Promise<void> {
|
|
||||||
return whereami().then((location) => {
|
|
||||||
ctx.commit("setCurrentLocation", location);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as Module<LocationState, State>;
|
} as Module<LocationState, State>;
|
||||||
|
@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
|
|||||||
import { ActionContext } from "vuex";
|
import { ActionContext } from "vuex";
|
||||||
|
|
||||||
export interface MeState {
|
export interface MeState {
|
||||||
me: User | null;
|
me: User | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context = ActionContext<MeState, State>;
|
type Context = ActionContext<MeState, State>;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: (): MeState => ({
|
state: (): MeState => ({
|
||||||
me: null,
|
me: null,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getMe: function (state: MeState): User | null {
|
getMe: function (state: MeState): User | null {
|
||||||
return state.me;
|
return state.me;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
mutations: {
|
||||||
mutations: {
|
setWhoAmi(state: MeState, me: User) {
|
||||||
setWhoAmi(state: MeState, me: User) {
|
state.me = me;
|
||||||
state.me = me;
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@@ -1,51 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="chill-red">
|
<h2 class="chill-red">
|
||||||
{{ $t("choose_your_calendar_user") }}
|
{{ $t("choose_your_calendar_user") }}
|
||||||
</h2>
|
</h2>
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="field"
|
name="field"
|
||||||
id="calendarUserSelector"
|
id="calendarUserSelector"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
label="value"
|
label="value"
|
||||||
:custom-label="transName"
|
:custom-label="transName"
|
||||||
:placeholder="$t('select_user')"
|
:placeholder="$t('select_user')"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:close-on-select="false"
|
:close-on-select="false"
|
||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
:model-value="value"
|
:model-value="value"
|
||||||
:select-label="$t('multiselect.select_label')"
|
:select-label="$t('multiselect.select_label')"
|
||||||
:deselect-label="$t('multiselect.deselect_label')"
|
:deselect-label="$t('multiselect.deselect_label')"
|
||||||
:selected-label="$t('multiselect.selected_label')"
|
:selected-label="$t('multiselect.selected_label')"
|
||||||
@select="selectUsers"
|
@select="selectUsers"
|
||||||
@remove="unSelectUsers"
|
@remove="unSelectUsers"
|
||||||
@close="coloriseSelectedValues"
|
@close="coloriseSelectedValues"
|
||||||
:options="options"
|
:options="options"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="myCalendar"
|
id="myCalendar"
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
v-model="showMyCalendarWidget"
|
v-model="showMyCalendarWidget"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" for="myCalendar">{{
|
<label class="form-check-label" for="myCalendar">{{
|
||||||
$t("show_my_calendar")
|
$t("show_my_calendar")
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="weekends"
|
id="weekends"
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
@click="toggleWeekends"
|
@click="toggleWeekends"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" for="weekends">{{
|
<label class="form-check-label" for="weekends">{{
|
||||||
$t("show_weekends")
|
$t("show_weekends")
|
||||||
}}</label>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
|
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
|
||||||
@@ -53,183 +53,206 @@ import VueMultiselect from "vue-multiselect";
|
|||||||
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
|
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
|
||||||
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
|
||||||
"#8dd3c7",
|
"#8dd3c7",
|
||||||
"#ffffb3",
|
"#ffffb3",
|
||||||
"#bebada",
|
"#bebada",
|
||||||
"#fb8072",
|
"#fb8072",
|
||||||
"#80b1d3",
|
"#80b1d3",
|
||||||
"#fdb462",
|
"#fdb462",
|
||||||
"#b3de69",
|
"#b3de69",
|
||||||
"#fccde5",
|
"#fccde5",
|
||||||
"#d9d9d9",
|
"#d9d9d9",
|
||||||
"#bc80bd",
|
"#bc80bd",
|
||||||
"#ccebc5",
|
"#ccebc5",
|
||||||
"#ffed6f",
|
"#ffed6f",
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CalendarUserSelector",
|
name: "CalendarUserSelector",
|
||||||
components: { VueMultiselect },
|
components: { VueMultiselect },
|
||||||
props: [
|
props: [
|
||||||
"users",
|
"users",
|
||||||
"updateEventsSource",
|
"updateEventsSource",
|
||||||
"calendarEvents",
|
"calendarEvents",
|
||||||
"showMyCalendar",
|
"showMyCalendar",
|
||||||
"toggleMyCalendar",
|
"toggleMyCalendar",
|
||||||
"toggleWeekends",
|
"toggleWeekends",
|
||||||
],
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
errorMsg: [],
|
errorMsg: [],
|
||||||
value: [],
|
value: [],
|
||||||
options: [],
|
options: [],
|
||||||
};
|
};
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
showMyCalendarWidget: {
|
|
||||||
set(value) {
|
|
||||||
this.toggleMyCalendar(value);
|
|
||||||
this.updateEventsSource();
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.showMyCalendar;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
computed: {
|
||||||
methods: {
|
showMyCalendarWidget: {
|
||||||
init() {
|
set(value) {
|
||||||
this.fetchData();
|
this.toggleMyCalendar(value);
|
||||||
|
this.updateEventsSource();
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return this.showMyCalendar;
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fetchData() {
|
methods: {
|
||||||
fetchCalendarRanges()
|
init() {
|
||||||
.then(
|
this.fetchData();
|
||||||
(calendarRanges) =>
|
},
|
||||||
new Promise((resolve, reject) => {
|
fetchData() {
|
||||||
let results = calendarRanges.results;
|
fetchCalendarRanges()
|
||||||
|
.then(
|
||||||
let users = [];
|
(calendarRanges) =>
|
||||||
|
|
||||||
results.forEach((i) => {
|
|
||||||
if (!users.some((j) => i.user.id === j.id)) {
|
|
||||||
let ratio = Math.floor(users.length / COLORS.length);
|
|
||||||
let colorIndex = users.length - ratio * COLORS.length;
|
|
||||||
users.push({
|
|
||||||
id: i.user.id,
|
|
||||||
username: i.user.username,
|
|
||||||
color: COLORS[colorIndex],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let calendarEvents = [];
|
|
||||||
users.forEach((u) => {
|
|
||||||
let arr = results
|
|
||||||
.filter((i) => i.user.id === u.id)
|
|
||||||
.map((i) => ({
|
|
||||||
start: i.startDate.datetime,
|
|
||||||
end: i.endDate.datetime,
|
|
||||||
calendarRangeId: i.id,
|
|
||||||
sourceColor: u.color,
|
|
||||||
//display: 'background' // can be an option for the disponibility
|
|
||||||
}));
|
|
||||||
calendarEvents.push({
|
|
||||||
events: arr,
|
|
||||||
color: u.color,
|
|
||||||
textColor: "#444444",
|
|
||||||
editable: false,
|
|
||||||
id: u.id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.users.loaded = users;
|
|
||||||
this.options = users;
|
|
||||||
|
|
||||||
this.calendarEvents.loaded = calendarEvents;
|
|
||||||
whoami().then(
|
|
||||||
(me) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
this.users.logged = me;
|
|
||||||
let currentUser = users.find((u) => u.id === me.id);
|
|
||||||
this.value = currentUser;
|
|
||||||
|
|
||||||
fetchCalendar(currentUser.id).then(
|
|
||||||
(calendar) =>
|
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
let results = calendar.results;
|
let results = calendarRanges.results;
|
||||||
let events = results.map((i) => ({
|
|
||||||
start: i.startDate.datetime,
|
|
||||||
end: i.endDate.datetime,
|
|
||||||
}));
|
|
||||||
let calendarEventsCurrentUser = {
|
|
||||||
events: events,
|
|
||||||
color: "darkblue",
|
|
||||||
id: 1000,
|
|
||||||
editable: false,
|
|
||||||
};
|
|
||||||
this.calendarEvents.user = calendarEventsCurrentUser;
|
|
||||||
|
|
||||||
this.selectUsers(currentUser);
|
let users = [];
|
||||||
|
|
||||||
resolve();
|
results.forEach((i) => {
|
||||||
|
if (!users.some((j) => i.user.id === j.id)) {
|
||||||
|
let ratio = Math.floor(
|
||||||
|
users.length / COLORS.length,
|
||||||
|
);
|
||||||
|
let colorIndex =
|
||||||
|
users.length - ratio * COLORS.length;
|
||||||
|
users.push({
|
||||||
|
id: i.user.id,
|
||||||
|
username: i.user.username,
|
||||||
|
color: COLORS[colorIndex],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let calendarEvents = [];
|
||||||
|
users.forEach((u) => {
|
||||||
|
let arr = results
|
||||||
|
.filter((i) => i.user.id === u.id)
|
||||||
|
.map((i) => ({
|
||||||
|
start: i.startDate.datetime,
|
||||||
|
end: i.endDate.datetime,
|
||||||
|
calendarRangeId: i.id,
|
||||||
|
sourceColor: u.color,
|
||||||
|
//display: 'background' // can be an option for the disponibility
|
||||||
|
}));
|
||||||
|
calendarEvents.push({
|
||||||
|
events: arr,
|
||||||
|
color: u.color,
|
||||||
|
textColor: "#444444",
|
||||||
|
editable: false,
|
||||||
|
id: u.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.users.loaded = users;
|
||||||
|
this.options = users;
|
||||||
|
|
||||||
|
this.calendarEvents.loaded = calendarEvents;
|
||||||
|
whoami().then(
|
||||||
|
(me) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
this.users.logged = me;
|
||||||
|
let currentUser = users.find(
|
||||||
|
(u) => u.id === me.id,
|
||||||
|
);
|
||||||
|
this.value = currentUser;
|
||||||
|
|
||||||
|
fetchCalendar(currentUser.id).then(
|
||||||
|
(calendar) =>
|
||||||
|
new Promise(
|
||||||
|
(resolve, reject) => {
|
||||||
|
let results =
|
||||||
|
calendar.results;
|
||||||
|
let events =
|
||||||
|
results.map(
|
||||||
|
(i) => ({
|
||||||
|
start: i
|
||||||
|
.startDate
|
||||||
|
.datetime,
|
||||||
|
end: i
|
||||||
|
.endDate
|
||||||
|
.datetime,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let calendarEventsCurrentUser =
|
||||||
|
{
|
||||||
|
events: events,
|
||||||
|
color: "darkblue",
|
||||||
|
id: 1000,
|
||||||
|
editable: false,
|
||||||
|
};
|
||||||
|
this.calendarEvents.user =
|
||||||
|
calendarEventsCurrentUser;
|
||||||
|
|
||||||
|
this.selectUsers(
|
||||||
|
currentUser,
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve();
|
||||||
}),
|
}),
|
||||||
);
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
this.errorMsg.push(error.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
transName(value) {
|
||||||
|
return `${value.username}`;
|
||||||
|
},
|
||||||
|
coloriseSelectedValues() {
|
||||||
|
let tags = document.querySelectorAll(
|
||||||
|
"div.multiselect__tags-wrap",
|
||||||
|
)[0];
|
||||||
|
|
||||||
resolve();
|
if (tags.hasChildNodes()) {
|
||||||
}),
|
let children = tags.childNodes;
|
||||||
);
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
let child = children[i];
|
||||||
resolve();
|
if (child.nodeType === Node.ELEMENT_NODE) {
|
||||||
}),
|
this.users.selected.forEach((u) => {
|
||||||
)
|
if (child.hasChildNodes()) {
|
||||||
.catch((error) => {
|
if (child.firstChild.innerText == u.username) {
|
||||||
this.errorMsg.push(error.message);
|
child.style.background = u.color;
|
||||||
});
|
child.firstChild.style.color = "#444444";
|
||||||
},
|
}
|
||||||
transName(value) {
|
}
|
||||||
return `${value.username}`;
|
});
|
||||||
},
|
}
|
||||||
coloriseSelectedValues() {
|
|
||||||
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
|
|
||||||
|
|
||||||
if (tags.hasChildNodes()) {
|
|
||||||
let children = tags.childNodes;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
let child = children[i];
|
|
||||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
this.users.selected.forEach((u) => {
|
|
||||||
if (child.hasChildNodes()) {
|
|
||||||
if (child.firstChild.innerText == u.username) {
|
|
||||||
child.style.background = u.color;
|
|
||||||
child.firstChild.style.color = "#444444";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
}
|
selectEvents() {
|
||||||
}
|
let selectedUsersId = this.users.selected.map((a) => a.id);
|
||||||
}
|
this.calendarEvents.selected = this.calendarEvents.loaded.filter(
|
||||||
|
(a) => selectedUsersId.includes(a.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
selectUsers(value) {
|
||||||
|
this.users.selected.push(value);
|
||||||
|
this.coloriseSelectedValues();
|
||||||
|
this.selectEvents();
|
||||||
|
this.updateEventsSource();
|
||||||
|
},
|
||||||
|
unSelectUsers(value) {
|
||||||
|
this.users.selected = this.users.selected.filter(
|
||||||
|
(a) => a.id != value.id,
|
||||||
|
);
|
||||||
|
this.selectEvents();
|
||||||
|
this.updateEventsSource();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
selectEvents() {
|
mounted() {
|
||||||
let selectedUsersId = this.users.selected.map((a) => a.id);
|
this.init();
|
||||||
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
|
|
||||||
selectedUsersId.includes(a.id),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
selectUsers(value) {
|
|
||||||
this.users.selected.push(value);
|
|
||||||
this.coloriseSelectedValues();
|
|
||||||
this.selectEvents();
|
|
||||||
this.updateEventsSource();
|
|
||||||
},
|
|
||||||
unSelectUsers(value) {
|
|
||||||
this.users.selected = this.users.selected.filter((a) => a.id != value.id);
|
|
||||||
this.selectEvents();
|
|
||||||
this.updateEventsSource();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -24,7 +24,11 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
|
|
||||||
class CalendarForShortMessageProvider
|
class CalendarForShortMessageProvider
|
||||||
{
|
{
|
||||||
public function __construct(private readonly CalendarRepository $calendarRepository, private readonly EntityManagerInterface $em, private readonly RangeGeneratorInterface $rangeGenerator) {}
|
public function __construct(
|
||||||
|
private readonly CalendarRepository $calendarRepository,
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
|
private readonly RangeGeneratorInterface $rangeGenerator,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate calendars instance.
|
* Generate calendars instance.
|
||||||
|
@@ -21,7 +21,6 @@ namespace Chill\CalendarBundle\Tests\Service\ShortMessageNotification;
|
|||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\CalendarBundle\Repository\CalendarRepository;
|
use Chill\CalendarBundle\Repository\CalendarRepository;
|
||||||
use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider;
|
use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider;
|
||||||
use Chill\CalendarBundle\Service\ShortMessageNotification\DefaultRangeGenerator;
|
|
||||||
use Chill\CalendarBundle\Service\ShortMessageNotification\RangeGeneratorInterface;
|
use Chill\CalendarBundle\Service\ShortMessageNotification\RangeGeneratorInterface;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -82,10 +81,16 @@ final class CalendarForShortMessageProviderTest extends TestCase
|
|||||||
$em = $this->prophesize(EntityManagerInterface::class);
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
$em->clear()->shouldBeCalled();
|
$em->clear()->shouldBeCalled();
|
||||||
|
|
||||||
|
$calendarRangeGenerator = $this->prophesize(RangeGeneratorInterface::class);
|
||||||
|
$calendarRangeGenerator->generateRange(Argument::any())->willReturn([
|
||||||
|
'startDate' => new \DateTimeImmutable('yesterday'),
|
||||||
|
'endDate' => new \DateTimeImmutable('now'),
|
||||||
|
]);
|
||||||
|
|
||||||
$provider = new CalendarForShortMessageProvider(
|
$provider = new CalendarForShortMessageProvider(
|
||||||
$calendarRepository->reveal(),
|
$calendarRepository->reveal(),
|
||||||
$em->reveal(),
|
$em->reveal(),
|
||||||
new DefaultRangeGenerator()
|
$calendarRangeGenerator->reveal(),
|
||||||
);
|
);
|
||||||
|
|
||||||
$calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now')));
|
$calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now')));
|
||||||
@@ -103,26 +108,32 @@ final class CalendarForShortMessageProviderTest extends TestCase
|
|||||||
Argument::type(\DateTimeImmutable::class),
|
Argument::type(\DateTimeImmutable::class),
|
||||||
Argument::type('int'),
|
Argument::type('int'),
|
||||||
Argument::exact(0)
|
Argument::exact(0)
|
||||||
)->will(static fn ($args) => array_fill(0, 1, new Calendar()))->shouldBeCalledTimes(1);
|
)->will(static fn ($args) => array_fill(0, 10, new Calendar()))->shouldBeCalledTimes(1);
|
||||||
$calendarRepository->findByNotificationAvailable(
|
$calendarRepository->findByNotificationAvailable(
|
||||||
Argument::type(\DateTimeImmutable::class),
|
Argument::type(\DateTimeImmutable::class),
|
||||||
Argument::type(\DateTimeImmutable::class),
|
Argument::type(\DateTimeImmutable::class),
|
||||||
Argument::type('int'),
|
Argument::type('int'),
|
||||||
Argument::not(0)
|
Argument::exact(10)
|
||||||
)->will(static fn ($args) => [])->shouldBeCalledTimes(1);
|
)->will(static fn ($args) => [])->shouldBeCalledTimes(1);
|
||||||
|
|
||||||
$em = $this->prophesize(EntityManagerInterface::class);
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
$em->clear()->shouldBeCalled();
|
$em->clear()->shouldBeCalled();
|
||||||
|
|
||||||
|
$calendarRangeGenerator = $this->prophesize(RangeGeneratorInterface::class);
|
||||||
|
$calendarRangeGenerator->generateRange(Argument::any())->willReturn([
|
||||||
|
'startDate' => new \DateTimeImmutable('yesterday'),
|
||||||
|
'endDate' => new \DateTimeImmutable('now'),
|
||||||
|
]);
|
||||||
|
|
||||||
$provider = new CalendarForShortMessageProvider(
|
$provider = new CalendarForShortMessageProvider(
|
||||||
$calendarRepository->reveal(),
|
$calendarRepository->reveal(),
|
||||||
$em->reveal(),
|
$em->reveal(),
|
||||||
new DefaultRangeGenerator()
|
$calendarRangeGenerator->reveal(),
|
||||||
);
|
);
|
||||||
|
|
||||||
$calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now')));
|
$calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now')));
|
||||||
|
|
||||||
$this->assertEquals(1, \count($calendars));
|
$this->assertEquals(10, \count($calendars));
|
||||||
$this->assertContainsOnly(Calendar::class, $calendars);
|
$this->assertContainsOnly(Calendar::class, $calendars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
<?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\CustomFieldsBundle\EntityRepository;
|
||||||
|
|
||||||
|
use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class CustomFieldsDefaultGroupRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, CustomFieldsDefaultGroup::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneByEntity(string $className): ?CustomFieldsDefaultGroup
|
||||||
|
{
|
||||||
|
return $this->findOneBy(['entity' => $className]);
|
||||||
|
}
|
||||||
|
}
|
@@ -127,3 +127,7 @@ services:
|
|||||||
factory: ["@doctrine", getRepository]
|
factory: ["@doctrine", getRepository]
|
||||||
arguments:
|
arguments:
|
||||||
- "Chill\\CustomFieldsBundle\\Entity\\CustomFieldLongChoice\\Option"
|
- "Chill\\CustomFieldsBundle\\Entity\\CustomFieldLongChoice\\Option"
|
||||||
|
|
||||||
|
Chill\CustomFieldsBundle\EntityRepository\CustomFieldsDefaultGroupRepository:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
@@ -1,54 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template v-if="templates.length > 0">
|
<template v-if="templates.length > 0">
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
<h2>{{ $t("generate_document") }}</h2>
|
<h2>{{ $t("generate_document") }}</h2>
|
||||||
</slot>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<slot name="label">
|
|
||||||
<label>{{ $t("select_a_template") }}</label>
|
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
|
||||||
<div class="col-md-8">
|
<div class="container">
|
||||||
<div class="input-group mb-3">
|
<div class="row">
|
||||||
<select class="form-select" v-model="template">
|
<div class="col-md-4">
|
||||||
<option disabled selected value="">
|
<slot name="label">
|
||||||
{{ $t("choose_a_template") }}
|
<label>{{ $t("select_a_template") }}</label>
|
||||||
</option>
|
</slot>
|
||||||
<template v-for="t in templates" :key="t.id">
|
</div>
|
||||||
<option :value="t.id">
|
<div class="col-md-8">
|
||||||
{{ localizeString(t.name) || "Aucun nom défini" }}
|
<div class="input-group mb-3">
|
||||||
</option>
|
<select class="form-select" v-model="template">
|
||||||
</template>
|
<option disabled selected value="">
|
||||||
</select>
|
{{ $t("choose_a_template") }}
|
||||||
<a
|
</option>
|
||||||
v-if="canGenerate"
|
<template v-for="t in templates" :key="t.id">
|
||||||
class="btn btn-update btn-sm change-icon"
|
<option :value="t.id">
|
||||||
:href="buildUrlGenerate"
|
{{
|
||||||
@click.prevent="clickGenerate($event, buildUrlGenerate)"
|
localizeString(t.name) ||
|
||||||
><i class="fa fa-fw fa-cog"
|
"Aucun nom défini"
|
||||||
/></a>
|
}}
|
||||||
<a
|
</option>
|
||||||
v-else
|
</template>
|
||||||
class="btn btn-update btn-sm change-icon"
|
</select>
|
||||||
href="#"
|
<a
|
||||||
disabled
|
v-if="canGenerate"
|
||||||
><i class="fa fa-fw fa-cog"
|
class="btn btn-update btn-sm change-icon"
|
||||||
/></a>
|
:href="buildUrlGenerate"
|
||||||
|
@click.prevent="
|
||||||
|
clickGenerate($event, buildUrlGenerate)
|
||||||
|
"
|
||||||
|
><i class="fa fa-fw fa-cog"
|
||||||
|
/></a>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
class="btn btn-update btn-sm change-icon"
|
||||||
|
href="#"
|
||||||
|
disabled
|
||||||
|
><i class="fa fa-fw fa-cog"
|
||||||
|
/></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" v-if="hasDescription">
|
||||||
|
<div class="col-md-8 align-self-end">
|
||||||
|
<p>{{ getDescription }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" v-if="hasDescription">
|
|
||||||
<div class="col-md-8 align-self-end">
|
|
||||||
<p>{{ getDescription }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -56,83 +61,83 @@ import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
|
|||||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PickTemplate",
|
name: "PickTemplate",
|
||||||
props: {
|
props: {
|
||||||
entityId: [String, Number],
|
entityId: [String, Number],
|
||||||
entityClass: {
|
entityClass: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
},
|
||||||
|
templates: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
preventDefaultMoveToGenerate: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
templates: {
|
emits: ["goToGenerateDocument"],
|
||||||
type: Array,
|
data() {
|
||||||
required: true,
|
return {
|
||||||
|
template: null,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
preventDefaultMoveToGenerate: {
|
computed: {
|
||||||
type: Boolean,
|
canGenerate() {
|
||||||
required: false,
|
return this.template != null;
|
||||||
default: false,
|
},
|
||||||
},
|
hasDescription() {
|
||||||
},
|
if (this.template == null) {
|
||||||
emits: ["goToGenerateDocument"],
|
return false;
|
||||||
data() {
|
}
|
||||||
return {
|
|
||||||
template: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
canGenerate() {
|
|
||||||
return this.template != null;
|
|
||||||
},
|
|
||||||
hasDescription() {
|
|
||||||
if (this.template == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
getDescription() {
|
getDescription() {
|
||||||
if (null === this.template) {
|
if (null === this.template) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
let desc = this.templates.find((t) => t.id === this.template);
|
let desc = this.templates.find((t) => t.id === this.template);
|
||||||
if (null === desc) {
|
if (null === desc) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return desc.description || "";
|
return desc.description || "";
|
||||||
},
|
},
|
||||||
buildUrlGenerate() {
|
buildUrlGenerate() {
|
||||||
if (null === this.template) {
|
if (null === this.template) {
|
||||||
return "#";
|
return "#";
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildLink(this.template, this.entityId, this.entityClass);
|
return buildLink(this.template, this.entityId, this.entityClass);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
methods: {
|
||||||
methods: {
|
localizeString(str) {
|
||||||
localizeString(str) {
|
return localizeString(str);
|
||||||
return localizeString(str);
|
},
|
||||||
},
|
clickGenerate(event, link) {
|
||||||
clickGenerate(event, link) {
|
if (!this.preventDefaultMoveToGenerate) {
|
||||||
if (!this.preventDefaultMoveToGenerate) {
|
window.location.assign(link);
|
||||||
window.location.assign(link);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit("goToGenerateDocument", {
|
this.$emit("goToGenerateDocument", {
|
||||||
event,
|
event,
|
||||||
link,
|
link,
|
||||||
template: this.template,
|
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>
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
|||||||
use Chill\DocStoreBundle\Service\Cryptography\KeyGenerator;
|
use Chill\DocStoreBundle\Service\Cryptography\KeyGenerator;
|
||||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Symfony\Component\Filesystem\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
|
|
||||||
@@ -147,16 +148,11 @@ class StoredObjectManager implements StoredObjectManagerInterface
|
|||||||
public function writeContent(string $filename, string $encryptedContent): void
|
public function writeContent(string $filename, string $encryptedContent): void
|
||||||
{
|
{
|
||||||
$fullPath = $this->buildPath($filename);
|
$fullPath = $this->buildPath($filename);
|
||||||
$dir = Path::getDirectory($fullPath);
|
|
||||||
|
|
||||||
if (!$this->filesystem->exists($dir)) {
|
try {
|
||||||
$this->filesystem->mkdir($dir);
|
$this->filesystem->dumpFile($fullPath, $encryptedContent);
|
||||||
}
|
} catch (IOExceptionInterface $exception) {
|
||||||
|
throw StoredObjectManagerException::unableToStoreDocumentOnDisk($exception);
|
||||||
$result = file_put_contents($fullPath, $encryptedContent);
|
|
||||||
|
|
||||||
if (false === $result) {
|
|
||||||
throw StoredObjectManagerException::unableToStoreDocumentOnDisk();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -59,7 +59,7 @@ final readonly class StoredObjectVersionApiController
|
|||||||
|
|
||||||
return new JsonResponse(
|
return new JsonResponse(
|
||||||
$this->serializer->serialize(
|
$this->serializer->serialize(
|
||||||
new Collection($items, $paginator),
|
new Collection(array_values($items->toArray()), $paginator),
|
||||||
'json',
|
'json',
|
||||||
[AbstractNormalizer::GROUPS => ['read', StoredObjectVersionNormalizer::WITH_POINT_IN_TIMES_CONTEXT, StoredObjectVersionNormalizer::WITH_RESTORED_CONTEXT]]
|
[AbstractNormalizer::GROUPS => ['read', StoredObjectVersionNormalizer::WITH_POINT_IN_TIMES_CONTEXT, StoredObjectVersionNormalizer::WITH_RESTORED_CONTEXT]]
|
||||||
),
|
),
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
<?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\Exception;
|
||||||
|
|
||||||
|
class ConversionWithSameMimeTypeException extends \RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(string $mimeType, ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Conversion to same MIME type '{$mimeType}' is not allowed: already at the same MIME type", 0, $previous);
|
||||||
|
}
|
||||||
|
}
|
@@ -6,20 +6,20 @@ const algo = "AES-CBC";
|
|||||||
const URL_POST = "/asyncupload/temp_url/generate/post";
|
const URL_POST = "/asyncupload/temp_url/generate/post";
|
||||||
|
|
||||||
const keyDefinition = {
|
const keyDefinition = {
|
||||||
name: algo,
|
name: algo,
|
||||||
length: 256,
|
length: 256,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFilename = (): string => {
|
const createFilename = (): string => {
|
||||||
let text = "";
|
let text = "";
|
||||||
const possible =
|
const possible =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,59 +30,59 @@ const createFilename = (): string => {
|
|||||||
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
|
* @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
|
||||||
*/
|
*/
|
||||||
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
|
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
|
||||||
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
|
return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadVersion = async (
|
export const uploadVersion = async (
|
||||||
uploadFile: ArrayBuffer,
|
uploadFile: ArrayBuffer,
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append("expires_delay", "180");
|
params.append("expires_delay", "180");
|
||||||
params.append("submit_delay", "180");
|
params.append("submit_delay", "180");
|
||||||
const asyncData: PostStoreObjectSignature = await makeFetch(
|
const asyncData: PostStoreObjectSignature = await makeFetch(
|
||||||
"GET",
|
"GET",
|
||||||
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
`/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
|
||||||
"?" +
|
"?" +
|
||||||
params.toString(),
|
params.toString(),
|
||||||
);
|
);
|
||||||
const suffix = createFilename();
|
const suffix = createFilename();
|
||||||
const filename = asyncData.prefix + suffix;
|
const filename = asyncData.prefix + suffix;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("redirect", asyncData.redirect);
|
formData.append("redirect", asyncData.redirect);
|
||||||
formData.append("max_file_size", asyncData.max_file_size.toString());
|
formData.append("max_file_size", asyncData.max_file_size.toString());
|
||||||
formData.append("max_file_count", asyncData.max_file_count.toString());
|
formData.append("max_file_count", asyncData.max_file_count.toString());
|
||||||
formData.append("expires", asyncData.expires.toString());
|
formData.append("expires", asyncData.expires.toString());
|
||||||
formData.append("signature", asyncData.signature);
|
formData.append("signature", asyncData.signature);
|
||||||
formData.append(filename, new Blob([uploadFile]), suffix);
|
formData.append(filename, new Blob([uploadFile]), suffix);
|
||||||
|
|
||||||
const response = await window.fetch(asyncData.url, {
|
const response = await window.fetch(asyncData.url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Error while sending file to store", response);
|
console.error("Error while sending file to store", response);
|
||||||
throw new Error(response.statusText);
|
throw new Error(response.statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(filename);
|
return Promise.resolve(filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const encryptFile = async (
|
export const encryptFile = async (
|
||||||
originalFile: ArrayBuffer,
|
originalFile: ArrayBuffer,
|
||||||
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||||
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
|
||||||
"encrypt",
|
"encrypt",
|
||||||
"decrypt",
|
"decrypt",
|
||||||
]);
|
]);
|
||||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
{ name: algo, iv: iv },
|
{ name: algo, iv: iv },
|
||||||
key,
|
key,
|
||||||
originalFile,
|
originalFile,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.resolve([encrypted, iv, exportedKey]);
|
return Promise.resolve([encrypted, iv, exportedKey]);
|
||||||
};
|
};
|
||||||
|
@@ -2,9 +2,9 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
|||||||
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
|
||||||
export function fetch_generic_docs_by_accompanying_period(
|
export function fetch_generic_docs_by_accompanying_period(
|
||||||
periodId: number,
|
periodId: number,
|
||||||
): Promise<GenericDocForAccompanyingPeriod[]> {
|
): Promise<GenericDocForAccompanyingPeriod[]> {
|
||||||
return fetchResults(
|
return fetchResults(
|
||||||
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -6,116 +6,117 @@ import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vue
|
|||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
const startApp = (
|
const startApp = (
|
||||||
divElement: HTMLDivElement,
|
divElement: HTMLDivElement,
|
||||||
collectionEntry: null | HTMLLIElement,
|
collectionEntry: null | HTMLLIElement,
|
||||||
): void => {
|
): void => {
|
||||||
console.log("app started", divElement);
|
console.log("app started", divElement);
|
||||||
|
|
||||||
const inputTitle = collectionEntry?.querySelector("input[type='text']");
|
const inputTitle = collectionEntry?.querySelector("input[type='text']");
|
||||||
|
|
||||||
const input_stored_object: HTMLInputElement | null = divElement.querySelector(
|
const input_stored_object: HTMLInputElement | null =
|
||||||
"input[data-stored-object]",
|
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);
|
||||||
}
|
}
|
||||||
const app_container = document.createElement("div");
|
const app_container = document.createElement("div");
|
||||||
divElement.appendChild(app_container);
|
divElement.appendChild(app_container);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template:
|
template:
|
||||||
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
'<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
existingDoc: existingDoc,
|
existingDoc: existingDoc,
|
||||||
inputTitle: inputTitle,
|
inputTitle: inputTitle,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
DropFileWidget,
|
DropFileWidget,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addDocument: function ({
|
addDocument: function ({
|
||||||
stored_object,
|
stored_object,
|
||||||
stored_object_version,
|
stored_object_version,
|
||||||
file_name,
|
file_name,
|
||||||
}: {
|
}: {
|
||||||
stored_object: StoredObject;
|
stored_object: StoredObject;
|
||||||
stored_object_version: StoredObjectVersion;
|
stored_object_version: StoredObjectVersion;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
}): void {
|
}): void {
|
||||||
stored_object.title = file_name;
|
stored_object.title = file_name;
|
||||||
console.log("object added", stored_object);
|
console.log("object added", stored_object);
|
||||||
console.log("version added", stored_object_version);
|
console.log("version added", stored_object_version);
|
||||||
this.$data.existingDoc = stored_object;
|
this.$data.existingDoc = stored_object;
|
||||||
this.$data.existingDoc.currentVersion = stored_object_version;
|
this.$data.existingDoc.currentVersion = stored_object_version;
|
||||||
input_stored_object.value = JSON.stringify(this.$data.existingDoc);
|
input_stored_object.value = JSON.stringify(
|
||||||
if (this.$data.inputTitle) {
|
this.$data.existingDoc,
|
||||||
if (!this.$data.inputTitle?.value) {
|
);
|
||||||
this.$data.inputTitle.value = file_name;
|
if (this.$data.inputTitle) {
|
||||||
}
|
if (!this.$data.inputTitle?.value) {
|
||||||
}
|
this.$data.inputTitle.value = file_name;
|
||||||
},
|
}
|
||||||
removeDocument: function (object: StoredObject): void {
|
}
|
||||||
console.log("catch remove document", object);
|
},
|
||||||
input_stored_object.value = "";
|
removeDocument: function (object: StoredObject): void {
|
||||||
this.$data.existingDoc = undefined;
|
console.log("catch remove document", object);
|
||||||
console.log("collectionEntry", collectionEntry);
|
input_stored_object.value = "";
|
||||||
|
this.$data.existingDoc = undefined;
|
||||||
|
console.log("collectionEntry", collectionEntry);
|
||||||
|
|
||||||
if (null !== collectionEntry) {
|
if (null !== collectionEntry) {
|
||||||
console.log("will remove collection");
|
console.log("will remove collection");
|
||||||
collectionEntry.remove();
|
collectionEntry.remove();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).mount(app_container);
|
app.use(i18n).mount(app_container);
|
||||||
};
|
};
|
||||||
window.addEventListener("collection-add-entry", ((
|
window.addEventListener("collection-add-entry", ((
|
||||||
e: CustomEvent<CollectionEventPayload>,
|
e: CustomEvent<CollectionEventPayload>,
|
||||||
) => {
|
) => {
|
||||||
const detail = e.detail;
|
const detail = e.detail;
|
||||||
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
const divElement: null | HTMLDivElement = detail.entry.querySelector(
|
||||||
"div[data-stored-object]",
|
"div[data-stored-object]",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (null === divElement) {
|
if (null === divElement) {
|
||||||
throw new Error("div[data-stored-object] not found");
|
throw new Error("div[data-stored-object] not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
startApp(divElement, detail.entry);
|
startApp(divElement, detail.entry);
|
||||||
}) as EventListener);
|
}) as EventListener);
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
|
||||||
"div[data-stored-object]",
|
"div[data-stored-object]",
|
||||||
);
|
);
|
||||||
|
|
||||||
upload_inputs.forEach((input: HTMLDivElement): void => {
|
upload_inputs.forEach((input: HTMLDivElement): void => {
|
||||||
// test for a parent to check if this is a collection entry
|
// test for a parent to check if this is a collection entry
|
||||||
let collectionEntry: null | HTMLLIElement = null;
|
let collectionEntry: null | HTMLLIElement = null;
|
||||||
const parent = input.parentElement;
|
const parent = input.parentElement;
|
||||||
console.log("parent", parent);
|
console.log("parent", parent);
|
||||||
if (null !== parent) {
|
if (null !== parent) {
|
||||||
const grandParent = parent.parentElement;
|
const grandParent = parent.parentElement;
|
||||||
console.log("grandParent", grandParent);
|
console.log("grandParent", grandParent);
|
||||||
if (null !== grandParent) {
|
if (null !== grandParent) {
|
||||||
if (
|
if (
|
||||||
grandParent.tagName.toLowerCase() === "li" &&
|
grandParent.tagName.toLowerCase() === "li" &&
|
||||||
grandParent.classList.contains("entry")
|
grandParent.classList.contains("entry")
|
||||||
) {
|
) {
|
||||||
collectionEntry = grandParent as HTMLLIElement;
|
collectionEntry = grandParent as HTMLLIElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
startApp(input, collectionEntry);
|
||||||
}
|
});
|
||||||
startApp(input, collectionEntry);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
@@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification";
|
|||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", function (e) {
|
window.addEventListener("DOMContentLoaded", function (e) {
|
||||||
document
|
document
|
||||||
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
.querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
|
||||||
.forEach((el) => {
|
.forEach((el) => {
|
||||||
const storedObject = JSON.parse(
|
const storedObject = JSON.parse(
|
||||||
el.dataset.storedObject as string,
|
el.dataset.storedObject as string,
|
||||||
) as StoredObject;
|
) as StoredObject;
|
||||||
const title = el.dataset.title as string;
|
const title = el.dataset.title as string;
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: { DownloadButton },
|
components: { DownloadButton },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
storedObject,
|
storedObject,
|
||||||
title,
|
title,
|
||||||
classes: { btn: true, "btn-outline-primary": true },
|
classes: { btn: true, "btn-outline-primary": true },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template:
|
template:
|
||||||
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
'<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).use(ToastPlugin).mount(el);
|
app.use(i18n).use(ToastPlugin).mount(el);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -8,66 +8,66 @@ import ToastPlugin from "vue-toast-notification";
|
|||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", function (e) {
|
window.addEventListener("DOMContentLoaded", function (e) {
|
||||||
document
|
document
|
||||||
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
.querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
|
||||||
.forEach((el) => {
|
.forEach((el) => {
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
components: { DocumentActionButtonsGroup },
|
components: { DocumentActionButtonsGroup },
|
||||||
data() {
|
data() {
|
||||||
const datasets = el.dataset as {
|
const datasets = el.dataset as {
|
||||||
filename: string;
|
filename: string;
|
||||||
canEdit: string;
|
canEdit: string;
|
||||||
storedObject: string;
|
storedObject: string;
|
||||||
buttonSmall: string;
|
buttonSmall: string;
|
||||||
davLink: string;
|
davLink: string;
|
||||||
davLinkExpiration: string;
|
davLinkExpiration: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const storedObject = JSON.parse(
|
const storedObject = JSON.parse(
|
||||||
datasets.storedObject,
|
datasets.storedObject,
|
||||||
) as StoredObject,
|
) as StoredObject,
|
||||||
filename = datasets.filename,
|
filename = datasets.filename,
|
||||||
canEdit = datasets.canEdit === "1",
|
canEdit = datasets.canEdit === "1",
|
||||||
small = datasets.buttonSmall === "1",
|
small = datasets.buttonSmall === "1",
|
||||||
davLink =
|
davLink =
|
||||||
"davLink" in datasets && datasets.davLink !== ""
|
"davLink" in datasets && datasets.davLink !== ""
|
||||||
? datasets.davLink
|
? datasets.davLink
|
||||||
: null,
|
: null,
|
||||||
davLinkExpiration =
|
davLinkExpiration =
|
||||||
"davLinkExpiration" in datasets
|
"davLinkExpiration" in datasets
|
||||||
? Number.parseInt(datasets.davLinkExpiration)
|
? Number.parseInt(datasets.davLinkExpiration)
|
||||||
: null;
|
: null;
|
||||||
return {
|
return {
|
||||||
storedObject,
|
storedObject,
|
||||||
filename,
|
filename,
|
||||||
canEdit,
|
canEdit,
|
||||||
small,
|
small,
|
||||||
davLink,
|
davLink,
|
||||||
davLinkExpiration,
|
davLinkExpiration,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template:
|
template:
|
||||||
'<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
'<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
||||||
methods: {
|
methods: {
|
||||||
onStoredObjectStatusChange: function (
|
onStoredObjectStatusChange: function (
|
||||||
newStatus: StoredObjectStatusChange,
|
newStatus: StoredObjectStatusChange,
|
||||||
): void {
|
): void {
|
||||||
this.$data.storedObject.status = newStatus.status;
|
this.$data.storedObject.status = newStatus.status;
|
||||||
this.$data.storedObject.filename = newStatus.filename;
|
this.$data.storedObject.filename = newStatus.filename;
|
||||||
this.$data.storedObject.type = newStatus.type;
|
this.$data.storedObject.type = newStatus.type;
|
||||||
|
|
||||||
// remove eventual div which inform pending status
|
// remove eventual div which inform pending status
|
||||||
document
|
document
|
||||||
.querySelectorAll(
|
.querySelectorAll(
|
||||||
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
`[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
|
||||||
)
|
)
|
||||||
.forEach(function (el) {
|
.forEach(function (el) {
|
||||||
el.remove();
|
el.remove();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).use(ToastPlugin).mount(el);
|
app.use(i18n).use(ToastPlugin).mount(el);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,7 +2,7 @@ import { DateTime } from "ChillMainAssets/types";
|
|||||||
import { StoredObject } from "ChillDocStoreAssets/types/index";
|
import { StoredObject } from "ChillDocStoreAssets/types/index";
|
||||||
|
|
||||||
export interface GenericDocMetadata {
|
export interface GenericDocMetadata {
|
||||||
isPresent: boolean;
|
isPresent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,57 +15,57 @@ export interface EmptyMetadata extends GenericDocMetadata {}
|
|||||||
* Minimal Metadata for a GenericDoc with a normalizer
|
* Minimal Metadata for a GenericDoc with a normalizer
|
||||||
*/
|
*/
|
||||||
export interface BaseMetadata extends GenericDocMetadata {
|
export interface BaseMetadata extends GenericDocMetadata {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
||||||
*/
|
*/
|
||||||
export interface GenericDoc {
|
export interface GenericDoc {
|
||||||
type: "doc_store_generic_doc";
|
type: "doc_store_generic_doc";
|
||||||
uniqueKey: string;
|
uniqueKey: string;
|
||||||
key: string;
|
key: string;
|
||||||
identifiers: object;
|
identifiers: { id: number };
|
||||||
context: "person" | "accompanying-period";
|
context: "person" | "accompanying-period";
|
||||||
doc_date: DateTime;
|
doc_date: DateTime;
|
||||||
metadata: GenericDocMetadata;
|
metadata: GenericDocMetadata;
|
||||||
storedObject: StoredObject | null;
|
storedObject: StoredObject | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
||||||
context: "accompanying-period";
|
context: "accompanying-period";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseMetadataWithHtml extends BaseMetadata {
|
interface BaseMetadataWithHtml extends BaseMetadata {
|
||||||
html: string;
|
html: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseDocument
|
export interface GenericDocForAccompanyingCourseDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_course_document";
|
key: "accompanying_course_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseActivityDocument
|
export interface GenericDocForAccompanyingCourseActivityDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_course_activity_document";
|
key: "accompanying_course_activity_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseCalendarDocument
|
export interface GenericDocForAccompanyingCourseCalendarDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_course_calendar_document";
|
key: "accompanying_course_calendar_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCoursePersonDocument
|
export interface GenericDocForAccompanyingCoursePersonDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "person_document";
|
key: "person_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
||||||
extends GenericDocForAccompanyingPeriod {
|
extends GenericDocForAccompanyingPeriod {
|
||||||
key: "accompanying_period_work_evaluation_document";
|
key: "accompanying_period_work_evaluation_document";
|
||||||
metadata: BaseMetadataWithHtml;
|
metadata: BaseMetadataWithHtml;
|
||||||
}
|
}
|
||||||
|
@@ -4,73 +4,73 @@ import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpe
|
|||||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
||||||
|
|
||||||
export interface StoredObject {
|
export interface StoredObject {
|
||||||
id: number;
|
id: number;
|
||||||
title: string | null;
|
title: string | null;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
status: StoredObjectStatus;
|
status: StoredObjectStatus;
|
||||||
currentVersion:
|
currentVersion:
|
||||||
| null
|
| null
|
||||||
| StoredObjectVersionCreated
|
| StoredObjectVersionCreated
|
||||||
| StoredObjectVersionPersisted;
|
| StoredObjectVersionPersisted;
|
||||||
totalVersions: number;
|
totalVersions: number;
|
||||||
datas: object;
|
datas: object;
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
creationDate: DateTime;
|
creationDate: DateTime;
|
||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
createdBy: User | null;
|
createdBy: User | null;
|
||||||
_permissions: {
|
_permissions: {
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
canSee: boolean;
|
canSee: boolean;
|
||||||
};
|
};
|
||||||
_links?: {
|
_links?: {
|
||||||
dav_link?: {
|
dav_link?: {
|
||||||
href: string;
|
href: string;
|
||||||
expiration: number;
|
expiration: number;
|
||||||
|
};
|
||||||
|
downloadLink?: SignedUrlGet;
|
||||||
};
|
};
|
||||||
downloadLink?: SignedUrlGet;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersion {
|
export interface StoredObjectVersion {
|
||||||
/**
|
/**
|
||||||
* filename of the object in the object storage
|
* filename of the object in the object storage
|
||||||
*/
|
*/
|
||||||
filename: string;
|
filename: string;
|
||||||
iv: number[];
|
iv: number[];
|
||||||
keyInfos: JsonWebKey;
|
keyInfos: JsonWebKey;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersionCreated extends StoredObjectVersion {
|
export interface StoredObjectVersionCreated extends StoredObjectVersion {
|
||||||
persisted: false;
|
persisted: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersionPersisted
|
export interface StoredObjectVersionPersisted
|
||||||
extends StoredObjectVersionCreated {
|
extends StoredObjectVersionCreated {
|
||||||
version: number;
|
version: number;
|
||||||
id: number;
|
id: number;
|
||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
createdBy: User | null;
|
createdBy: User | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectStatusChange {
|
export interface StoredObjectStatusChange {
|
||||||
id: number;
|
id: number;
|
||||||
filename: string;
|
filename: string;
|
||||||
status: StoredObjectStatus;
|
status: StoredObjectStatus;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectVersionWithPointInTime
|
export interface StoredObjectVersionWithPointInTime
|
||||||
extends StoredObjectVersionPersisted {
|
extends StoredObjectVersionPersisted {
|
||||||
"point-in-times": StoredObjectPointInTime[];
|
"point-in-times": StoredObjectPointInTime[];
|
||||||
"from-restored": StoredObjectVersionPersisted | null;
|
"from-restored": StoredObjectVersionPersisted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoredObjectPointInTime {
|
export interface StoredObjectPointInTime {
|
||||||
id: number;
|
id: number;
|
||||||
byUser: User | null;
|
byUser: User | null;
|
||||||
reason: "keep-before-conversion" | "keep-by-user";
|
reason: "keep-before-conversion" | "keep-by-user";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,63 +82,63 @@ export type WopiEditButtonExecutableBeforeLeaveFunction = () => Promise<void>;
|
|||||||
* Object containing information for performering a POST request to a swift object store
|
* Object containing information for performering a POST request to a swift object store
|
||||||
*/
|
*/
|
||||||
export interface PostStoreObjectSignature {
|
export interface PostStoreObjectSignature {
|
||||||
method: "POST";
|
method: "POST";
|
||||||
max_file_size: number;
|
max_file_size: number;
|
||||||
max_file_count: 1;
|
max_file_count: 1;
|
||||||
expires: number;
|
expires: number;
|
||||||
submit_delay: 180;
|
submit_delay: 180;
|
||||||
redirect: string;
|
redirect: string;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
url: string;
|
url: string;
|
||||||
signature: string;
|
signature: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PDFPage {
|
export interface PDFPage {
|
||||||
index: number;
|
index: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
export interface SignatureZone {
|
export interface SignatureZone {
|
||||||
index: number | null;
|
index: number | null;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
PDFPage: PDFPage;
|
PDFPage: PDFPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Signature {
|
export interface Signature {
|
||||||
id: number;
|
id: number;
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
zones: SignatureZone[];
|
zones: SignatureZone[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SignedState =
|
export type SignedState =
|
||||||
| "pending"
|
| "pending"
|
||||||
| "signed"
|
| "signed"
|
||||||
| "rejected"
|
| "rejected"
|
||||||
| "canceled"
|
| "canceled"
|
||||||
| "error";
|
| "error";
|
||||||
|
|
||||||
export interface CheckSignature {
|
export interface CheckSignature {
|
||||||
state: SignedState;
|
state: SignedState;
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CanvasEvent = "select" | "add";
|
export type CanvasEvent = "select" | "add";
|
||||||
|
|
||||||
export interface ZoomLevel {
|
export interface ZoomLevel {
|
||||||
id: number;
|
id: number;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
label: {
|
label: {
|
||||||
fr?: string;
|
fr?: string;
|
||||||
nl?: string;
|
nl?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericDoc {
|
export interface GenericDoc {
|
||||||
type: "doc_store_generic_doc";
|
type: "doc_store_generic_doc";
|
||||||
key: string;
|
key: string;
|
||||||
context: "person" | "accompanying-period";
|
context: "person" | "accompanying-period";
|
||||||
doc_date: DateTime;
|
doc_date: DateTime;
|
||||||
}
|
}
|
||||||
|
@@ -1,65 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
<div v-if="isButtonGroupDisplayable" class="btn-group">
|
||||||
<button
|
<button
|
||||||
:class="
|
:class="
|
||||||
Object.assign({
|
Object.assign({
|
||||||
btn: true,
|
btn: true,
|
||||||
'btn-outline-primary': true,
|
'btn-outline-primary': true,
|
||||||
'dropdown-toggle': true,
|
'dropdown-toggle': true,
|
||||||
'btn-sm': props.small,
|
'btn-sm': props.small,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
Actions
|
Actions
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li v-if="isEditableOnline">
|
<li v-if="isEditableOnline">
|
||||||
<wopi-edit-button
|
<wopi-edit-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
:execute-before-leave="props.executeBeforeLeave"
|
:execute-before-leave="props.executeBeforeLeave"
|
||||||
></wopi-edit-button>
|
></wopi-edit-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isEditableOnDesktop">
|
<li v-if="isEditableOnDesktop">
|
||||||
<desktop-edit-button
|
<desktop-edit-button
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
:edit-link="props.davLink"
|
:edit-link="props.davLink"
|
||||||
:expiration-link="props.davLinkExpiration"
|
:expiration-link="props.davLinkExpiration"
|
||||||
></desktop-edit-button>
|
></desktop-edit-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isConvertibleToPdf">
|
<li v-if="isConvertibleToPdf">
|
||||||
<convert-button
|
<convert-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:filename="filename"
|
:filename="filename"
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
></convert-button>
|
></convert-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isDownloadable">
|
<li v-if="isDownloadable">
|
||||||
<download-button
|
<download-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:at-version="props.storedObject.currentVersion"
|
:at-version="props.storedObject.currentVersion"
|
||||||
:filename="filename"
|
:filename="filename"
|
||||||
:classes="{ 'dropdown-item': true }"
|
:classes="{ 'dropdown-item': true }"
|
||||||
:display-action-string-in-button="true"
|
:display-action-string-in-button="true"
|
||||||
></download-button>
|
></download-button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isHistoryViewable">
|
<li v-if="isHistoryViewable">
|
||||||
<history-button
|
<history-button
|
||||||
:stored-object="props.storedObject"
|
:stored-object="props.storedObject"
|
||||||
:can-edit="canEdit && props.storedObject._permissions.canEdit"
|
:can-edit="
|
||||||
></history-button>
|
canEdit && props.storedObject._permissions.canEdit
|
||||||
</li>
|
"
|
||||||
</ul>
|
></history-button>
|
||||||
</div>
|
</li>
|
||||||
<div v-else-if="'pending' === props.storedObject.status">
|
</ul>
|
||||||
<div class="btn btn-outline-info">Génération en cours</div>
|
</div>
|
||||||
</div>
|
<div v-else-if="'pending' === props.storedObject.status">
|
||||||
<div v-else-if="'failure' === props.storedObject.status">
|
<div class="btn btn-outline-info">Génération en cours</div>
|
||||||
<div class="btn btn-outline-danger">La génération a échoué</div>
|
</div>
|
||||||
</div>
|
<div v-else-if="'failure' === props.storedObject.status">
|
||||||
|
<div class="btn btn-outline-danger">La génération a échoué</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -68,66 +70,68 @@ import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
|
|||||||
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
||||||
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
||||||
import {
|
import {
|
||||||
is_extension_editable,
|
is_extension_editable,
|
||||||
is_extension_viewable,
|
is_extension_viewable,
|
||||||
is_object_ready,
|
is_object_ready,
|
||||||
} from "./StoredObjectButton/helpers";
|
} from "./StoredObjectButton/helpers";
|
||||||
import {
|
import {
|
||||||
StoredObject,
|
StoredObject,
|
||||||
StoredObjectStatusChange,
|
StoredObjectStatusChange,
|
||||||
StoredObjectVersion,
|
StoredObjectVersion,
|
||||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
|
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
|
||||||
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
|
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
|
||||||
|
|
||||||
interface DocumentActionButtonsGroupConfig {
|
interface DocumentActionButtonsGroupConfig {
|
||||||
storedObject: StoredObject;
|
storedObject: StoredObject;
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
canEdit?: boolean;
|
canEdit?: boolean;
|
||||||
canDownload?: boolean;
|
canDownload?: boolean;
|
||||||
canConvertPdf?: boolean;
|
canConvertPdf?: boolean;
|
||||||
returnPath?: string;
|
returnPath?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will be the filename displayed to the user when he·she download the document
|
* Will be the filename displayed to the user when he·she download the document
|
||||||
* (the document will be saved on his disk with this name)
|
* (the document will be saved on his disk with this name)
|
||||||
*
|
*
|
||||||
* If not set, 'document' will be used.
|
* If not set, 'document' will be used.
|
||||||
*/
|
*/
|
||||||
filename?: string;
|
filename?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set, will execute this function before leaving to the editor
|
* If set, will execute this function before leaving to the editor
|
||||||
*/
|
*/
|
||||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a link to download and edit file using webdav
|
* a link to download and edit file using webdav
|
||||||
*/
|
*/
|
||||||
davLink?: string;
|
davLink?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the expiration date of the download, as a unix timestamp
|
* the expiration date of the download, as a unix timestamp
|
||||||
*/
|
*/
|
||||||
davLinkExpiration?: number;
|
davLinkExpiration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit =
|
const emit =
|
||||||
defineEmits<
|
defineEmits<
|
||||||
(
|
(
|
||||||
e: "onStoredObjectStatusChange",
|
e: "onStoredObjectStatusChange",
|
||||||
newStatus: StoredObjectStatusChange,
|
newStatus: StoredObjectStatusChange,
|
||||||
) => void
|
) => void
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
||||||
small: false,
|
small: false,
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
canDownload: true,
|
canDownload: true,
|
||||||
canConvertPdf: true,
|
canConvertPdf: true,
|
||||||
returnPath:
|
returnPath:
|
||||||
window.location.pathname + window.location.search + window.location.hash,
|
window.location.pathname +
|
||||||
|
window.location.search +
|
||||||
|
window.location.hash,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,93 +145,93 @@ let tryiesForReady = 0;
|
|||||||
const maxTryiesForReady = 120;
|
const maxTryiesForReady = 120;
|
||||||
|
|
||||||
const isButtonGroupDisplayable = computed<boolean>(() => {
|
const isButtonGroupDisplayable = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
isDownloadable.value ||
|
isDownloadable.value ||
|
||||||
isEditableOnline.value ||
|
isEditableOnline.value ||
|
||||||
isEditableOnDesktop.value ||
|
isEditableOnDesktop.value ||
|
||||||
isConvertibleToPdf.value
|
isConvertibleToPdf.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDownloadable = computed<boolean>(() => {
|
const isDownloadable = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
props.storedObject.status === "ready" ||
|
props.storedObject.status === "ready" ||
|
||||||
// happens when the stored object version is just added, but not persisted
|
// happens when the stored object version is just added, but not persisted
|
||||||
(props.storedObject.currentVersion !== null &&
|
(props.storedObject.currentVersion !== null &&
|
||||||
props.storedObject.status === "empty")
|
props.storedObject.status === "empty")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEditableOnline = computed<boolean>(() => {
|
const isEditableOnline = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
props.storedObject.status === "ready" &&
|
props.storedObject.status === "ready" &&
|
||||||
props.storedObject._permissions.canEdit &&
|
props.storedObject._permissions.canEdit &&
|
||||||
props.canEdit &&
|
props.canEdit &&
|
||||||
props.storedObject.currentVersion !== null &&
|
props.storedObject.currentVersion !== null &&
|
||||||
is_extension_editable(props.storedObject.currentVersion.type) &&
|
is_extension_editable(props.storedObject.currentVersion.type) &&
|
||||||
props.storedObject.currentVersion.persisted !== false
|
props.storedObject.currentVersion.persisted !== false
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEditableOnDesktop = computed<boolean>(() => {
|
const isEditableOnDesktop = computed<boolean>(() => {
|
||||||
return isEditableOnline.value;
|
return isEditableOnline.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isConvertibleToPdf = computed<boolean>(() => {
|
const isConvertibleToPdf = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
props.storedObject.status === "ready" &&
|
props.storedObject.status === "ready" &&
|
||||||
props.storedObject._permissions.canSee &&
|
props.storedObject._permissions.canSee &&
|
||||||
props.canConvertPdf &&
|
props.canConvertPdf &&
|
||||||
props.storedObject.currentVersion !== null &&
|
props.storedObject.currentVersion !== null &&
|
||||||
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
is_extension_viewable(props.storedObject.currentVersion.type) &&
|
||||||
props.storedObject.currentVersion.type !== "application/pdf" &&
|
props.storedObject.currentVersion.type !== "application/pdf" &&
|
||||||
props.storedObject.currentVersion.persisted !== false
|
props.storedObject.currentVersion.persisted !== false
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isHistoryViewable = computed<boolean>(() => {
|
const isHistoryViewable = computed<boolean>(() => {
|
||||||
return props.storedObject.status === "ready";
|
return props.storedObject.status === "ready";
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkForReady = function (): void {
|
const checkForReady = function (): void {
|
||||||
if (
|
if (
|
||||||
"ready" === props.storedObject.status ||
|
"ready" === props.storedObject.status ||
|
||||||
"empty" === props.storedObject.status ||
|
"empty" === props.storedObject.status ||
|
||||||
"failure" === props.storedObject.status ||
|
"failure" === props.storedObject.status ||
|
||||||
// stop reloading if the page stays opened for a long time
|
// stop reloading if the page stays opened for a long time
|
||||||
tryiesForReady > maxTryiesForReady
|
tryiesForReady > maxTryiesForReady
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tryiesForReady = tryiesForReady + 1;
|
tryiesForReady = tryiesForReady + 1;
|
||||||
|
|
||||||
setTimeout(onObjectNewStatusCallback, 5000);
|
setTimeout(onObjectNewStatusCallback, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onObjectNewStatusCallback = async function (): Promise<void> {
|
const onObjectNewStatusCallback = async function (): Promise<void> {
|
||||||
if (props.storedObject.status === "stored_object_created") {
|
if (props.storedObject.status === "stored_object_created") {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const new_status = await is_object_ready(props.storedObject);
|
const new_status = await is_object_ready(props.storedObject);
|
||||||
if (props.storedObject.status !== new_status.status) {
|
if (props.storedObject.status !== new_status.status) {
|
||||||
emit("onStoredObjectStatusChange", new_status);
|
emit("onStoredObjectStatusChange", new_status);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else if ("failure" === new_status.status) {
|
} else if ("failure" === new_status.status) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("ready" !== new_status.status) {
|
if ("ready" !== new_status.status) {
|
||||||
// we check for new status, unless it is ready
|
// we check for new status, unless it is ready
|
||||||
checkForReady();
|
checkForReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkForReady();
|
checkForReady();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -4,36 +4,36 @@ import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
|||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
|
||||||
const appMessages = {
|
const appMessages = {
|
||||||
fr: {
|
fr: {
|
||||||
yes: "Oui",
|
yes: "Oui",
|
||||||
are_you_sure: "Êtes-vous sûr·e?",
|
are_you_sure: "Êtes-vous sûr·e?",
|
||||||
you_are_going_to_sign: "Vous allez signer le document",
|
you_are_going_to_sign: "Vous allez signer le document",
|
||||||
signature_confirmation: "Confirmation de la signature",
|
signature_confirmation: "Confirmation de la signature",
|
||||||
sign: "Signer",
|
sign: "Signer",
|
||||||
choose_another_signature: "Choisir une autre zone",
|
choose_another_signature: "Choisir une autre zone",
|
||||||
cancel: "Annuler",
|
cancel: "Annuler",
|
||||||
last_sign_zone: "Zone de signature précédente",
|
last_sign_zone: "Zone de signature précédente",
|
||||||
next_sign_zone: "Zone de signature suivante",
|
next_sign_zone: "Zone de signature suivante",
|
||||||
add_sign_zone: "Ajouter une zone de signature",
|
add_sign_zone: "Ajouter une zone de signature",
|
||||||
click_on_document: "Cliquer sur le document",
|
click_on_document: "Cliquer sur le document",
|
||||||
last_zone: "Zone précédente",
|
last_zone: "Zone précédente",
|
||||||
next_zone: "Zone suivante",
|
next_zone: "Zone suivante",
|
||||||
add_zone: "Ajouter une zone",
|
add_zone: "Ajouter une zone",
|
||||||
another_zone: "Autre zone",
|
another_zone: "Autre zone",
|
||||||
electronic_signature_in_progress: "Signature électronique en cours...",
|
electronic_signature_in_progress: "Signature électronique en cours...",
|
||||||
loading: "Chargement...",
|
loading: "Chargement...",
|
||||||
remove_sign_zone: "Enlever la zone",
|
remove_sign_zone: "Enlever la zone",
|
||||||
return: "Retour",
|
return: "Retour",
|
||||||
see_all_pages: "Voir toutes les pages",
|
see_all_pages: "Voir toutes les pages",
|
||||||
all_pages: "Toutes les pages",
|
all_pages: "Toutes les pages",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const i18n = _createI18n(appMessages);
|
const i18n = _createI18n(appMessages);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app></app>`,
|
template: `<app></app>`,
|
||||||
})
|
})
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component("app", App)
|
.component("app", App)
|
||||||
.mount("#document-signature");
|
.mount("#document-signature");
|
||||||
|
@@ -1,206 +1,208 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { StoredObject, StoredObjectVersionCreated } from "../../types";
|
import { StoredObject, StoredObjectVersionCreated } from "../../types";
|
||||||
import {
|
import {
|
||||||
encryptFile,
|
encryptFile,
|
||||||
fetchNewStoredObject,
|
fetchNewStoredObject,
|
||||||
uploadVersion,
|
uploadVersion,
|
||||||
} from "../../js/async-upload/uploader";
|
} from "../../js/async-upload/uploader";
|
||||||
import { computed, ref, Ref } from "vue";
|
import { computed, ref, Ref } from "vue";
|
||||||
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
|
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
|
||||||
|
|
||||||
interface DropFileConfig {
|
interface DropFileConfig {
|
||||||
existingDoc?: StoredObject;
|
existingDoc?: StoredObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||||
existingDoc: null,
|
existingDoc: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit =
|
const emit =
|
||||||
defineEmits<
|
defineEmits<
|
||||||
(
|
(
|
||||||
e: "addDocument",
|
e: "addDocument",
|
||||||
{
|
{
|
||||||
stored_object_version: StoredObjectVersionCreated,
|
stored_object_version: StoredObjectVersionCreated,
|
||||||
stored_object: StoredObject,
|
stored_object: StoredObject,
|
||||||
file_name: string,
|
file_name: string,
|
||||||
},
|
},
|
||||||
) => void
|
) => void
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const is_dragging: Ref<boolean> = ref(false);
|
const is_dragging: Ref<boolean> = ref(false);
|
||||||
const uploading: Ref<boolean> = ref(false);
|
const uploading: Ref<boolean> = ref(false);
|
||||||
const display_filename: Ref<string | null> = ref(null);
|
const display_filename: Ref<string | null> = ref(null);
|
||||||
|
|
||||||
const has_existing_doc = computed<boolean>(() => {
|
const has_existing_doc = computed<boolean>(() => {
|
||||||
return props.existingDoc !== undefined && props.existingDoc !== null;
|
return props.existingDoc !== undefined && props.existingDoc !== null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDragOver = (e: Event) => {
|
const onDragOver = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
is_dragging.value = true;
|
is_dragging.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragLeave = (e: Event) => {
|
const onDragLeave = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
is_dragging.value = false;
|
is_dragging.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDrop = (e: DragEvent) => {
|
const onDrop = (e: DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const files = e.dataTransfer?.files;
|
const files = e.dataTransfer?.files;
|
||||||
|
|
||||||
if (null === files || undefined === files) {
|
if (null === files || undefined === files) {
|
||||||
console.error("no files transferred", e.dataTransfer);
|
console.error("no files transferred", e.dataTransfer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
console.error("no files given");
|
console.error("no files given");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFile(files[0]);
|
handleFile(files[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onZoneClick = (e: Event) => {
|
const onZoneClick = (e: Event) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.addEventListener("change", onFileChange);
|
input.addEventListener("change", onFileChange);
|
||||||
|
|
||||||
input.click();
|
input.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFileChange = async (event: Event): Promise<void> => {
|
const onFileChange = async (event: Event): Promise<void> => {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
|
|
||||||
if (input.files && input.files[0]) {
|
if (input.files && input.files[0]) {
|
||||||
console.log("file added", input.files[0]);
|
console.log("file added", input.files[0]);
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
await handleFile(file);
|
await handleFile(file);
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw "No file given";
|
throw "No file given";
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFile = async (file: File): Promise<void> => {
|
const handleFile = async (file: File): Promise<void> => {
|
||||||
uploading.value = true;
|
uploading.value = true;
|
||||||
display_filename.value = file.name;
|
display_filename.value = file.name;
|
||||||
const type = file.type;
|
const type = file.type;
|
||||||
|
|
||||||
// create a stored_object if not exists
|
// create a stored_object if not exists
|
||||||
let stored_object;
|
let stored_object;
|
||||||
if (null === props.existingDoc) {
|
if (null === props.existingDoc) {
|
||||||
stored_object = await fetchNewStoredObject();
|
stored_object = await fetchNewStoredObject();
|
||||||
} else {
|
} else {
|
||||||
stored_object = props.existingDoc;
|
stored_object = props.existingDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await file.arrayBuffer();
|
const buffer = await file.arrayBuffer();
|
||||||
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
|
const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
|
||||||
const filename = await uploadVersion(encrypted, stored_object);
|
const filename = await uploadVersion(encrypted, stored_object);
|
||||||
|
|
||||||
const stored_object_version: StoredObjectVersionCreated = {
|
const stored_object_version: StoredObjectVersionCreated = {
|
||||||
filename: filename,
|
filename: filename,
|
||||||
iv: Array.from(iv),
|
iv: Array.from(iv),
|
||||||
keyInfos: jsonWebKey,
|
keyInfos: jsonWebKey,
|
||||||
type: type,
|
type: type,
|
||||||
persisted: false,
|
persisted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileName = file.name;
|
const fileName = file.name;
|
||||||
let file_name = "Nouveau document";
|
let file_name = "Nouveau document";
|
||||||
const file_name_split = fileName.split(".");
|
const file_name_split = fileName.split(".");
|
||||||
if (file_name_split.length > 1) {
|
if (file_name_split.length > 1) {
|
||||||
const extension = file_name_split
|
const extension = file_name_split
|
||||||
? file_name_split[file_name_split.length - 1]
|
? file_name_split[file_name_split.length - 1]
|
||||||
: "";
|
: "";
|
||||||
file_name = fileName.replace(extension, "").slice(0, -1);
|
file_name = fileName.replace(extension, "").slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit("addDocument", {
|
emit("addDocument", {
|
||||||
stored_object,
|
stored_object,
|
||||||
stored_object_version,
|
stored_object_version,
|
||||||
file_name: file_name,
|
file_name: file_name,
|
||||||
});
|
});
|
||||||
uploading.value = false;
|
uploading.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="drop-file">
|
<div class="drop-file">
|
||||||
<div
|
<div
|
||||||
v-if="!uploading"
|
v-if="!uploading"
|
||||||
:class="{ area: true, dragging: is_dragging }"
|
:class="{ area: true, dragging: is_dragging }"
|
||||||
@click="onZoneClick"
|
@click="onZoneClick"
|
||||||
@dragover="onDragOver"
|
@dragover="onDragOver"
|
||||||
@dragleave="onDragLeave"
|
@dragleave="onDragLeave"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
>
|
>
|
||||||
<p v-if="has_existing_doc" class="file-icon">
|
<p v-if="has_existing_doc" class="file-icon">
|
||||||
<file-icon :type="props.existingDoc?.type"></file-icon>
|
<file-icon :type="props.existingDoc?.type"></file-icon>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="display_filename !== null" class="display-filename">
|
<p v-if="display_filename !== null" class="display-filename">
|
||||||
{{ display_filename }}
|
{{ display_filename }}
|
||||||
</p>
|
</p>
|
||||||
<!-- todo i18n -->
|
<!-- todo i18n -->
|
||||||
<p v-if="has_existing_doc">
|
<p v-if="has_existing_doc">
|
||||||
Déposez un document ou cliquez ici pour remplacer le document existant
|
Déposez un document ou cliquez ici pour remplacer le document
|
||||||
</p>
|
existant
|
||||||
<p v-else>
|
</p>
|
||||||
Déposez un document ou cliquez ici pour ouvrir le navigateur de fichier
|
<p v-else>
|
||||||
</p>
|
Déposez un document ou cliquez ici pour ouvrir le navigateur de
|
||||||
|
fichier
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="waiting">
|
||||||
|
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="waiting">
|
|
||||||
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.drop-file {
|
.drop-file {
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.file-icon {
|
|
||||||
font-size: xx-large;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-filename {
|
|
||||||
font-variant: small-caps;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .area,
|
|
||||||
& > .waiting {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 10rem;
|
|
||||||
|
|
||||||
display: flex;
|
.file-icon {
|
||||||
flex-direction: column;
|
font-size: xx-large;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
p {
|
|
||||||
// require for display in DropFileModal
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
& > .area {
|
.display-filename {
|
||||||
border: 4px dashed #ccc;
|
font-variant: small-caps;
|
||||||
|
font-weight: 200;
|
||||||
&.dragging {
|
}
|
||||||
border: 4px dashed blue;
|
|
||||||
|
& > .area,
|
||||||
|
& > .waiting {
|
||||||
|
width: 100%;
|
||||||
|
height: 10rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
// require for display in DropFileModal
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .area {
|
||||||
|
border: 4px dashed #ccc;
|
||||||
|
|
||||||
|
&.dragging {
|
||||||
|
border: 4px dashed blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -4,26 +4,27 @@ import { StoredObject, StoredObjectVersion } from "../../types";
|
|||||||
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
|
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
import { useToast } from "vue-toast-notification";
|
import { useToast } from "vue-toast-notification";
|
||||||
|
import { DOCUMENT_ADD, trans } from "translator";
|
||||||
|
|
||||||
interface DropFileConfig {
|
interface DropFileConfig {
|
||||||
allowRemove: boolean;
|
allowRemove: boolean;
|
||||||
existingDoc?: StoredObject;
|
existingDoc?: StoredObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||||
allowRemove: false,
|
allowRemove: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(
|
(
|
||||||
e: "addDocument",
|
e: "addDocument",
|
||||||
{
|
{
|
||||||
stored_object: StoredObject,
|
stored_object: StoredObject,
|
||||||
stored_object_version: StoredObjectVersion,
|
stored_object_version: StoredObjectVersion,
|
||||||
file_name: string,
|
file_name: string,
|
||||||
},
|
},
|
||||||
): void;
|
): void;
|
||||||
(e: "removeDocument"): void;
|
(e: "removeDocument"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const $toast = useToast();
|
const $toast = useToast();
|
||||||
@@ -33,67 +34,65 @@ const state = reactive({ showModal: false });
|
|||||||
const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
|
const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
|
||||||
|
|
||||||
const buttonState = computed<"add" | "replace">(() => {
|
const buttonState = computed<"add" | "replace">(() => {
|
||||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||||
return "add";
|
return "add";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "replace";
|
return "replace";
|
||||||
});
|
});
|
||||||
|
|
||||||
function onAddDocument({
|
function onAddDocument({
|
||||||
stored_object,
|
stored_object,
|
||||||
stored_object_version,
|
stored_object_version,
|
||||||
file_name,
|
file_name,
|
||||||
}: {
|
}: {
|
||||||
stored_object: StoredObject;
|
stored_object: StoredObject;
|
||||||
stored_object_version: StoredObjectVersion;
|
stored_object_version: StoredObjectVersion;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
}): void {
|
}): void {
|
||||||
const message =
|
const message =
|
||||||
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
|
buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
|
||||||
$toast.success(message);
|
$toast.success(message);
|
||||||
emit("addDocument", { stored_object_version, stored_object, file_name });
|
emit("addDocument", { stored_object_version, stored_object, file_name });
|
||||||
state.showModal = false;
|
state.showModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRemoveDocument(): void {
|
function onRemoveDocument(): void {
|
||||||
emit("removeDocument");
|
emit("removeDocument");
|
||||||
}
|
}
|
||||||
|
|
||||||
function openModal(): void {
|
function openModal(): void {
|
||||||
state.showModal = true;
|
state.showModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal(): void {
|
function closeModal(): void {
|
||||||
state.showModal = false;
|
state.showModal = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-if="buttonState === 'add'"
|
v-if="buttonState === 'add'"
|
||||||
@click="openModal"
|
@click="openModal"
|
||||||
class="btn btn-create"
|
class="btn btn-create"
|
||||||
>
|
>
|
||||||
Ajouter un document
|
{{ trans(DOCUMENT_ADD) }}
|
||||||
</button>
|
</button>
|
||||||
<button v-else @click="openModal" class="btn btn-edit">
|
<button v-else @click="openModal" class="btn btn-edit"></button>
|
||||||
Remplacer le document
|
<modal
|
||||||
</button>
|
v-if="state.showModal"
|
||||||
<modal
|
:modal-dialog-class="modalClasses"
|
||||||
v-if="state.showModal"
|
@close="closeModal"
|
||||||
:modal-dialog-class="modalClasses"
|
>
|
||||||
@close="closeModal"
|
<template v-slot:body>
|
||||||
>
|
<drop-file-widget
|
||||||
<template v-slot:body>
|
:existing-doc="existingDoc"
|
||||||
<drop-file-widget
|
:allow-remove="allowRemove"
|
||||||
:existing-doc="existingDoc"
|
@add-document="onAddDocument"
|
||||||
:allow-remove="allowRemove"
|
@remove-document="onRemoveDocument"
|
||||||
@add-document="onAddDocument"
|
></drop-file-widget>
|
||||||
@remove-document="onRemoveDocument"
|
</template>
|
||||||
></drop-file-widget>
|
</modal>
|
||||||
</template>
|
|
||||||
</modal>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
@@ -5,97 +5,97 @@ import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue";
|
|||||||
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
||||||
|
|
||||||
interface DropFileConfig {
|
interface DropFileConfig {
|
||||||
allowRemove: boolean;
|
allowRemove: boolean;
|
||||||
existingDoc?: StoredObject;
|
existingDoc?: StoredObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DropFileConfig>(), {
|
const props = withDefaults(defineProps<DropFileConfig>(), {
|
||||||
allowRemove: false,
|
allowRemove: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(
|
(
|
||||||
e: "addDocument",
|
e: "addDocument",
|
||||||
{
|
{
|
||||||
stored_object: StoredObject,
|
stored_object: StoredObject,
|
||||||
stored_object_version: StoredObjectVersion,
|
stored_object_version: StoredObjectVersion,
|
||||||
file_name: string,
|
file_name: string,
|
||||||
},
|
},
|
||||||
): void;
|
): void;
|
||||||
(e: "removeDocument"): void;
|
(e: "removeDocument"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const has_existing_doc = computed<boolean>(() => {
|
const has_existing_doc = computed<boolean>(() => {
|
||||||
return props.existingDoc !== undefined && props.existingDoc !== null;
|
return props.existingDoc !== undefined && props.existingDoc !== null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const dav_link_expiration = computed<number | undefined>(() => {
|
const dav_link_expiration = computed<number | undefined>(() => {
|
||||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (props.existingDoc.status !== "ready") {
|
if (props.existingDoc.status !== "ready") {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.existingDoc._links?.dav_link?.expiration;
|
return props.existingDoc._links?.dav_link?.expiration;
|
||||||
});
|
});
|
||||||
|
|
||||||
const dav_link_href = computed<string | undefined>(() => {
|
const dav_link_href = computed<string | undefined>(() => {
|
||||||
if (props.existingDoc === undefined || props.existingDoc === null) {
|
if (props.existingDoc === undefined || props.existingDoc === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (props.existingDoc.status !== "ready") {
|
if (props.existingDoc.status !== "ready") {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.existingDoc._links?.dav_link?.href;
|
return props.existingDoc._links?.dav_link?.href;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onAddDocument = ({
|
const onAddDocument = ({
|
||||||
stored_object,
|
stored_object,
|
||||||
stored_object_version,
|
stored_object_version,
|
||||||
file_name,
|
file_name,
|
||||||
}: {
|
}: {
|
||||||
stored_object: StoredObject;
|
stored_object: StoredObject;
|
||||||
stored_object_version: StoredObjectVersion;
|
stored_object_version: StoredObjectVersion;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
}): void => {
|
}): void => {
|
||||||
emit("addDocument", { stored_object, stored_object_version, file_name });
|
emit("addDocument", { stored_object, stored_object_version, file_name });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoveDocument = (e: Event): void => {
|
const onRemoveDocument = (e: Event): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
emit("removeDocument");
|
emit("removeDocument");
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<drop-file
|
<drop-file
|
||||||
:existingDoc="props.existingDoc"
|
:existingDoc="props.existingDoc"
|
||||||
@addDocument="onAddDocument"
|
@addDocument="onAddDocument"
|
||||||
></drop-file>
|
></drop-file>
|
||||||
|
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li v-if="has_existing_doc">
|
<li v-if="has_existing_doc">
|
||||||
<document-action-buttons-group
|
<document-action-buttons-group
|
||||||
:stored-object="props.existingDoc"
|
:stored-object="props.existingDoc"
|
||||||
:can-edit="props.existingDoc?.status === 'ready'"
|
:can-edit="props.existingDoc?.status === 'ready'"
|
||||||
:can-download="true"
|
:can-download="true"
|
||||||
:dav-link="dav_link_href"
|
:dav-link="dav_link_href"
|
||||||
:dav-link-expiration="dav_link_expiration"
|
:dav-link-expiration="dav_link_expiration"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
v-if="allowRemove"
|
v-if="allowRemove"
|
||||||
class="btn btn-delete"
|
class="btn btn-delete"
|
||||||
@click="onRemoveDocument($event)"
|
@click="onRemoveDocument($event)"
|
||||||
></button>
|
></button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
@@ -1,46 +1,46 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
interface FileIconConfig {
|
interface FileIconConfig {
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<FileIconConfig>();
|
const props = defineProps<FileIconConfig>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
|
<i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-word-o"
|
class="fa fa-file-word-o"
|
||||||
v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
|
v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-word-o"
|
class="fa fa-file-word-o"
|
||||||
v-else-if="
|
v-else-if="
|
||||||
props.type ===
|
props.type ===
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||||
"
|
"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-word-o"
|
class="fa fa-file-word-o"
|
||||||
v-else-if="props.type === 'application/msword'"
|
v-else-if="props.type === 'application/msword'"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-excel-o"
|
class="fa fa-file-excel-o"
|
||||||
v-else-if="
|
v-else-if="
|
||||||
props.type ===
|
props.type ===
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
"
|
"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-excel-o"
|
class="fa fa-file-excel-o"
|
||||||
v-else-if="props.type === 'application/vnd.ms-excel'"
|
v-else-if="props.type === 'application/vnd.ms-excel'"
|
||||||
></i>
|
></i>
|
||||||
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i>
|
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i>
|
||||||
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i>
|
<i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i>
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-archive-o"
|
class="fa fa-file-archive-o"
|
||||||
v-else-if="props.type === 'application/x-zip-compressed'"
|
v-else-if="props.type === 'application/x-zip-compressed'"
|
||||||
></i>
|
></i>
|
||||||
<i class="fa fa-file-code-o" v-else></i>
|
<i class="fa fa-file-code-o" v-else></i>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user